diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 61b5beff4f..34f0909721 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -48,13 +48,14 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.reactive.function.BodyExtractor; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.client.ClientResponse; -import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriBuilder; -import static java.nio.charset.StandardCharsets.*; -import static org.springframework.test.util.AssertionErrors.*; -import static org.springframework.web.reactive.function.BodyExtractors.*; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.springframework.test.util.AssertionErrors.assertEquals; +import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.web.reactive.function.BodyExtractors.toFlux; +import static org.springframework.web.reactive.function.BodyExtractors.toMono; /** * Default implementation of {@link WebTestClient}. @@ -80,12 +81,6 @@ class DefaultWebTestClient implements WebTestClient { this.timeout = (timeout != null ? timeout : Duration.ofSeconds(5)); } - private DefaultWebTestClient(DefaultWebTestClient webTestClient, ExchangeFilterFunction filter) { - this.webClient = webTestClient.webClient.filter(filter); - this.wiretapConnector = webTestClient.wiretapConnector; - this.timeout = webTestClient.timeout; - } - private Duration getTimeout() { return this.timeout; @@ -134,12 +129,6 @@ class DefaultWebTestClient implements WebTestClient { } - @Override - public WebTestClient filter(ExchangeFilterFunction filter) { - return new DefaultWebTestClient(this, filter); - } - - @SuppressWarnings("unchecked") private class DefaultUriSpec> implements UriSpec { @@ -193,8 +182,8 @@ class DefaultWebTestClient implements WebTestClient { } @Override - public RequestBodySpec headers(HttpHeaders headers) { - this.bodySpec.headers(headers); + public RequestBodySpec headers(Consumer headersConsumer) { + this.bodySpec.headers(headersConsumer); return this; } @@ -229,8 +218,9 @@ class DefaultWebTestClient implements WebTestClient { } @Override - public RequestBodySpec cookies(MultiValueMap cookies) { - this.bodySpec.cookies(cookies); + public RequestBodySpec cookies( + Consumer> cookiesConsumer) { + this.bodySpec.cookies(cookiesConsumer); return this; } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java index 7950ab7f13..edddbe8ba1 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java @@ -17,10 +17,15 @@ package org.springframework.test.web.reactive.server; import java.time.Duration; +import java.util.List; +import java.util.function.Consumer; +import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; import org.springframework.http.server.reactive.HttpHandler; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriBuilderFactory; @@ -71,12 +76,37 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder { return this; } + @Override + public WebTestClient.Builder defaultHeaders(Consumer headersConsumer) { + this.webClientBuilder.defaultHeaders(headersConsumer); + return this; + } + @Override public WebTestClient.Builder defaultCookie(String cookieName, String... cookieValues) { this.webClientBuilder.defaultCookie(cookieName, cookieValues); return this; } + @Override + public WebTestClient.Builder defaultCookies( + Consumer> cookiesConsumer) { + this.webClientBuilder.defaultCookies(cookiesConsumer); + return this; + } + + @Override + public WebTestClient.Builder filter(ExchangeFilterFunction filter) { + this.webClientBuilder.filter(filter); + return this; + } + + @Override + public WebTestClient.Builder filters(Consumer> filtersConsumer) { + this.webClientBuilder.filters(filtersConsumer); + return this; + } + @Override public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) { this.webClientBuilder.exchangeStrategies(strategies); diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 7d02579f8a..043c8eae32 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -43,7 +43,6 @@ import org.springframework.web.reactive.config.ViewResolverRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.client.ExchangeFilterFunction; -import org.springframework.web.reactive.function.client.ExchangeFunction; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.server.HandlerStrategies; @@ -128,15 +127,6 @@ public interface WebTestClient { UriSpec> options(); - /** - * Filter the client with the given {@code ExchangeFilterFunction}. - * @param filterFunction the filter to apply to this client - * @return the filtered client - * @see ExchangeFilterFunction#apply(ExchangeFunction) - */ - WebTestClient filter(ExchangeFilterFunction filterFunction); - - // Static, factory methods /** @@ -321,6 +311,17 @@ public interface WebTestClient { */ Builder defaultHeader(String headerName, String... headerValues); + /** + * Manipulate the default headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer can be used to + * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, + * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other + * {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} + * @return this builder + */ + Builder defaultHeaders(Consumer headersConsumer); + /** * Add the given header to all requests that haven't added it. * @param cookieName the cookie name @@ -328,6 +329,32 @@ public interface WebTestClient { */ Builder defaultCookie(String cookieName, String... cookieValues); + /** + * Manipulate the default cookies with the given consumer. The + * map provided to the consumer is "live", so that the consumer can be used to + * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, + * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other + * {@link MultiValueMap} methods. + * @param cookiesConsumer a function that consumes the cookies map + * @return this builder + */ + Builder defaultCookies(Consumer> cookiesConsumer); + + /** + * Add the given filter to the filter chain. + * @param filter the filter to be added to the chain + */ + Builder filter(ExchangeFilterFunction filter); + + /** + * Manipulate the filters with the given consumer. The + * list provided to the consumer is "live", so that the consumer can be used to remove + * filters, change ordering, etc. + * @param filtersConsumer a function that consumes the filter list + * @return this builder + */ + Builder filters(Consumer> filtersConsumer); + /** * Configure the {@link ExchangeStrategies} to use. *

