-
Notifications
You must be signed in to change notification settings - Fork 38.7k
Description
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);