Spring RestTemplate Behavior when handling responses with a status of NO_CONTENT

匿名 (未验证) 提交于 2019-12-03 01:55:01

问题:

Okay, I have a class NamedSystems, that has as its only field a Set of 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.

I'm using the Spring RestTemplate class and I'm making a call like this in a unit test:

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).

When I run my junit test, I get this error:

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?

Spring's HttpMessageConverterExtractor extractData method:

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:

public  ResponseEntity exchange(String url, HttpMethod method,   HttpEntity> requestEntity, Class responseType, Object... uriVariables) throws RestClientException {     HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);     ResponseEntityResponseExtractor responseExtractor = new ResponseEntityResponseExtractor(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.

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.

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.

回答1:

I believe you should probably look at the ResponseExtractor interface & call execute on the RestTemplate providing your implementation of the extractor. To me it looks like a common requirement to do this so have logged this:

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

Here's one I prepared earlier:

private class MyResponseExtractor extends HttpMessageConverterExtractor {      public MyResponseExtractor (Class responseType,       List> 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;

E.g.

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

Then the call is:

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

Your mileage may vary. ;-)



回答2:

One more way to solve this would be to make response entity as null as shown below.

  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.



回答3:

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 ResponseExtractor for extraction.

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 doExecute(URI url, HttpMethod method, RequestCallback requestCallback, final ResponseExtractor responseExtractor)             throws RestClientException {          return super.doExecute(url, method, requestCallback, new ResponseExtractor() {             public T extractData(ClientHttpResponse response) throws IOException {                 if (response.getHeaders().getContentType() == null && defaultResponseContentType != null) {                     response.getHeaders().setContentType(defaultResponseContentType);                 }                  return responseExtractor.extractData(response);             }         });     } } 


回答4:

This should now be fixed in Spring 3.1 RC1.

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



回答5:

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.



回答6:

Or you could extend RestTemplate and override doExecute(..) and check the response body.

For example here is what I implemented and works for us:

@Override protected  T doExecute(final URI url, final HttpMethod method, final RequestCallback requestCallback, final ResponseExtractor 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();         }     } } 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!