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
This commit is contained in:
Brian Clozel 2021-03-01 20:55:03 +01:00
parent 0087578469
commit 5a11569790
2 changed files with 18 additions and 3 deletions

View File

@ -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<DataBuffer> body, ServerHttpRequest originalRequest) {
HttpHeaders headers, Flux<DataBuffer> 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());

View File

@ -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");