Affects: spring-web 5.3.27
This class writes the content length in two places: org.springframework.http.server.ServletServerHttpResponse#writeHeaders
private void writeHeaders() {
if (!this.headersWritten) {
getHeaders().forEach((headerName, headerValues) -> {
for (String headerValue : headerValues) {
this.servletResponse.addHeader(headerName, headerValue);
}
});
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (this.servletResponse.getContentType() == null && this.headers.getContentType() != null) {
this.servletResponse.setContentType(this.headers.getContentType().toString());
}
if (this.servletResponse.getCharacterEncoding() == null && this.headers.getContentType() != null &&
this.headers.getContentType().getCharset() != null) {
this.servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
}
long contentLength = getHeaders().getContentLength();
if (contentLength != -1) {
this.servletResponse.setContentLengthLong(contentLength);
}
this.headersWritten = true;
}
}
One time in the for loop on top without any validation and a second time in the setContentLengthLong with validation. I guess the first one is unintentional.
What I try to do: I have another filter before, that does compression (gzip and brotli) and compression is required to have a chunked encoding. The servlet engine switches to chunked encoding, if it does not have a content length and you send out more than one chunk or flush. If a content-length is set, the server cannot respond with a chunked encoding and the response will hang in a load balancer as it would expect more bytes than it gets due to the compression.
For that, the code blocks access to setContentLengthLong
and setContentLengthLong
Workaround: Catch set and add header calls as well:
@Override
public void setHeader(String name, String value) {
if ("Content-Length".equalsIgnoreCase(name)) return;
super.setHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
if ("Content-Length".equalsIgnoreCase(name)) return;
super.addHeader(name, value);
}
Comment From: bclozel
Any Spring controller could get injected with the HttpServletResponse and set a content-length header the same way. I think your filter should wrap the Servlet response and guard against these cases anyway.