By default {@link ExchangeStrategies#withDefaults()} is used. @@ -417,12 +444,15 @@ public interface WebTestClient { S cookie(String name, String value); /** - * Copy the given cookies into the entity's cookies map. - * - * @param cookies the existing cookies to copy from - * @return the same instance + * Manipulate this request's cookies with the given consumer. The + * map provided to the consumer is "live", so that the consumer can be used to + * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, + * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other + * {@link MultiValueMap} methods. + * @param cookiesConsumer a function that consumes the cookies map + * @return this builder */ - S cookies(MultiValueMap cookies); + S cookies(Consumer> cookiesConsumer); /** * Set the value of the {@code If-Modified-Since} header. @@ -449,11 +479,15 @@ public interface WebTestClient { S header(String headerName, String... headerValues); /** - * Copy the given headers into the entity's headers map. - * @param headers the existing headers to copy from - * @return the same instance + * Manipulate the request's headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer can be used to + * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, + * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other + * {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} + * @return this builder */ - S headers(HttpHeaders headers); + S headers(Consumer headersConsumer); /** * Perform the exchange without a request body. diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java index f2de0a72de..1aeb38cbf9 100644 --- a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java @@ -59,8 +59,14 @@ public class ExchangeMutatorWebFilterTests { @Test public void perRequestMutators() throws Exception { - this.webTestClient + + this.webTestClient = WebTestClient.bindToController(new TestController()) + .webFilter(this.exchangeMutator) + .configureClient() .filter(this.exchangeMutator.perClient(userIdentity("Giovanni"))) + .build(); + + this.webTestClient .get().uri("/userIdentity") .exchange() .expectStatus().isOk() diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java index d577c930f2..2b44659191 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java @@ -24,6 +24,7 @@ import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import org.reactivestreams.Publisher; @@ -66,16 +67,18 @@ class DefaultWebClient implements WebClient { private final MultiValueMap defaultCookies; + private final DefaultWebClientBuilder builder; + DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable UriBuilderFactory factory, - @Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap defaultCookies) { + @Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap defaultCookies, + DefaultWebClientBuilder builder) { this.exchangeFunction = exchangeFunction; this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory()); - this.defaultHeaders = (defaultHeaders != null ? - HttpHeaders.readOnlyHttpHeaders(defaultHeaders) : null); - this.defaultCookies = (defaultCookies != null ? - CollectionUtils.unmodifiableMultiValueMap(defaultCookies) : null); + this.defaultHeaders = defaultHeaders; + this.defaultCookies = defaultCookies; + this.builder = builder; } @@ -125,13 +128,10 @@ class DefaultWebClient implements WebClient { } @Override - public WebClient filter(ExchangeFilterFunction filterFunction) { - ExchangeFunction filteredExchangeFunction = this.exchangeFunction.filter(filterFunction); - return new DefaultWebClient(filteredExchangeFunction, - this.uriBuilderFactory, this.defaultHeaders, this.defaultCookies); + public Builder mutate() { + return this.builder; } - private class DefaultUriSpec> implements UriSpec { private final HttpMethod httpMethod; @@ -204,8 +204,9 @@ class DefaultWebClient implements WebClient { } @Override - public DefaultRequestBodySpec headers(HttpHeaders headers) { - getHeaders().putAll(headers); + public DefaultRequestBodySpec headers(Consumer headersConsumer) { + Assert.notNull(headersConsumer, "'headersConsumer' must not be null"); + headersConsumer.accept(this.headers); return this; } @@ -240,8 +241,10 @@ class DefaultWebClient implements WebClient { } @Override - public DefaultRequestBodySpec cookies(MultiValueMap cookies) { - getCookies().putAll(cookies); + public DefaultRequestBodySpec cookies( + Consumer> cookiesConsumer) { + Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null"); + cookiesConsumer.accept(this.cookies); return this; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java index 24e295aa84..5bef1ae576 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java @@ -16,13 +16,20 @@ package org.springframework.web.reactive.function.client; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import java.util.function.Consumer; import org.springframework.http.HttpHeaders; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.util.DefaultUriBuilderFactory; @@ -46,12 +53,38 @@ class DefaultWebClientBuilder implements WebClient.Builder { private MultiValueMap defaultCookies; + private List filters; + private ClientHttpConnector connector; private ExchangeStrategies exchangeStrategies = ExchangeStrategies.withDefaults(); private ExchangeFunction exchangeFunction; + public DefaultWebClientBuilder() { + } + + public DefaultWebClientBuilder(DefaultWebClientBuilder other) { + Assert.notNull(other, "'other' must not be null"); + + this.baseUrl = other.baseUrl; + this.defaultUriVariables = (other.defaultUriVariables != null ? + new LinkedHashMap<>(other.defaultUriVariables) : null); + this.uriBuilderFactory = other.uriBuilderFactory; + if (other.defaultHeaders != null) { + this.defaultHeaders = new HttpHeaders(); + this.defaultHeaders.putAll(other.defaultHeaders); + } + else { + this.defaultHeaders = null; + } + this.defaultCookies = (other.defaultCookies != null ? + new LinkedMultiValueMap<>(other.defaultCookies) : null); + this.filters = (other.filters != null ? new ArrayList<>(other.filters) : null); + this.connector = other.connector; + this.exchangeStrategies = other.exchangeStrategies; + this.exchangeFunction = other.exchangeFunction; + } @Override public WebClient.Builder baseUrl(String baseUrl) { @@ -73,22 +106,47 @@ class DefaultWebClientBuilder implements WebClient.Builder { @Override public WebClient.Builder defaultHeader(String headerName, String... headerValues) { - if (this.defaultHeaders == null) { - this.defaultHeaders = new HttpHeaders(); - } + initHeaders(); for (String headerValue : headerValues) { this.defaultHeaders.add(headerName, headerValue); } return this; } + @Override + public WebClient.Builder defaultHeaders(Consumer headersConsumer) { + Assert.notNull(headersConsumer, "'headersConsumer' must not be null"); + initHeaders(); + headersConsumer.accept(this.defaultHeaders); + return this; + } + + private void initHeaders() { + if (this.defaultHeaders == null) { + this.defaultHeaders = new HttpHeaders(); + } + } + @Override public WebClient.Builder defaultCookie(String cookieName, String... cookieValues) { + initCookies(); + this.defaultCookies.addAll(cookieName, Arrays.asList(cookieValues)); + return this; + } + + @Override + public WebClient.Builder defaultCookies( + Consumer> cookiesConsumer) { + Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null"); + initCookies(); + cookiesConsumer.accept(this.defaultCookies); + return this; + } + + private void initCookies() { if (this.defaultCookies == null) { this.defaultCookies = new LinkedMultiValueMap<>(4); } - this.defaultCookies.addAll(cookieName, Arrays.asList(cookieValues)); - return this; } @Override @@ -97,9 +155,31 @@ class DefaultWebClientBuilder implements WebClient.Builder { return this; } + @Override + public WebClient.Builder filter(ExchangeFilterFunction filter) { + Assert.notNull(filter, "'filter' must not be null"); + initFilters(); + this.filters.add(filter); + return this; + } + + @Override + public WebClient.Builder filters(Consumer> filtersConsumer) { + Assert.notNull(filtersConsumer, "'filtersConsumer' must not be null"); + initFilters(); + filtersConsumer.accept(this.filters); + return this; + } + + private void initFilters() { + if (this.filters == null) { + this.filters = new ArrayList<>(); + } + } + @Override public WebClient.Builder exchangeStrategies(ExchangeStrategies strategies) { - Assert.notNull(strategies, "ExchangeStrategies is required."); + Assert.notNull(strategies, "'strategies' must not be null"); this.exchangeStrategies = strategies; return this; } @@ -112,8 +192,37 @@ class DefaultWebClientBuilder implements WebClient.Builder { @Override public WebClient build() { - return new DefaultWebClient(initExchangeFunction(), initUriBuilderFactory(), - this.defaultHeaders, this.defaultCookies); + ExchangeFunction exchange = initExchangeFunction(); + ExchangeFunction filteredExchange = (this.filters != null ? this.filters.stream() + .reduce(ExchangeFilterFunction::andThen) + .map(filter -> filter.apply(exchange)) + .orElse(exchange) : exchange); + return new DefaultWebClient(filteredExchange, initUriBuilderFactory(), + unmodifiableCopy(this.defaultHeaders), unmodifiableCopy(this.defaultCookies), + new DefaultWebClientBuilder(this)); + } + + private static @Nullable HttpHeaders unmodifiableCopy(@Nullable HttpHeaders original) { + if (original != null) { + HttpHeaders copy = new HttpHeaders(); + copy.putAll(original); + return HttpHeaders.readOnlyHttpHeaders(copy); + } else { + return null; + } + } + + private static @Nullable MultiValueMap unmodifiableCopy(@Nullable MultiValueMap original) { + if (original != null) { + return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(original)); + } + else { + return null; + } + } + + private static List unmodifiableCopy(List list) { + return Collections.unmodifiableList(new ArrayList<>(list)); } private UriBuilderFactory initUriBuilderFactory() { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java index b8e1aae32f..cc05d5ca28 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java @@ -21,6 +21,7 @@ import java.nio.charset.Charset; import java.time.ZonedDateTime; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.function.Function; import org.reactivestreams.Publisher; @@ -108,12 +109,9 @@ public interface WebClient { /** - * Filter the client with the given {@code ExchangeFilterFunction}. - * @param filterFunction the filter to apply to this client - * @return the filtered client - * @see ExchangeFilterFunction#apply(ExchangeFunction) + * Return a builder to mutate properties of this web client. */ - WebClient filter(ExchangeFilterFunction filterFunction); + Builder mutate(); // Static, factory methods @@ -217,12 +215,23 @@ public interface WebClient { Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); /** - * Add the given header to all requests that haven't added it. + * Add the given header to all requests that have not added it. * @param headerName the header name * @param headerValues the header values */ Builder defaultHeader(String headerName, String... headerValues); + /** + * Manipulate the default headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer can be used to + * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, + * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other + * {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} + * @return this builder + */ + Builder defaultHeaders(Consumer headersConsumer); + /** * Add the given header to all requests that haven't added it. * @param cookieName the cookie name @@ -230,6 +239,17 @@ public interface WebClient { */ Builder defaultCookie(String cookieName, String... cookieValues); + /** + * Manipulate the default cookies with the given consumer. The + * map provided to the consumer is "live", so that the consumer can be used to + * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, + * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other + * {@link MultiValueMap} methods. + * @param cookiesConsumer a function that consumes the cookies map + * @return this builder + */ + Builder defaultCookies(Consumer> cookiesConsumer); + /** * Configure the {@link ClientHttpConnector} to use. *

By default an instance of @@ -243,6 +263,21 @@ public interface WebClient { */ Builder clientConnector(ClientHttpConnector connector); + /** + * Add the given filter to the filter chain. + * @param filter the filter to be added to the chain + */ + Builder filter(ExchangeFilterFunction filter); + + /** + * Manipulate the filters with the given consumer. The + * list provided to the consumer is "live", so that the consumer can be used to remove + * filters, change ordering, etc. + * @param filtersConsumer a function that consumes the filter list + * @return this builder + */ + Builder filters(Consumer> filtersConsumer); + /** * Configure the {@link ExchangeStrategies} to use. *

By default {@link ExchangeStrategies#withDefaults()} is used. @@ -334,11 +369,15 @@ public interface WebClient { S cookie(String name, String value); /** - * Copy the given cookies into the entity's cookies map. - * @param cookies the existing cookies to copy from + * Manipulate the request's cookies with the given consumer. The + * map provided to the consumer is "live", so that the consumer can be used to + * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, + * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other + * {@link MultiValueMap} methods. + * @param cookiesConsumer a function that consumes the cookies map * @return this builder */ - S cookies(MultiValueMap cookies); + S cookies(Consumer> cookiesConsumer); /** * Set the value of the {@code If-Modified-Since} header. @@ -365,11 +404,15 @@ public interface WebClient { S header(String headerName, String... headerValues); /** - * Copy the given headers into the entity's headers map. - * @param headers the existing headers to copy from + * Manipulate the request's headers with the given consumer. The + * headers provided to the consumer are "live", so that the consumer can be used to + * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, + * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other + * {@link HttpHeaders} methods. + * @param headersConsumer a function that consumes the {@code HttpHeaders} * @return this builder */ - S headers(HttpHeaders headers); + S headers(Consumer headersConsumer); /** * Exchange the request for a {@code ClientResponse} with full access diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java index 4fdaabe30b..d9b9138ee1 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java @@ -124,6 +124,28 @@ public class DefaultWebClientTests { client.post().uri("http://example.com").syncBody(mono); } + @Test + public void mutateDoesCopy() throws Exception { + + WebClient.Builder builder = WebClient.builder(); + builder.filter((request, next) -> next.exchange(request)); + builder.defaultHeader("foo", "bar"); + builder.defaultCookie("foo", "bar"); + WebClient client1 = builder.build(); + builder.filter((request, next) -> next.exchange(request)); + builder.defaultHeader("baz", "qux"); + builder.defaultCookie("baz", "qux"); + WebClient client2 = builder.build(); + + client1.mutate().filters(filters -> assertEquals(1, filters.size())); + client1.mutate().defaultHeaders(headers -> assertEquals(1, headers.size())); + client1.mutate().defaultCookies(cookies -> assertEquals(1, cookies.size())); + client2.mutate().filters(filters -> assertEquals(2, filters.size())); + client2.mutate().defaultHeaders(headers -> assertEquals(2, headers.size())); + client2.mutate().defaultCookies(cookies -> assertEquals(2, cookies.size())); + } + + private WebClient.Builder builder() { return WebClient.builder().baseUrl("/base").exchangeFunction(this.exchangeFunction); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java index 7a133ce59d..85346e7255 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java @@ -52,11 +52,12 @@ public class WebClientIntegrationTests { private WebClient webClient; + private String baseUrl; @Before public void setup() { this.server = new MockWebServer(); - String baseUrl = this.server.url("/").toString(); + baseUrl = this.server.url("/").toString(); this.webClient = WebClient.create(baseUrl); } @@ -394,13 +395,16 @@ public class WebClientIntegrationTests { @Test public void filter() throws Exception { - this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!")); + this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain") + .setBody("Hello Spring!")); - WebClient filteredClient = this.webClient.filter( - (request, next) -> { - ClientRequest filteredRequest = ClientRequest.from(request).header("foo", "bar").build(); + WebClient filteredClient = this.webClient.mutate() + .filter((request, next) -> { + ClientRequest filteredRequest = + ClientRequest.from(request).header("foo", "bar").build(); return next.exchange(filteredRequest); - }); + }) + .build(); Mono result = filteredClient.get() .uri("/greeting?name=Spring") @@ -429,7 +433,9 @@ public class WebClientIntegrationTests { } ); - WebClient filteredClient = this.webClient.filter(filter); + WebClient filteredClient = this.webClient.mutate() + .filter(filter) + .build(); // header not present this.server.enqueue(new MockResponse().setHeader("Content-Type", "text/plain").setBody("Hello Spring!")); @@ -462,6 +468,7 @@ public class WebClientIntegrationTests { Assert.assertEquals(2, server.getRequestCount()); } + @SuppressWarnings("serial") private static class MyException extends RuntimeException {