From 5a11569790c35f81069abfb07dcbb07980fde8dc Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Mon, 1 Mar 2021 20:55:03 +0100 Subject: [PATCH] Allow ServerHttpRequest content-type mutation Prior to this commit, `ServerHttpRequest.mutate()` would not reflect changes made on the "Accept" and "Content-Type" HTTP headers. This was due to the fact that the instantiation of a new request based on the mutated values would not use the writable HTTP headers used during the mutation, but rather a read-only view of the headers backed by `ReadOnlyHttpHeaders`. `ReadOnlyHttpHeaders` caches those values for performance reasons, so getting those from the new request would not reflect the changes made during the mutation phase. This commit ensures that the new request uses the mutated headers. Fixes gh-26615 --- .../reactive/DefaultServerHttpRequestBuilder.java | 7 ++++--- .../server/reactive/ServerHttpRequestTests.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java index 847669e0eb..d4398d7609 100644 --- a/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java +++ b/spring-web/src/main/java/org/springframework/http/server/reactive/DefaultServerHttpRequestBuilder.java @@ -38,6 +38,7 @@ import org.springframework.util.StringUtils; * * @author Rossen Stoyanchev * @author Sebastien Deleuze + * @author Brian Clozel * @since 5.0 */ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder { @@ -131,7 +132,7 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder { @Override public ServerHttpRequest build() { return new MutatedServerHttpRequest(getUriToUse(), this.contextPath, - this.httpMethodValue, this.sslInfo, this.remoteAddress, this.body, this.originalRequest); + this.httpMethodValue, this.sslInfo, this.remoteAddress, this.headers, this.body, this.originalRequest); } private URI getUriToUse() { @@ -190,9 +191,9 @@ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder { public MutatedServerHttpRequest(URI uri, @Nullable String contextPath, String methodValue, @Nullable SslInfo sslInfo, @Nullable InetSocketAddress remoteAddress, - Flux body, ServerHttpRequest originalRequest) { + HttpHeaders headers, Flux body, ServerHttpRequest originalRequest) { - super(uri, contextPath, originalRequest.getHeaders()); + super(uri, contextPath, headers); this.methodValue = methodValue; this.remoteAddress = (remoteAddress != null ? remoteAddress : originalRequest.getRemoteAddress()); this.sslInfo = (sslInfo != null ? sslInfo : originalRequest.getSslInfo()); diff --git a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java index d9ca717ca6..7eca5ed6e1 100644 --- a/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java +++ b/spring-web/src/test/java/org/springframework/http/server/reactive/ServerHttpRequestTests.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import org.springframework.util.MultiValueMap; import org.springframework.web.testfixture.servlet.DelegatingServletInputStream; import org.springframework.web.testfixture.servlet.MockAsyncContext; @@ -46,6 +47,7 @@ import static org.mockito.Mockito.mock; * * @author Rossen Stoyanchev * @author Sam Brannen + * @author Brian Clozel */ public class ServerHttpRequestTests { @@ -166,6 +168,18 @@ public class ServerHttpRequestTests { assertThat(request.getHeaders().get(headerName)).containsExactly(headerValue3); } + @Test // gh-26615 + void mutateContentTypeHeaderValue() throws Exception { + ServerHttpRequest request = createRequest("/path").mutate() + .headers(headers -> headers.setContentType(MediaType.APPLICATION_JSON)).build(); + + assertThat(request.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON); + + ServerHttpRequest mutated = request.mutate() + .headers(headers -> headers.setContentType(MediaType.APPLICATION_CBOR)).build(); + assertThat(mutated.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_CBOR); + } + @Test void mutateWithExistingContextPath() throws Exception { ServerHttpRequest request = createRequest("/context/path", "/context");