Better empty response body support in RestTemplate

Prior to this change, RestTemplate returned an empty response body if:

* HTTP return status 204 or 304
* Content-length header equals 0

This change adds a new condition for this, better supporting RFC7230
section 3.4, for connections that are closed without response body:

* No Content-length header
* No Transfer-encoding: chunked header value
* a Connection: close header value

See SPR-7911 for previous efforts in that space.

Issue: SPR-8016
master
Brian Clozel 10 years ago
parent b2878a4f60
commit 98870251f9
  1. 27
      spring-web/src/main/java/org/springframework/web/client/HttpMessageConverterExtractor.java
  2. 25
      spring-web/src/test/java/org/springframework/web/client/HttpMessageConverterExtractorTests.java

@ -23,6 +23,7 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
@ -122,9 +123,14 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
}
/**
* Indicates whether the given response has a message body. <p>Default implementation
* returns {@code false} for a response status of {@code 204} or {@code 304}, or a {@code
* Content-Length} of {@code 0}.
* Indicates whether the given response has a message body.
* <p>Default implementation returns {@code false} for:
* <ul>
* <li>a response status of {@code 204} or {@code 304}</li>
* <li>a {@code Content-Length} of {@code 0}</li>
* <li>no indication of content (no {@code Content-Length} nor {@code Transfer-encoding: chunked}) and
* a ({@code Connection: closed}) header. See rfc7230 section 3.4</li>
* </ul>
*
* @param response the response to check for a message body
* @return {@code true} if the response has a body, {@code false} otherwise
@ -136,8 +142,19 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
responseStatus == HttpStatus.NOT_MODIFIED) {
return false;
}
long contentLength = response.getHeaders().getContentLength();
return contentLength != 0;
HttpHeaders headers = response.getHeaders();
long contentLength = headers.getContentLength();
if(contentLength == 0) {
return false;
}
boolean chunked = headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& headers.get(HttpHeaders.TRANSFER_ENCODING).contains("chunked");
boolean closed = headers.containsKey(HttpHeaders.CONNECTION)
&& headers.getConnection().contains("close");
if(!chunked && contentLength == -1 && closed) {
return false;
}
return true;
}
}

@ -108,7 +108,7 @@ public class HttpMessageConverterExtractorTests {
assertEquals(expected, result);
}
@Test
@Test(expected = RestClientException.class)
@SuppressWarnings("unchecked")
public void cannotRead() throws IOException {
HttpMessageConverter<String> converter = mock(HttpMessageConverter.class);
@ -122,13 +122,22 @@ public class HttpMessageConverterExtractorTests {
given(response.getHeaders()).willReturn(responseHeaders);
given(converter.canRead(String.class, contentType)).willReturn(false);
try {
extractor.extractData(response);
fail("RestClientException expected");
}
catch (RestClientException expected) {
// expected
}
extractor.extractData(response);
}
@Test
public void connectionClose() throws IOException {
HttpMessageConverter<String> converter = mock(HttpMessageConverter.class);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(converter);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setConnection("close");
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter));
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
Object result = extractor.extractData(response);
assertNull(result);
}
@Test

Loading…
Cancel
Save