Consistent adaptation of HTTP headers on Servlet responses

Closes gh-36345
This commit is contained in:
Juergen Hoeller
2026-02-17 18:40:50 +01:00
parent bfa81290b3
commit acab61f9a7
5 changed files with 78 additions and 37 deletions
@@ -187,10 +187,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);
}
@@ -207,7 +207,6 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
}
}
}
return this.headers;
}
@@ -18,6 +18,7 @@ package org.springframework.http.server;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -118,19 +119,31 @@ public class ServletServerHttpResponse implements ServerHttpResponse {
this.servletResponse.addHeader(headerName, headerValue);
}
});
// HttpServletResponse exposes some headers as properties: we should include those if not already present
MediaType contentTypeHeader = this.headers.getContentType();
if (this.servletResponse.getContentType() == null && contentTypeHeader != null) {
this.servletResponse.setContentType(contentTypeHeader.toString());
if (this.servletResponse.getContentType() == null && this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
this.servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
}
if (this.servletResponse.getCharacterEncoding() == null && contentTypeHeader != null &&
contentTypeHeader.getCharset() != null) {
this.servletResponse.setCharacterEncoding(contentTypeHeader.getCharset().name());
if (this.servletResponse.getCharacterEncoding() == null && this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
try {
// Lazy parsing into MediaType
MediaType contentType = this.headers.getContentType();
if (contentType != null) {
Charset charset = contentType.getCharset();
if (charset != null) {
this.servletResponse.setCharacterEncoding(charset.name());
}
}
}
catch (Exception ex) {
// Leave character encoding unspecified
}
}
long contentLength = getHeaders().getContentLength();
long contentLength = this.headers.getContentLength();
if (contentLength != -1) {
this.servletResponse.setContentLengthLong(contentLength);
}
this.headersWritten = true;
}
}
@@ -50,7 +50,9 @@ import org.springframework.util.ReflectionUtils;
*/
class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
private static final boolean IS_SERVLET61 = ReflectionUtils.findField(HttpServletResponse.class, "SC_PERMANENT_REDIRECT") != null;
private static final boolean SERVLET61 =
(ReflectionUtils.findField(HttpServletResponse.class, "SC_PERMANENT_REDIRECT") != null);
private final HttpServletResponse response;
@@ -138,31 +140,35 @@ 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();
Charset charset = (contentType != null ? contentType.getCharset() : null);
if (this.response.getCharacterEncoding() == null && charset != null) {
this.response.setCharacterEncoding(charset.name());
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (this.response.getContentType() == null && headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
this.response.setContentType(headers.getFirst(HttpHeaders.CONTENT_TYPE));
}
long contentLength = getHeaders().getContentLength();
if (this.response.getCharacterEncoding() == null && headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
try {
// Lazy parsing into MediaType
MediaType contentType = headers.getContentType();
if (contentType != null) {
Charset charset = contentType.getCharset();
if (charset != null) {
this.response.setCharacterEncoding(charset.name());
}
}
}
catch (Exception ex) {
// Leave character encoding unspecified
}
}
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);
}
}
@@ -186,7 +192,7 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
cookie.setSecure(httpCookie.isSecure());
cookie.setHttpOnly(httpCookie.isHttpOnly());
if (httpCookie.isPartitioned()) {
if (IS_SERVLET61) {
if (SERVLET61) {
cookie.setAttribute("Partitioned", "");
}
else {
@@ -373,7 +379,6 @@ class ServletServerHttpResponse extends AbstractListenerServerHttpResponse {
private class ResponseBodyProcessor extends AbstractListenerWriteProcessor<DataBuffer> {
public ResponseBodyProcessor() {
super(request.getLogPrefix());
}
@@ -17,6 +17,7 @@
package org.springframework.web.servlet.function;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Set;
@@ -28,6 +29,7 @@ import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
@@ -51,6 +53,7 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
private final MultiValueMap<String, Cookie> cookies;
protected AbstractServerResponse(
HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap<String, Cookie> cookies) {
@@ -60,6 +63,7 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(cookies));
}
@Override
public final HttpStatusCode statusCode() {
return this.statusCode;
@@ -118,14 +122,29 @@ abstract class AbstractServerResponse extends ErrorHandlingServerResponse {
servletResponse.addHeader(headerName, headerValue);
}
});
// HttpServletResponse exposes some headers as properties: we should include those if not already present
if (servletResponse.getContentType() == null && this.headers.getContentType() != null) {
servletResponse.setContentType(this.headers.getContentType().toString());
if (servletResponse.getContentType() == null && this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
servletResponse.setContentType(this.headers.getFirst(HttpHeaders.CONTENT_TYPE));
}
if (servletResponse.getCharacterEncoding() == null &&
this.headers.getContentType() != null &&
this.headers.getContentType().getCharset() != null) {
servletResponse.setCharacterEncoding(this.headers.getContentType().getCharset().name());
if (servletResponse.getCharacterEncoding() == null && this.headers.containsKey(HttpHeaders.CONTENT_TYPE)) {
try {
// Lazy parsing into MediaType
MediaType contentType = this.headers.getContentType();
if (contentType != null) {
Charset charset = contentType.getCharset();
if (charset != null) {
servletResponse.setCharacterEncoding(charset.name());
}
}
}
catch (Exception ex) {
// Leave character encoding unspecified
}
}
long contentLength = this.headers.getContentLength();
if (contentLength != -1) {
servletResponse.setContentLengthLong(contentLength);
}
}
@@ -459,6 +459,11 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
// ignore
}
// @Override - on Servlet 6.1
public void setCharacterEncoding(Charset encoding) {
// ignore
}
@Override
public void setContentLength(int len) {
// ignore