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