diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java index a31bdad11a4..17e81836f50 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpRequest.java @@ -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 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; } diff --git a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java index 4563348aa65..d3cebfb57a3 100644 --- a/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/ServletServerHttpResponse.java @@ -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; } } diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java index e108f761c10..945592b4aee 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/ServletServerHttpResponse.java @@ -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 { - public ResponseBodyProcessor() { super(request.getLogPrefix()); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java index b91c212da8e..16c563c3bfd 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/function/AbstractServerResponse.java @@ -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 cookies; + protected AbstractServerResponse( HttpStatusCode statusCode, HttpHeaders headers, MultiValueMap 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); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java index f293316d4f3..7919a4301be 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseBodyEmitterReturnValueHandler.java @@ -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