Consistent adaptation of HTTP headers on Servlet responses

Includes use of Servlet 6.1 setCharacterEncoding(Charset)

Closes gh-36343
This commit is contained in:
Juergen Hoeller
2026-02-17 17:38:50 +01:00
parent 0841e79e32
commit a9f447e8d7
6 changed files with 69 additions and 31 deletions
@@ -174,10 +174,10 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
if (contentType != null && contentType.getCharset() == null) {
String requestEncoding = this.servletRequest.getCharacterEncoding();
if (StringUtils.hasLength(requestEncoding)) {
Charset charSet = Charset.forName(requestEncoding);
Charset charset = Charset.forName(requestEncoding);
Map<String, String> params = new LinkedCaseInsensitiveMap<>();
params.putAll(contentType.getParameters());
params.put("charset", charSet.toString());
params.put("charset", charset.toString());
MediaType mediaType = new MediaType(contentType.getType(), contentType.getSubtype(), params);
this.headers.setContentType(mediaType);
}
@@ -24,6 +24,7 @@ import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
@@ -87,13 +88,13 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
public OutputStream getBody() throws IOException {
this.bodyUsed = true;
this.headersWritten = true;
writeHeaders();
return this.servletResponse.getOutputStream();
}
@Override
public void flush() throws IOException {
this.headersWritten = true;
writeHeaders();
if (this.bodyUsed) {
this.servletResponse.flushBuffer();
}
@@ -101,6 +102,28 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
@Override
public void close() {
writeHeaders();
}
private void writeHeaders() {
if (!this.headersWritten) {
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (this.servletResponse.getContentType() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
this.servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
}
if (this.servletResponse.getCharacterEncoding() == null && this.headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
try {
// Lazy parsing into MediaType
MediaType contentType = this.headers.getContentType();
if (contentType != null) {
this.servletResponse.setCharacterEncoding(contentType.getCharset());
}
}
catch (Exception ex) {
// Leave character encoding unspecified
}
}
}
this.headersWritten = true;
}
@@ -18,7 +18,6 @@ package org.springframework.http.server.reactive;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import jakarta.servlet.AsyncContext;
import jakarta.servlet.AsyncEvent;
@@ -122,31 +121,34 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
}
protected void adaptHeaders(boolean removeAdaptedHeaders) {
MediaType contentType = null;
try {
contentType = getHeaders().getContentType();
}
catch (Exception ex) {
String rawContentType = getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
this.response.setContentType(rawContentType);
}
if (this.response.getContentType() == null && contentType != null) {
this.response.setContentType(contentType.toString());
HttpHeaders headers = getHeaders();
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (this.response.getContentType() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
this.response.setContentType(headers.getFirst(HttpHeaders.CONTENT_TYPE));
}
Charset charset = (contentType != null ? contentType.getCharset() : null);
if (this.response.getCharacterEncoding() == null && charset != null) {
this.response.setCharacterEncoding(charset.name());
if (this.response.getCharacterEncoding() == null && headers.containsHeader(HttpHeaders.CONTENT_TYPE)) {
try {
// Lazy parsing into MediaType
MediaType contentType = headers.getContentType();
if (contentType != null) {
this.response.setCharacterEncoding(contentType.getCharset());
}
}
catch (Exception ex) {
// Leave character encoding unspecified
}
}
long contentLength = getHeaders().getContentLength();
long contentLength = headers.getContentLength();
if (contentLength != -1) {
this.response.setContentLengthLong(contentLength);
}
if (removeAdaptedHeaders) {
getHeaders().remove(HttpHeaders.CONTENT_TYPE);
getHeaders().remove(HttpHeaders.CONTENT_LENGTH);
headers.remove(HttpHeaders.CONTENT_TYPE);
headers.remove(HttpHeaders.CONTENT_LENGTH);
}
}
@@ -352,7 +354,6 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
public ResponseBodyProcessor() {
super(request.getLogPrefix());
}