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 847669e0eb65..d4398d76091b 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 @@ * * @author Rossen Stoyanchev * @author Sebastien Deleuze + * @author Brian Clozel * @since 5.0 */ class DefaultServerHttpRequestBuilder implements ServerHttpRequest.Builder { @@ -131,7 +132,7 @@ public ServerHttpRequest.Builder remoteAddress(InetSocketAddress remoteAddress) @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 @@ private static class MutatedServerHttpRequest extends AbstractServerHttpRequest 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 d9ca717ca6a1..7eca5ed6e14e 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.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 @@ * * @author Rossen Stoyanchev * @author Sam Brannen + * @author Brian Clozel */ public class ServerHttpRequestTests { @@ -166,6 +168,18 @@ public void mutateHeaderBySettingHeaderValues() throws Exception { 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");