Skip to content

Processing response with no Content-Length header and no body raises EOFException #35361

@adam-mak

Description

@adam-mak

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

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: waiting-for-feedbackWe need additional information before we can continuestatus: waiting-for-triageAn issue we've not yet triaged or decided on

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions