【问题标题】:Spring RestTemplate Behavior when handling responses with a status of NO_CONTENTSpring RestTemplate 处理状态为 NO_CONTENT 的响应时的行为
【发布时间】:2010-07-23 21:09:37
【问题描述】:

好的,我有一个 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());

现在,由于这通常会返回 200,但我想返回 204,我的服务中有一个拦截器,用于确定 ModelAndView 是否为 NamedSystem 以及它的集合是否为空。如果是这样,我将状态代码设置为 NO_CONTENT (204)。

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

org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found

将状态设置为 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 + "]");
}

为了找出提取器的设置位置,我来看看我在测试中使用的 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);
}

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

我是不是被误导了,或者这看起来像是 RestTemplate 中的一个缺陷?当然,我现在正在使用 junit,所以我知道会发生什么,但是如果有人使用 RestTemplate 调用它并且不知道服务调用的结果,他们自然会将 NamedSystems 作为响应类型。但是,如果他们尝试了一个没有元素的条件搜索,他们就会遇到这个严重的错误。

有没有办法在不覆盖任何 RestTemplate 的情况下解决这个问题?我是否错误地看待这种情况?请帮忙,因为我有点困惑。

【问题讨论】:

  • 我现在正在处理这个问题。你有没有想出任何解决方案?
  • 并非如此。这对我们来说不是一个大问题,因为我们只在 junits 中使用 RestTemplate,所以我们只需将类设置为 null。看起来下面的答案可能会帮助你。您应该对下面的 spring jira 增强请求进行投票。
  • 我或多或少在同一条船上,使用 RestTemplate 进行单元测试。没什么大不了的,但我肯定会投票支持这项改进。

标签: java web-services unit-testing rest spring-mvc


【解决方案1】:

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

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

如果您仍然需要响应标头,请尝试实现 ResponseErrorHandler。

【讨论】:

  • 我已经以这种方式使用 restTemplate 进行 PUT 和 DELETE 调用,其中我需要响应状态值(即使成功)并且它工作正常。
【解决方案2】:

我相信您可能应该查看 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;
    }
}

我已经对此进行了测试,它似乎可以满足我的要求。

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

例如

ResponseExtractor<MyEntity> responseExtractor =
    new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());

那么调用是:

MyEntity responseAsEntity =
    restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);

您的里程可能会有所不同。 ;-)

【讨论】:

  • 如果您将 MyEntity 替换为泛型,这可能会更通用
【解决方案3】:

这是一个简单的解决方案,您可以在其中设置默认 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);
            }
        });
    }
}

【讨论】:

    【解决方案4】:

    这个问题现在应该在 Spring 3.1 RC1 中得到修复。

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

    【讨论】:

    【解决方案5】:

    或者您可以扩展 RestTemplate 并覆盖 doExecute(..) 并检查响应正文。

    例如,这是我为我们实现并为我们工作的:

    @Override
    protected <T> T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor<T> responseExtractor)
            throws RestClientException
    {
        Assert.notNull(url, "'url' must not be null");
        Assert.notNull(method, "'method' must not be null");
        ClientHttpResponse response = null;
        try
        {
            final ClientHttpRequest request = createRequest(url, method);
            if (requestCallback != null)
            {
                requestCallback.doWithRequest(request);
            }
            response = request.execute();
            if (!getErrorHandler().hasError(response))
            {
                logResponseStatus(method, url, response);
            }
            else
            {
                handleResponseError(method, url, response);
            }
            if ((response.getBody() == null) || (responseExtractor == null))
            {
                return null;
            }
            return responseExtractor.extractData(response);
        }
        catch (final IOException ex)
        {
            throw new ResourceAccessException("I/O error: " + ex.getMessage(), ex);
        }
        finally
        {
            if (response != null)
            {
                response.close();
            }
        }
    }
    

    【讨论】:

    【解决方案6】:

    我认为你是对的。 我有一个类似的问题。 我认为我们应该得到一个 HttpStatus 为 NO_CONTENT 且正文为空的 ResponseEntity。

    【讨论】:

    • 我同意你的看法。我也看到了。
    【解决方案7】:

    我想出了一个解决方法(不确定它是否符合您的情况):

    首先定义一个实现ClientHttpRequestInterceptor的自定义拦截器类。并检查 response.getStatusCode() 是否符合您的情况(我的情况是!= HttpStatus.NOT_FOUND 并且 response.getBody() 长度为 0),定义一个具有静态的自定义类(例如 DefaultResponseForEmptyRestTemplateBody) MockClientHttpResponse 类型的方法:

        public class RequestResponseInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    
        ClientHttpResponse response = execution.execute(request, body);
         if(response.getStatusCode()!=HttpStatus.NOT_FOUND && response.getBody().readAllBytes().length==0){
            response = DefaultResponseForEmptyRestTemplateBody.getResponse(response.getStatusCode());
    
        }
    
        return response;
    }
    
    
    }
    
        public static class DefaultResponseForEmptyRestTemplateBody  {
    MockClientHttpResponse response;
    private static byte[] content = new byte[0];
    
    public static MockClientHttpResponse getResponse(HttpStatus statusCode){
        content = "response body is empty".getBytes();
      return new MockClientHttpResponse(content, statusCode);
    }
    
    }
    

    最后将此拦截器添加到您的 restTemplate 对象中,如下所示:

    restTemplate.setInterceptors(Collections.singletonList(new RequestResponseLoggingInterceptor()));
    

    并调用您的 restTemplate.postForEntity:

    ResponseEntity<String> response = this.restTemplate.postForEntity(baseUrl, requestParams,String.class);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-10
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 2013-12-29
      • 2016-07-15
      • 1970-01-01
      相关资源
      最近更新 更多