web-dev-qa-db-fra.com

Spring: le renvoi de réponses HTTP vides avec ResponseEntity <Void> ne fonctionne pas

Nous implémentons une API REST avec Spring (4.1.1.). Pour certaines requêtes HTTP, nous aimerions renvoyer une tête sans corps comme réponse. Cependant, en utilisant ResponseEntity<Void> ne semble pas fonctionner. Lorsqu'il est appelé avec un test MockMvc, un 406 (non acceptable) est renvoyé. Utilisation de ResponseEntity<String> sans valeur de paramètre (new ResponseEntity<String>( HttpStatus.NOT_FOUND )) fonctionne bien.

Méthode:

@RequestMapping( method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {

    LOG.debug( "taxonomyPackageExists queried with key: {0}", key ); //$NON-NLS-1$

    final TaxonomyKey taxonomyKey = TaxonomyKey.fromString( key );

    LOG.debug( "Taxonomy key created: {0}", taxonomyKey ); //$NON-NLS-1$

    if ( this.xbrlInstanceValidator.taxonomyPackageExists( taxonomyKey ) ) {

        LOG.debug( "Taxonomy package with key: {0} exists.", taxonomyKey ); //$NON-NLS-1$

        return new ResponseEntity<Void>( HttpStatus.OK );

    } else {

        LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$

        return new ResponseEntity<Void>( HttpStatus.NOT_FOUND );
    }

}

Cas de test (TestNG):

public class TaxonomyQueryControllerTest {

private XbrlInstanceValidator   xbrlInstanceValidatorMock;
private TaxonomyQueryController underTest;
private MockMvc                 mockMvc;

@BeforeMethod
public void setUp() {
    this.xbrlInstanceValidatorMock = createMock( XbrlInstanceValidator.class );
    this.underTest = new TaxonomyQueryController( this.xbrlInstanceValidatorMock );
    this.mockMvc = MockMvcBuilders.standaloneSetup( this.underTest ).build();
}

@Test
public void taxonomyPackageDoesNotExist() throws Exception {
    // record
    expect( this.xbrlInstanceValidatorMock.taxonomyPackageExists( anyObject( TaxonomyKey.class ) ) ).andStubReturn(
            false );

    // replay
    replay( this.xbrlInstanceValidatorMock );

    // do the test
    final String taxonomyKey = RestDataFixture.taxonomyKeyString;

    this.mockMvc.perform( head( "/taxonomypackages/{key}", taxonomyKey ).accept( //$NON-NLS-1$
            MediaType.APPLICATION_XML ) ).andExpect( status().isNotFound() );

}

}

Échoue avec cette trace de pile:

FAILED: taxonomyPackageDoesNotExist
Java.lang.AssertionError: Status expected:<404> but was:<406>
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.Java:60)
at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.Java:89)
at org.springframework.test.web.servlet.result.StatusResultMatchers$10.match(StatusResultMatchers.Java:652)
at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.Java:153)
at de.zeb.control.application.xbrlstandalonevalidator.restservice.TaxonomyQueryControllerTest.taxonomyPackageDoesNotExist(TaxonomyQueryControllerTest.Java:54)
at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at Sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.Java:57)
at Sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.Java:43)
at Java.lang.reflect.Method.invoke(Method.Java:606)
at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.Java:84)
at org.testng.internal.Invoker.invokeMethod(Invoker.Java:714)
at org.testng.internal.Invoker.invokeTestMethod(Invoker.Java:901)
at org.testng.internal.Invoker.invokeTestMethods(Invoker.Java:1231)
at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.Java:127)
at org.testng.internal.TestMethodWorker.run(TestMethodWorker.Java:111)
at org.testng.TestRunner.privateRun(TestRunner.Java:767)
at org.testng.TestRunner.run(TestRunner.Java:617)
at org.testng.SuiteRunner.runTest(SuiteRunner.Java:334)
at org.testng.SuiteRunner.runSequentially(SuiteRunner.Java:329)
at org.testng.SuiteRunner.privateRun(SuiteRunner.Java:291)
at org.testng.SuiteRunner.run(SuiteRunner.Java:240)
at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.Java:52)
at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.Java:86)
at org.testng.TestNG.runSuitesSequentially(TestNG.Java:1224)
at org.testng.TestNG.runSuitesLocally(TestNG.Java:1149)
at org.testng.TestNG.run(TestNG.Java:1057)
at org.testng.remote.RemoteTestNG.run(RemoteTestNG.Java:111)
at org.testng.remote.RemoteTestNG.initAndRun(RemoteTestNG.Java:204)
at org.testng.remote.RemoteTestNG.main(RemoteTestNG.Java:175)
22
ScarOnTheSky

