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

问题描述 投票:0回答:7

我们正在使用 Spring (4.1.1.) 实现 REST API。对于某些 HTTP 请求,我们希望返回一个没有正文的头部作为响应。但是,使用

ResponseEntity<Void>
似乎不起作用。当使用
MockMvc
测试调用时,会返回 406(不可接受)。使用不带参数值 (
ResponseEntity<String>
) 的
new  ResponseEntity<String>( HttpStatus.NOT_FOUND )
效果很好。

方法:

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

}

测试用例(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() );

}

}

此堆栈跟踪失败:

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)
java spring rest spring-mvc
7个回答
65
投票

根据 Spring 4 MVC ResponseEntity.BodyBuilder 和 ResponseEntity 增强示例可以写成:

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

更新:

如果返回值是

Optional
,有方便的方法,返回
ok()
notFound()

return ResponseEntity.of(optional)

更新2:也可以随意使用状态

OK(200)

public void taxonomyPackageExists(...

以及状态

NO_CONTENT(204)

@ResponseStatus(HttpStatus.NO_CONTENT)
public void taxonomyPackageExists(...

59
投票

注意:这对于问题中提到的版本 4.1.1.RELEASE 来说是正确的。

Spring MVC 通过

ResponseEntity
处理
HttpEntityMethodProcessor
返回值。

ResponseEntity
值没有设置正文时(如代码片段中的情况),
HttpEntityMethodProcessor
尝试根据签名中
ResponseEntity
返回类型的参数化来确定响应正文的内容类型
@RequestMapping
处理程序方法。

所以对于

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

该类型将是

Void
。然后
HttpEntityMethodProcessor
将循环遍历其所有注册的
HttpMessageConverter
实例,并找到一个可以为
Void
类型编写主体的实例。根据您的配置,它可能会也可能找不到。

如果确实找到任何内容,它仍然需要确保相应的正文将使用与请求的

Accept
标头中提供的类型相匹配的 Content-Type 编写,在您的情况下为
application/xml

如果在所有这些检查之后,不存在这样的

HttpMessageConverter
,Spring MVC 将决定它无法生成可接受的响应,因此返回 406 Not Acceptable HTTP 响应。

使用

ResponseEntity<String>
,Spring 将使用
String
作为响应主体,并查找
StringHttpMessageConverter
作为处理程序。由于
StringHttpMessageHandler
可以为任何媒体类型生成内容(在
Accept
标头中提供),因此它将能够处理您的客户请求的
application/xml

Spring MVC 已更改为仅在

ResponseEntity
中的主体不是
null
时返回 406。如果您使用的是更新版本的 Spring MVC,您将看不到原始问题中的行为。


iddy85的解决方案中,这似乎表明

ResponseEntity<?>
,主体的类型将被推断为
Object
。如果您的类路径中有正确的库,即。 Jackson(版本 > 2.5.0)及其 XML 扩展,Spring MVC 将可以访问
MappingJackson2XmlHttpMessageConverter
,它可以用来为
application/xml
类型生成
Object
他们的解决方案仅在这些条件下有效。否则,它将因我上面描述的相同原因而失败。


17
投票

您也可以不指定类型参数,这看起来更清晰,并且在查看docs时Spring的意图是:

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

13
投票

对于 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());
}

10
投票

您的方法实现不明确,请尝试以下操作,稍微编辑一下您的代码并使用

HttpStatus.NO_CONTENT
即 204 No Content 代替
HttpStatus.OK

服务器已完成请求,但不需要返回 实体主体,并且可能想要返回更新的元信息。这 响应可能包括新的或更新的元信息,其形式为 实体标头,如果存在应该与 请求的变体。

对于 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 );
            }

    }

3
投票

就个人而言,为了处理空响应,我在集成测试中使用 MockMvcResponse 对象,如下所示:

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

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

在我的控制器中,我在特定情况下返回空响应,如下所示:

return ResponseEntity.noContent().build();

0
投票

更新 jar 或仅进行 Maven 构建/安装并重新运行服务或项目

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.