Java 处理状态为 NO_CONTENT 的响应时的 Spring RestTemplate 行为
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3322381/
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 RestTemplate Behavior when handling responses with a status of NO_CONTENT
提问by AHungerArtist
Okay, I have a class NamedSystems, that has as its only field a Set of NamedSystem.
好的,我有一个 NamedSystems 类,它的唯一字段是一组 NamedSystem。
I have a method to find NamedSystems by certain criteria. That's not really important. When it gets results, everything works fine. However, when it can't find anything, and thus returns a null (or empty -- I've tried both ways) set, I get problems. Let me explain.
我有一种方法可以根据某些标准查找 NamedSystems。这并不重要。当它得到结果时,一切正常。但是,当它找不到任何东西并因此返回一个空(或空——我已经尝试了两种方法)集时,我就会遇到问题。让我解释。
I'm using the Spring RestTemplate class and I'm making a call like this in a unit test:
我正在使用 Spring RestTemplate 类,并且正在单元测试中进行这样的调用:
ResponseEntity<?> responseEntity = template.exchange(BASE_SERVICE_URL + "?
alias={aliasValue}&aliasAuthority={aliasAssigningAuthority}",
HttpMethod.GET, makeHttpEntity("xml"), NamedSystems.class,
alias1.getAlias(), alias1.getAuthority());
Now, since this would normally return a 200, but I want to return a 204, I have an interceptor in my service that determines if a ModelAndView is a NamedSystem and if its set is null. If so, I then the set the status code to NO_CONTENT (204).
现在,由于这通常会返回 200,但我想返回 204,我的服务中有一个拦截器,用于确定 ModelAndView 是否为 NamedSystem 以及它的集合是否为空。如果是这样,我然后将状态代码设置为 NO_CONTENT (204)。
When I run my junit test, I get this error:
当我运行 junit 测试时,出现此错误:
org.springframework.web.client.RestClientException: Cannot extract response: no Content-Type found
Setting the status to NO_CONTENT seems to wipe the content-type field (which does make sense when I think about it). So why is it even looking at it?
将状态设置为 NO_CONTENT 似乎会擦除 content-type 字段(当我想到它时确实有意义)。那么它为什么还要看它呢?
Spring's HttpMessageConverterExtractor extractData method:
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 + "]");
}
Going up the chain a bit to find out where that Extractor is set, I come to RestTemplate's exchange() method that I used in the test:
继续往上走一点,找出提取器的设置位置,我来到了我在测试中使用的 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);
}
So, it's trying to convert what amounts to nothing because of the supplied response type from the exchange call. If I change the responseType from NamedSystems.class to null, it works as expected. It doesn't try to convert anything. If I had tried to set the status code to 404, it also executes fine.
因此,由于交换调用提供的响应类型,它试图将数量转换为无。如果我将 responseType 从 NamedSystems.class 更改为 null,它会按预期工作。它不会尝试转换任何东西。如果我尝试将状态代码设置为 404,它也可以正常执行。
Am I misguided, or does this seem like a flaw in RestTemplate? Sure, I'm using a junit right now so I know what's going to happen, but if someone is using RestTemplate to call this and doesn't know the outcome of the service call, they would naturally have NamedSystems as a response type. However, if they tried a criteria search that came up with no elements, they'd have this nasty error.
我是否被误导了,或者这似乎是 RestTemplate 中的一个缺陷?当然,我现在正在使用 junit,所以我知道会发生什么,但是如果有人使用 RestTemplate 调用它并且不知道服务调用的结果,他们自然会将 NamedSystems 作为响应类型。但是,如果他们尝试了一个没有任何元素的条件搜索,他们就会遇到这个令人讨厌的错误。
Is there a way around this without overriding any RestTemplate stuff? Am I viewing this situation incorrectly? Please help as I'm a bit baffled.
有没有办法在不覆盖任何 RestTemplate 的情况下解决这个问题?我是否错误地看待这种情况?请帮助,因为我有点困惑。
采纳答案by AHungerArtist
This should now be fixed in Spring 3.1 RC1.
这现在应该在 Spring 3.1 RC1 中得到修复。
回答by springenthusiast
I think you are right. I'm having a similar problem. I think we should be getting a ResponseEntity with a HttpStatus of NO_CONTENT and a null body.
我想你是对的。我有类似的问题。我认为我们应该得到一个 ResponseEntity ,其 HttpStatus 为 NO_CONTENT 和一个空主体。
回答by David Victor
I believe you should probably look at the ResponseExtractor interface & call executeon the RestTemplate providing your implementation of the extractor. To me it looks like a common requirement to do this so have logged this:
我相信您应该查看 ResponseExtractor 接口并在提供提取器实现的 RestTemplate 上调用execute。对我来说,这样做似乎是一个常见的要求,所以记录了这个:
https://jira.springsource.org/browse/SPR-8016
https://jira.springsource.org/browse/SPR-8016
Here's one I prepared earlier:
这是我之前准备的一个:
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;
}
}
I've tested this & it seems to do what I want.
我已经对此进行了测试,它似乎可以满足我的要求。
To create the instance of the ResponseExtractor I call the constructor & pass the converters from a RestTemplate instance that's been injected;
要创建 ResponseExtractor 的实例,我调用构造函数并从已注入的 RestTemplate 实例传递转换器;
E.g.
例如
ResponseExtractor<MyEntity> responseExtractor =
new MyResponseExtractor(MyEntity.class, restTemplate.getMessageConverters());
Then the call is:
然后调用是:
MyEntity responseAsEntity =
restTemplate.execute(urlToCall, HttpMethod.GET, null, responseExtractor);
Your mileage may vary. ;-)
你的旅费可能会改变。;-)
回答by Chandra
One more way to solve this would be to make response entity as nullas shown below.
解决此问题的另一种方法是将响应实体设为null,如下所示。
ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}", HttpMethod.DELETE, requestEntity, null, userID);
ResponseEntity<?> response = restTemplate.exchange("http://localhost:8080/myapp/user/{userID}", HttpMethod.DELETE, requestEntity, null, userID);
If you still need response headers, try implementing the ResponseErrorHandler.
如果您仍然需要响应标头,请尝试实现 ResponseErrorHandler。
回答by Mig56
Or you could extend RestTemplate and override doExecute(..) and check the response body.
或者您可以扩展 RestTemplate 并覆盖 doExecute(..) 并检查响应正文。
For example here is what I implemented and works for us:
例如,这是我实施并为我们工作的内容:
@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();
}
}
}
回答by Jer K.
Here's a simple solution where you can set the default Content-Type for use if it is missing in the response. The Content-Type is added to the response header before it is handed back off to the preconfigured ResponseExtractorfor extraction.
这是一个简单的解决方案,您可以在其中设置默认的 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);
}
});
}
}