简体   繁体   English

Spring:使用 ResponseEntity 返回空的 HTTP 响应<Void>不起作用

[英]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.

相关问题 在控制器中返回错误时,Spring MVC ResponseEntity始终为空 - Spring MVC ResponseEntity is always empty when returning error in controller 为什么 org.springframework.http.ResponseEntity 没有实现 Serializable - Why org.springframework.http.ResponseEntity doesn't implements Serializable 返回和转换ResponseEntity <List<T> &gt; - Returning and Casting ResponseEntity<List<T>> 在 Web 客户端中,返回包含 Body 和 Http 状态码的 ResponseEntity(成功和失败) JAVA spring boot - In Web Client, returning ResponseEntity containing both Body and Http Status Code(both success and failure) JAVA spring boot 在Spring 3中使用@ResponseBody返回对象不起作用 - Returning object using @ResponseBody with spring 3 doesn't work Spring Security不适用于Http11NioProtocol - Spring Security doesn't work with Http11NioProtocol 不使用ResponseEntity时Spring控制器不返回值 - Spring controller not returning value when not using ResponseEntity Spring ResponseEntity不返回所有对象属性 - Spring ResponseEntity not returning all object attributes 如何将 ResponseEntity 设置为 Spring MVC 4 的 HTTP 404 - How to set ResponseEntity to HTTP 404 for Spring MVC 4 Spring Java ResponseEntity <T> 具有多种参数类型 - Spring java ResponseEntity<T> with multiple parameter types
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM