Java Spring:使用 ResponseEntity<Void> 返回空的 HTTP 响应不起作用
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26550124/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring: Returning empty HTTP Responses with ResponseEntity<Void> doesn't work
提问by ScarOnTheSky
We are implementing a REST API with Spring (4.1.1.). For certain HTTP requests, we would like to return a head with no body as a response. However, using ResponseEntity<Void>
doesn't seem to work. When called with a MockMvc
test, a 406 (Not acceptable) is returned. Using ResponseEntity<String>
without a parameter value (new ResponseEntity<String>( HttpStatus.NOT_FOUND )
) works fine.
我们正在使用 Spring (4.1.1.) 实现 REST API。对于某些 HTTP 请求,我们希望返回一个没有正文的头部作为响应。但是,使用ResponseEntity<Void>
似乎不起作用。使用MockMvc
测试调用时,会返回 406(不可接受)。使用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.match(StatusResultMatchers.java:652)
at org.springframework.test.web.servlet.MockMvc.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)
采纳答案by Sotirios Delimanolis
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 MVCResponseEntity
通过HttpEntityMethodProcessor
.
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
尝试根据处理程序方法ResponseEntity
签名中返回类型的参数化来确定响应正文的内容类型@RequestMapping
。
So for
因此对于
public ResponseEntity<Void> taxonomyPackageExists( @PathVariable final String key ) {
that type will be Void
. HttpEntityMethodProcessor
will then loop through all its registered HttpMessageConverter
instances and find one that can write a body for a Void
type. Depending on your configuration, it may or may not find any.
那种类型将是Void
. HttpEntityMethodProcessor
然后将遍历所有已注册的HttpMessageConverter
实例并找到可以为Void
类型编写主体的实例。根据您的配置,它可能会也可能不会找到任何。
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. 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.
使用ResponseEntity<String>
,Spring 将String
用作响应主体并使用 findStringHttpMessageConverter
作为处理程序。并且由于StringHttpMessageHandler
可以为任何媒体类型(在Accept
标题中提供)生成内容,因此它将能够处理application/xml
您的客户端请求的内容。
Spring MVC has since been changed to only return 406 if the body in the ResponseEntity
is NOT null
. You won't see the behavior in the original question if you're using a more recent version of Spring MVC.
Spring MVC 已更改为仅返回 406,如果其中的主体ResponseEntity
为 NOT null
。如果您使用的是较新版本的 Spring MVC,您将看不到原始问题中的行为。
In iddy85's solution, which seems to suggest ResponseEntity<?>
, the type for the body will be inferred as Object
. If you have the correct libraries in your classpath, ie. Hymanson (version > 2.5.0) and its XML extension, Spring MVC will have access to MappingHymanson2XmlHttpMessageConverter
which it can use to produce application/xml
for the type Object
. Their solution only works under these conditions.Otherwise, it will fail for the same reason I've described above.
在iddy85 的解决方案中,这似乎暗示ResponseEntity<?>
了身体的类型将被推断为Object
. 如果您的类路径中有正确的库,即。Hymanson(版本 > 2.5.0)及其 XML 扩展,Spring MVC 将可以访问MappingHymanson2XmlHttpMessageConverter
它可以用来生成application/xml
类型Object
。他们的解决方案仅适用于这些条件。否则,它会因为我上面描述的相同原因而失败。
回答by iamiddy
Your method implementation is ambiguous, try the following , edited your code a little bit and used HttpStatus.NO_CONTENT
i.e 204 No Content as in place of HttpStatus.OK
您的方法实现不明确,请尝试以下操作,稍微编辑您的代码并使用HttpStatus.NO_CONTENT
ie 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 Twill 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 );
}
}
回答by adanilev
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);
}
回答by Grigory Kislin
According Spring 4 MVC ResponseEntity.BodyBuilder and ResponseEntity Enhancements Exampleit 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)
回答by Alex
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();
回答by MariuszS
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());
}