mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-10-09 00:29:04 +00:00
Preserve original headers and cookies when mutating client response
Prior to this commit, the `DefaultClientResponseBuilder` would assume that an original client HTTP response, when mutated, would not be reused nor read anymore. While this is the advised use case, there was some inconsistency with the builder API here when mutating: some data like the response status would copied, but the HTTP headers and cookies would refer directly to the previous entries, making all changes visible to the previous response instance. This commit ensures that deep copies are performed when mutating a client response with the builder API. Fixes gh-37086
This commit is contained in:
+6
-2
@@ -18,6 +18,7 @@ package org.springframework.web.reactive.function.client;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
@@ -144,7 +145,7 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
|
||||
@SuppressWarnings({"ConstantConditions", "NullAway"})
|
||||
private HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
this.headers = new HttpHeaders(this.originalResponse.headers().asHttpHeaders());
|
||||
this.headers = HttpHeaders.copyOf(this.originalResponse.headers().asHttpHeaders());
|
||||
}
|
||||
return this.headers;
|
||||
}
|
||||
@@ -166,7 +167,10 @@ final class DefaultClientResponseBuilder implements ClientResponse.Builder {
|
||||
@SuppressWarnings({"ConstantConditions", "NullAway"})
|
||||
private MultiValueMap<String, ResponseCookie> getCookies() {
|
||||
if (this.cookies == null) {
|
||||
this.cookies = new LinkedMultiValueMap<>(this.originalResponse.cookies());
|
||||
MultiValueMap<String, ResponseCookie> originalCookies = this.originalResponse.cookies();
|
||||
this.cookies = new LinkedMultiValueMap<>(originalCookies.size());
|
||||
originalCookies.forEach(
|
||||
(name, values) -> this.cookies.put(name, new ArrayList<>(values)));
|
||||
}
|
||||
return this.cookies;
|
||||
}
|
||||
|
||||
+7
-2
@@ -83,16 +83,21 @@ class DefaultClientResponseBuilderTests {
|
||||
ClientResponse result = otherResponse.mutate()
|
||||
.statusCode(HttpStatus.BAD_REQUEST)
|
||||
.headers(headers -> headers.set("foo", "baar"))
|
||||
.cookies(cookies -> cookies.set("baz", ResponseCookie.from("baz", "quux").build()))
|
||||
.cookies(cookies -> cookies.add("baz", ResponseCookie.from("baz", "pop").build()))
|
||||
.build();
|
||||
|
||||
assertThat(otherResponse.headers().asHttpHeaders().getFirst("foo")).isEqualTo("bar");
|
||||
assertThat(otherResponse.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz");
|
||||
assertThat(otherResponse.cookies().get("baz")).hasSize(1);
|
||||
|
||||
assertThat(result.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST);
|
||||
assertThat(result.headers().asHttpHeaders().size()).isEqualTo(3);
|
||||
assertThat(result.headers().asHttpHeaders().getFirst("foo")).isEqualTo("baar");
|
||||
assertThat(result.headers().asHttpHeaders().getFirst("bar")).isEqualTo("baz");
|
||||
assertThat(result.cookies()).hasSize(1);
|
||||
assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("quux");
|
||||
assertThat(result.cookies().get("baz")).hasSize(2);
|
||||
assertThat(result.cookies().getFirst("baz").getValue()).isEqualTo("qux");
|
||||
assertThat(result.cookies().get("baz").get(1).getValue()).isEqualTo("pop");
|
||||
assertThat(result.logPrefix()).isEqualTo("my-prefix");
|
||||
|
||||
StepVerifier.create(result.bodyToFlux(String.class))
|
||||
|
||||
Reference in New Issue
Block a user