18

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

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.

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
AHungerArtist
  • 9,332
  • 17
  • 73
  • 109
  • I'm dealing with exactly this problem now. Did you come up with any sort of solution? – Fil Mar 17 '11 at 16:20
  • Not really. It wasn't as big a concern for us because we were only using the RestTemplate in junits, so we'd just set the class to null. Looks like the answer below might help you out though. You should vote on the spring jira enhancement request below. – AHungerArtist Mar 17 '11 at 20:08
  • I'm more or less in the same boat, using RestTemplate for unit testing. Not a huge deal, but I will definitely vote on that enhancement. – Fil Mar 18 '11 at 13:01

7 Answers7

12

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.

Vao Tsun
  • 47,234
  • 13
  • 100
  • 132
Chandra
  • 1,577
  • 3
  • 21
  • 28
  • I have used restTemplate this way for PUT and DELETE calls where I require the response status value (even on success) and it work fine. – Andrew B Feb 17 '12 at 14:23
10

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<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;

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

Walery Strauch
  • 6,792
  • 8
  • 50
  • 57
David Victor
  • 830
  • 9
  • 29
6

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> 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);
            }
        });
    }
}
Jer K.
  • 316
  • 3
  • 6
4

This should now be fixed in Spring 3.1 RC1.

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

AHungerArtist
  • 9,332
  • 17
  • 73
  • 109
2

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> 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();
        }
    }
}
Mig56
  • 131
  • 1
  • 6
1

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.

0

I came along a workaround (not sure if it meets your case):

First define a custom interceptor class which implements ClientHttpRequestInterceptor. and check if response.getStatusCode() meets your case (my case is != HttpStatus.NOT_FOUND and response.getBody() length is 0), define a custom class (e.x. DefaultResponseForEmptyRestTemplateBody) which has a static method of type 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);
}

}

finally add this interceptor to your restTemplate object as below:

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

and call your restTemplate.postForEntity:

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