Spring RestTemplate处理状态为NO_CONTENT的响应时的行为

AHu*_*ist 18 java rest unit-testing web-services spring-mvc

好的,我有一个名为NamedSystems的类,它的唯一字段是一组NamedSystem.

我有一种方法可以通过某些标准找到NamedSystems.那不是很重要.当它得到结果时,一切正常.但是,当它找不到任何东西,从而返回null(或空 - 我已尝试过两种方式)设置时,我就会遇到问题.让我解释.

我正在使用Spring RestTemplate类,我在单元测试中进行这样的调用:

ResponseEntity<?> responseEntity = template.exchange(BASE_SERVICE_URL + "?
  alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}", 
  HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class, 
  alias1.getAlias(), alias1.getAuthority());
Run Code Online (Sandbox Code Playgroud)

现在,因为这通常会返回200,但我想返回204,我的服务中有一个拦截器,它确定ModelAndView是否是NamedSystem,如果它的集合为null.如果是,我然后将状态代码设置为NO_CONTENT(204).

当我运行junit测试时,我收到此错误:

org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found
Run Code Online (Sandbox Code Playgroud)

将状态设置为NO_CONTENT似乎擦除了内容类型字段(当我考虑它时这确实有意义).那为什么还要看呢?

Spring的HttpMessageConverterExtractor extractData方法:

public T extractData(ClientHttpResponse response) throws IOException {
    MediaType contentType = response.getHeaders().getContentType();
    if (contentType == null) {
        throw new RestClientException("Cannot extract response: no Content-Type found");
    }
    for (HttpMessageConverter messageConverter : messageConverters) {
        if (messageConverter.canRead(responseType, contentType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Reading [" + responseType.getName() + "] as \"" + contentType
                    +"\" using [" + messageConverter + "]");
            }
            return (T) messageConverter.read(this.responseType, response);
        }
    }
    throw new RestClientException(
        "Could not extract response: no suitable HttpMessageConverter found for response type [" +
        this.responseType.getName() + "] and content type [" + contentType + "]");
}
Run Code Online (Sandbox Code Playgroud)

上一点链以找出Extractor的设置位置,我来看看我在测试中使用的RestTemplate的exchange()方法:

public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
  HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
    HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
    ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
    return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
Run Code Online (Sandbox Code Playgroud)

因此,由于交换调用提供的响应类型,它正在尝试将无效转换为什么.如果我将responseType从NamedSystems.class更改为null,它将按预期工作.它不会尝试转换任何东西.如果我试图将状态代码设置为404,它也可以正常执行.

我是误入歧途,还是看起来像RestTemplate中的一个缺陷?当然,我现在正在使用junit所以我知道会发生什么,但如果有人使用RestTemplate来调用它并且不知道服务调用的结果,他们自然会将NamedSystems作为响应类型.但是,如果他们尝试了没有元素的标准搜索,他们就会遇到这个令人讨厌的错误.

有没有办法解决这个问题而不重写任何RestTemplate的东西?我是否错误地查看了这种情况?请帮忙,因为我有点困惑.

Cha*_*dra 12

解决此问题的另一种方法是将响应实体设为null,如下所示.

  ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}",
                                                             HttpMethod.DELETE, 
                                                             requestEntity,
                                                             null,
                                                             userID);
Run Code Online (Sandbox Code Playgroud)

如果仍需要响应头,请尝试实现ResponseErrorHandler.


Dav*_*tor 10

我相信您应该查看ResponseExtractor接口并在RestTemplate上调用execute,提供您的提取器实现.对我来说,这似乎是一个常见的要求,所以记录了这个:

https://jira.springsource.org/browse/SPR-8016

这是我之前准备的一个:

private class MyResponseExtractor extends HttpMessageConverterExtractor<MyEntity> {

    public MyResponseExtractor (Class<MyEntity> responseType,
      List<HttpMessageConverter<?>> messageConverters) {
        super(responseType, messageConverters);
    }

    @Override
    public MyEntity extractData(ClientHttpResponse response) throws IOException {

        MyEntity result;

        if (response.getStatusCode() == HttpStatus.OK) {
            result = super.extractData(response);
        } else {
            result = null;
        }

        return result;
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经测试了这个,它似乎做了我想要的.

要创建ResponseExtractor的实例,我调用构造函数并从已注入的RestTemplate实例传递转换器;

例如

ResponseExtractor<MyEntity> responseExtractor =
    new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());
Run Code Online (Sandbox Code Playgroud)

然后电话是:

MyEntity responseAsEntity =
    restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);
Run Code Online (Sandbox Code Playgroud)

你的旅费可能会改变.;-)


Jer*_* K. 6

这是一个简单的解决方案,您可以设置默认的Content-Type,以便在响应中缺少时使用.在将Content-Type传递回预先配置的ResponseExtractor以进行提取之前,它将添加到响应标头中.

public class CustomRestTemplate extends RestTemplate {

    private MediaType defaultResponseContentType;

    public CustomRestTemplate() {
        super();
    }

    public CustomRestTemplate(ClientHttpRequestFactory requestFactory) {
        super(requestFactory);
    }

    public void setDefaultResponseContentType(String defaultResponseContentType) {
        this.defaultResponseContentType = MediaType.parseMediaType(defaultResponseContentType);
    }

    @Override
    protected <T> T doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
            throws RestClientException {

        return super.doExecute(url, method, requestCallback, new ResponseExtractor<T>() {
            public T extractData(ClientHttpResponse response) throws IOException {
                if (response.getHeaders().getContentType() == null && defaultResponseContentType != null) {
                    response.getHeaders().setContentType(defaultResponseContentType);
                }

                return responseExtractor.extractData(response);
            }
        });
    }
}
Run Code Online (Sandbox Code Playgroud)


AHu*_*ist 4

现在应该在 Spring 3.1 RC1 中修复此问题。

https://jira.spring.io/browse/SPR-7911