[英]Spring: Returning empty HTTP Responses with ResponseEntity<Void> doesn't work
We are implementing a REST API with Spring (4.1.1.).我们正在使用 Spring (4.1.1.) 实现 REST API。 For certain HTTP requests, we would like to return a head with no body as a response.对于某些 HTTP 请求,我们希望返回一个没有正文的头部作为响应。 However, using ResponseEntity<Void>
doesn't seem to work.但是,使用ResponseEntity<Void>
似乎不起作用。 When called with a MockMvc
test, a 406 (Not acceptable) is returned.当使用MockMvc
测试调用时,返回 406(不可接受)。 Using ResponseEntity<String>
without a parameter value ( new ResponseEntity<String>( HttpStatus.NOT_FOUND )
) works fine.使用ResponseEntity<String>
不带参数的值( new ResponseEntity<String>( HttpStatus.NOT_FOUND )
能正常工作。
Method:方法:
@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 );
}
}
Test case (TestNG):测试用例(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() );
}
}
Fails with this stack trace:此堆栈跟踪失败:
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)
NOTE: This is true for the version mentioned in the question, 4.1.1.RELEASE.注意:对于问题 4.1.1.RELEASE 中提到的版本,这是正确的。
Spring MVC handles a ResponseEntity
return value through HttpEntityMethodProcessor
. Spring MVC 通过HttpEntityMethodProcessor
处理ResponseEntity
返回值。
When the ResponseEntity
value doesn't have a body set, as is the case in your snippet, HttpEntityMethodProcessor
tries to determine a content type for the response body from the parameterization of the ResponseEntity
return type in the signature of the @RequestMapping
handler method.当ResponseEntity
值没有设置正文时(如您的代码段中的情况), HttpEntityMethodProcessor
尝试根据@RequestMapping
处理程序方法签名中ResponseEntity
返回类型的参数化来确定响应正文的内容类型。
So for因此对于
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
that type will be Void
.该类型将是Void
。 HttpEntityMethodProcessor
will then loop through all its registered HttpMessageConverter
instances and find one that can write a body for a Void
type.然后HttpEntityMethodProcessor
将遍历其所有已注册的HttpMessageConverter
实例,并找到可以为Void
类型编写主体的实例。 Depending on your configuration, it may or may not find any.根据您的配置,它可能会也可能不会找到任何。
If it does find any, it still needs to make sure that the corresponding body will be written with a Content-Type that matches the type(s) provided in the request's Accept
header, application/xml
in your case.如果确实找到了,它仍然需要确保相应的正文将使用与请求的Accept
标头中提供的类型匹配的 Content-Type 写入,在您的情况下为application/xml
。
If after all these checks, no such HttpMessageConverter
exists, Spring MVC will decide that it cannot produce an acceptable response and therefore return a 406 Not Acceptable HTTP response.如果经过所有这些检查,没有这样的HttpMessageConverter
存在,Spring MVC 将决定它不能产生可接受的响应,因此返回 406 Not Acceptable HTTP 响应。
With ResponseEntity<String>
, Spring will use String
as the response body and find StringHttpMessageConverter
as a handler.使用ResponseEntity<String>
,Spring 将使用String
作为响应主体并找到StringHttpMessageConverter
作为处理程序。 And since StringHttpMessageHandler
can produce content for any media type (provided in the Accept
header), it will be able to handle the application/xml
that your client is requesting.由于StringHttpMessageHandler
可以为任何媒体类型生成内容(在Accept
标头中提供),因此它将能够处理您的客户端请求的application/xml
。
Spring MVC has since been changed to only return 406 if the body in the ResponseEntity
is NOT null
.如果ResponseEntity
的主体不是null
则 Spring MVC 已更改为仅返回 406 。 You won't see the behavior in the original question if you're using a more recent version of Spring MVC.如果您使用的是较新版本的 Spring MVC,您将看不到原始问题中的行为。
In iddy85's solution , which seems to suggest ResponseEntity<?>
, the type for the body will be inferred as Object
.在iddy85 的解决方案中,似乎建议ResponseEntity<?>
,主体的类型将被推断为Object
。 If you have the correct libraries in your classpath, ie.如果您的类路径中有正确的库,即。 Jackson (version > 2.5.0) and its XML extension, Spring MVC will have access to MappingJackson2XmlHttpMessageConverter
which it can use to produce application/xml
for the type Object
. Jackson(版本 > 2.5.0)及其 XML 扩展,Spring MVC 将可以访问MappingJackson2XmlHttpMessageConverter
,它可以用来为Object
类型生成application/xml
。 Their solution only works under these conditions.他们的解决方案仅适用于这些条件。 Otherwise, it will fail for the same reason I've described above.否则,它会因为我上面描述的相同原因而失败。
According Spring 4 MVC ResponseEntity.BodyBuilder and ResponseEntity Enhancements Example it could be written as:根据Spring 4 MVC ResponseEntity.BodyBuilder 和 ResponseEntity Enhancements Example,它可以写为:
....
return ResponseEntity.ok().build();
....
return ResponseEntity.noContent().build();
UPDATE:更新:
If returned value is Optional
there are convinient method, returned ok()
or notFound()
:如果返回值是Optional
有方便的方法,返回ok()
或notFound()
:
return ResponseEntity.of(optional)
You can also not specify the type parameter which seems a bit cleaner and what Spring intended when looking at the docs :您也不能指定看起来更清晰的类型参数以及 Spring 在查看文档时的意图:
@RequestMapping(method = RequestMethod.HEAD, value = Constants.KEY )
public ResponseEntity taxonomyPackageExists( @PathVariable final String key ){
// ...
return new ResponseEntity(HttpStatus.NO_CONTENT);
}
Your method implementation is ambiguous, try the following , edited your code a little bit and used HttpStatus.NO_CONTENT
ie 204 No Content as in place of HttpStatus.OK
您的方法实现不明确,请尝试以下操作,稍微编辑您的代码并使用HttpStatus.NO_CONTENT
即 204 No Content 代替HttpStatus.OK
The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation.服务器已完成请求但不需要返回实体主体,并且可能想要返回更新的元信息。 The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.响应可以包含实体头形式的新的或更新的元信息,如果存在,应该与请求的变体相关联。
Any value of T will be ignored for 204, but not for 404对于 204, T 的任何值都将被忽略,但对于 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 );
}
}
For Spring 5.2+ this works for me:对于 Spring 5.2+,这对我有用:
@PostMapping("/foo")
ResponseEntity<Void> foo(@PathVariable UUID fooId) {
return fooService.findExam(fooId)
.map(uri -> ResponseEntity.noContent().<Void>build())
.orElse(ResponseEntity.notFound().build());
}
Personally, to deal with empty responses, I use in my Integration Tests the MockMvcResponse object like this :就个人而言,为了处理空响应,我在集成测试中使用 MockMvcResponse 对象,如下所示:
MockMvcResponse response = RestAssuredMockMvc.given()
.webAppContextSetup(webApplicationContext)
.when()
.get("/v1/ticket");
assertThat(response.mockHttpServletResponse().getStatus()).isEqualTo(HttpStatus.NO_CONTENT.value());
and in my controller I return empty response in a specific case like this :在我的控制器中,我在这样的特定情况下返回空响应:
return ResponseEntity.noContent().build();
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.