Lorsque vous renvoyez un ResponseEntity sans corps, Spring utilise l'argument type fourni dans la déclaration de type de retour ResponseEntity pour décider d'un type de corps.

Donc pour

public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {

ce type sera Void. Spring parcourra ensuite toutes ses instances HttpMessageConverter enregistrées et en trouvera une qui peut écrire un corps pour un type Void. Puisqu'il n'existe pas de HttpMessageConverter (pour une configuration par défaut), il décidera qu'il ne peut pas produire une réponse acceptable et retournera donc une réponse HTTP 406 non acceptable.

Avec ResponseEntity<String>, Spring utilisera String comme corps de réponse et trouvera StringHttpMessageConverter comme gestionnaire. Et puisque StringHttpMessageHandler peut produire du contenu pour n'importe quel type de support Accepted, il pourra gérer le application/xml que votre client demande.

In solution iddy85 (ce qui est actuellement faux, mais semble suggérer ResponseEntity<?>), le type du corps sera déduit comme Object. Si vous avez les bibliothèques correctes dans votre chemin de classe, Spring aura accès à un XML HttpMessageConverter qu'il pourra utiliser pour produire application/xml pour le type Object.

26

L'implémentation de votre méthode est ambiguë, essayez ce qui suit, modifiez un peu votre code et utilisez HttpStatus.NO_CONTENT c'est-à-dire 204 Pas de contenu comme à la place de HttpStatus.OK

Le serveur a répondu à la demande mais n'a pas besoin de renvoyer un corps d'entité et peut vouloir renvoyer une métainformation mise à jour. La réponse PEUT inclure une métainformation nouvelle ou mise à jour sous la forme d'en-têtes d'entité qui, s'ils sont présents, DEVRAIENT être associés à la variante demandée.

Toute valeur de T sera ignorée pour 204, mais pas pour 404

  public ResponseEntity<?> taxonomyPackageExists( @PathVariable final String key ) {
            LOG.debug( "taxonomyPackageExists queried with key: {0}", key ); //$NON-NLS-1$
            final TaxonomyKey taxonomyKey = TaxonomyKey.fromString( key );
            LOG.debug( "Taxonomy key created: {0}", taxonomyKey ); //$NON-NLS-1$

            if ( this.xbrlInstanceValidator.taxonomyPackageExists( taxonomyKey ) ) {
                LOG.debug( "Taxonomy package with key: {0} exists.", taxonomyKey ); //$NON-NLS-1$
                 return new ResponseEntity<T>(HttpStatus.NO_CONTENT);
            } else {
               LOG.debug( "Taxonomy package with key: {0} does NOT exist.", taxonomyKey ); //$NON-NLS-1$
                return new ResponseEntity<T>( HttpStatus.NOT_FOUND );
            }

    }
9
iamiddy

Vous ne pouvez pas non plus spécifier le paramètre de type qui semble un peu plus propre et ce que Spring voulait en regardant docs :

@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
    // ...
    return new ResponseEntity(HttpStatus.NO_CONTENT);
}
8
adanilev

Selon Spring 4 MVC ResponseEntity.BodyBuilder et ResponseEntity Enhancements Example il pourrait être écrit comme suit:

....
   return ResponseEntity.ok().build();
....
   return ResponseEntity.noContent().build();

MISE À JOUR:

Si la valeur renvoyée est Optional, il existe une méthode pratique, renvoyée ok() ou notFound():

return ResponseEntity.of(optional)
6
GKislin

Personnellement, pour gérer les réponses vides, j'utilise dans mes tests d'intégration l'objet MockMvcResponse comme ceci:

MockMvcResponse response = RestAssuredMockMvc.given()
                .webAppContextSetup(webApplicationContext)
                .when()
                .get("/v1/ticket");

    assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());

et dans mon contrôleur, je renvoie une réponse vide dans un cas spécifique comme celui-ci:

return ResponseEntity.noContent().build();
1
Alex