Bug Report
Environment
spring-web 6.1.21
Description
EOFException
when attempting to extract data from response while invoking RestTemplate.exchange
. API is sending back a response expectedly with a 202 ACCEPTED
, no body (not an empty body), and no Content-Length header. Seems like Spring doesn't account for this situation.
Under HttpMessageConverterExtractor.extractData()
, it checks if there is a message body, and if so, whether the body is empty. Before this check, the headers are received and processed:
HttpHeaders.java
:
/**
* Return the length of the body in bytes, as specified by the
* {@code Content-Length} header.
* <p>Returns -1 when the content-length is unknown.
*/
public long getContentLength() {
String value = getFirst(CONTENT_LENGTH);
return (value != null ? Long.parseLong(value) : -1);
}
The Content-Length header is set to -1 since no Content-Length header was received.
The problem occurs under IntrospectingClientHttpResponse.hasMessageBody()
:
/**
* Indicates whether the response has a message body.
* <p>Implementation returns {@code false} for:
* <ul>
* <li>a response status of {@code 1XX}, {@code 204} or {@code 304}</li>
* <li>a {@code Content-Length} header of {@code 0}</li>
* </ul>
* @return {@code true} if the response has a message body, {@code false} otherwise
* @throws IOException in case of I/O errors
*/
public boolean hasMessageBody() throws IOException {
HttpStatusCode statusCode = getStatusCode();
if (statusCode.is1xxInformational() || statusCode == HttpStatus.NO_CONTENT ||
statusCode == HttpStatus.NOT_MODIFIED) {
return false;
}
if (getHeaders().getContentLength() == 0) {
return false;
}
return true;
}
This method returns true
even without a body, as it only checks for status codes that are not 202 ACCEPTED
and falls back on the Content-Length header.
The EOFException
is raised in the following method under IntrospectingClientHttpResponse.hasEmptyMessageBody()
:
public boolean hasEmptyMessageBody() throws IOException {
InputStream body = getDelegate().getBody();
// Per contract body shouldn't be null, but check anyway..
if (body == null) {
return true;
}
if (body.markSupported()) {
body.mark(1);
if (body.read() == -1) { // raises EOFException
return true;
}
//...
}
I was able to workaround this by adding an interceptor to the rest template:
ClientHttpRequestInterceptor addContentLengthFor202Responses = (request, body, execution) -> {
ClientHttpResponse response = execution.execute(request, body);
if (response.getStatusCode() == HttpStatus.ACCEPTED) {
HttpHeaders.writableHttpHeaders(response.getHeaders()).setContentLength(0);
}
return response;
};
restTemplate.getInterceptors().add(addContentLengthFor202Responses);
Comment From: bclozel
Can you share a minimal sample application that reproduces the problem? Thanks for the detailed analysis but we could use an actual reproducer as it helps us to better understand the case and our options to fix the situation. Thanks!