with responseentity responsebody custom body badrequest bad java spring rest spring-mvc

java - custom - responseentity vs responsebody



Primavera: devolver las respuestas HTTP vacĂ­as con ResponseEntity<Void> no funciona (5)

Estamos implementando una API REST con Spring (4.1.1.). Para ciertas solicitudes HTTP, nos gustaría devolver un encabezado sin cuerpo como respuesta. Sin embargo, el uso de ResponseEntity<Void> no parece funcionar. Cuando se llama con una prueba de MockMvc , se MockMvc un 406 (No aceptable). El uso de ResponseEntity<String> sin un valor de parámetro ( new ResponseEntity<String>( HttpStatus.NOT_FOUND ) ) funciona bien.

Método:

@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 ); } }

Caso de prueba (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() ); } }

Falla con este seguimiento de pila:

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)


Cuando devuelve una ResponseEntity sin un cuerpo, Spring usa el argumento de tipo proporcionado en la declaración de tipo de ResponseEntity para decidir sobre un tipo de cuerpo.

Entonces para

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

ese tipo será Void . Spring luego pasará por todas sus instancias registradas de HttpMessageConverter y encontrará una que pueda escribir un cuerpo para un tipo Void . Dado que no existe tal HttpMessageConverter (para una configuración predeterminada), decidirá que no puede producir una respuesta aceptable y, por lo tanto, devolverá una respuesta HTTP 406 No aceptable.

Con ResponseEntity<String> , Spring usará String como el cuerpo de respuesta y encontrará StringHttpMessageConverter como manejador. Y dado que StringHttpMessageHandler puede producir contenido para cualquier tipo de medio Accepted , podrá manejar la application/xml que su cliente está solicitando.

En la solución de iddy85 (que actualmente es incorrecta, pero parece sugerir ResponseEntity<?> ), El tipo para el cuerpo se inferirá como Object . Si tiene las bibliotecas correctas en su ruta de clase, Spring tendrá acceso a un HttpMessageConverter XML que puede usar para producir application/xml para el tipo de Object .


De acuerdo con las mejoras de Spring 4 MVC ResponseEntity.BodyBuilder y ResponseEntity, se podría escribir como:

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

ACTUALIZAR:

Si el valor devuelto es Optional hay un método conveniente, se devuelve ok() o notFound() :

return ResponseEntity.of(optional)


La implementación de su método es ambigua, intente lo siguiente, HttpStatus.NO_CONTENT un poco su código y use HttpStatus.NO_CONTENT es decir, 204 Sin contenido, en lugar de HttpStatus.OK

El servidor ha cumplido la solicitud pero no necesita devolver un cuerpo de entidad, y puede querer devolver información actualizada. La respuesta PUEDE incluir metainformación nueva o actualizada en forma de encabezados de entidad, que si están presentes DEBEN asociarse con la variante solicitada.

Cualquier valor de T se ignorará para 204, pero no para 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 ); } }


Personalmente, para lidiar con respuestas vacías, uso en mis Pruebas de Integración el objeto MockMvcResponse así:

MockMvcResponse response = RestAssuredMockMvc.given() .webAppContextSetup(webApplicationContext) .when() .get("/v1/ticket"); assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());

y en mi controlador devuelvo una respuesta vacía en un caso específico como este:

return ResponseEntity.noContent().build();


Tampoco puede especificar el parámetro de tipo que parece un poco más limpio y qué pretendía Spring al mirar los docs :

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