Browse Source

Replace WebClient.filter with Builder.filter

This commit replaces the WebClient.filter method with
WebClient.Builder.filter. The reason for this change is that filters
added via WebClient.filter would be applied in the opposite order of
their declaration, due to the compositional nature of the method,
combined with the immutable nature of the WebClient.
WebClient.Builder.filter does keep the order of the filters, as
registered.

Furthermore, this commit introduces a WebClient.mutate() method,
returning a WebClient.Builder. This method allow to add/remove filters
and other defaults from a given WebClient.

Issue: SPR-15657

Add WebClient.Builder.addFilter

Add Consumer-based headers and cookies methods to builders.

Add WebClient.mutate
pull/1448/merge
Arjen Poutsma 8 years ago
parent
commit
4a0597d612
  1. 30
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java
  2. 30
      spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java
  3. 72
      spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java
  4. 8
      spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java
  5. 31
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java
  6. 125
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java
  7. 67
      spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java
  8. 22
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java
  9. 21
      spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

30
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java

@ -48,13 +48,14 @@ import org.springframework.util.MultiValueMap; @@ -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 { @@ -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 { @@ -134,12 +129,6 @@ class DefaultWebTestClient implements WebTestClient {
}
@Override
public WebTestClient filter(ExchangeFilterFunction filter) {
return new DefaultWebTestClient(this, filter);
}
@SuppressWarnings("unchecked")
private class DefaultUriSpec<S extends RequestHeadersSpec<?>> implements UriSpec<S> {
@ -193,8 +182,8 @@ class DefaultWebTestClient implements WebTestClient { @@ -193,8 +182,8 @@ class DefaultWebTestClient implements WebTestClient {
}
@Override
public RequestBodySpec headers(HttpHeaders headers) {
this.bodySpec.headers(headers);
public RequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
this.bodySpec.headers(headersConsumer);
return this;
}
@ -229,8 +218,9 @@ class DefaultWebTestClient implements WebTestClient { @@ -229,8 +218,9 @@ class DefaultWebTestClient implements WebTestClient {
}
@Override
public RequestBodySpec cookies(MultiValueMap<String, String> cookies) {
this.bodySpec.cookies(cookies);
public RequestBodySpec cookies(
Consumer<MultiValueMap<String, String>> cookiesConsumer) {
this.bodySpec.cookies(cookiesConsumer);
return this;
}

30
spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClientBuilder.java

@ -17,10 +17,15 @@ @@ -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 { @@ -71,12 +76,37 @@ class DefaultWebTestClientBuilder implements WebTestClient.Builder {
return this;
}
@Override
public WebTestClient.Builder defaultHeaders(Consumer<HttpHeaders> 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<MultiValueMap<String, String>> 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<List<ExchangeFilterFunction>> filtersConsumer) {
this.webClientBuilder.filters(filtersConsumer);
return this;
}
@Override
public WebTestClient.Builder exchangeStrategies(ExchangeStrategies strategies) {
this.webClientBuilder.exchangeStrategies(strategies);

72
spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java

@ -43,7 +43,6 @@ import org.springframework.web.reactive.config.ViewResolverRegistry; @@ -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 { @@ -128,15 +127,6 @@ public interface WebTestClient {
UriSpec<RequestHeadersSpec<?>> 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 { @@ -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<HttpHeaders> 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 { @@ -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<MultiValueMap<String, String>> 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<List<ExchangeFilterFunction>> filtersConsumer);
/**
* Configure the {@link ExchangeStrategies} to use.
* <p>By default {@link ExchangeStrategies#withDefaults()} is used.
@ -417,12 +444,15 @@ public interface WebTestClient { @@ -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<String, String> cookies);
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Set the value of the {@code If-Modified-Since} header.
@ -449,11 +479,15 @@ public interface WebTestClient { @@ -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<HttpHeaders> headersConsumer);
/**
* Perform the exchange without a request body.

8
spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/ExchangeMutatorWebFilterTests.java

@ -59,8 +59,14 @@ public class ExchangeMutatorWebFilterTests { @@ -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()

31
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClient.java

@ -24,6 +24,7 @@ import java.time.format.DateTimeFormatter; @@ -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 { @@ -66,16 +67,18 @@ class DefaultWebClient implements WebClient {
private final MultiValueMap<String, String> defaultCookies;
private final DefaultWebClientBuilder builder;
DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable UriBuilderFactory factory,
@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> defaultCookies) {
@Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap<String, String> 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 { @@ -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<S extends RequestHeadersSpec<?>> implements UriSpec<S> {
private final HttpMethod httpMethod;
@ -204,8 +204,9 @@ class DefaultWebClient implements WebClient { @@ -204,8 +204,9 @@ class DefaultWebClient implements WebClient {
}
@Override
public DefaultRequestBodySpec headers(HttpHeaders headers) {
getHeaders().putAll(headers);
public DefaultRequestBodySpec headers(Consumer<HttpHeaders> headersConsumer) {
Assert.notNull(headersConsumer, "'headersConsumer' must not be null");
headersConsumer.accept(this.headers);
return this;
}
@ -240,8 +241,10 @@ class DefaultWebClient implements WebClient { @@ -240,8 +241,10 @@ class DefaultWebClient implements WebClient {
}
@Override
public DefaultRequestBodySpec cookies(MultiValueMap<String, String> cookies) {
getCookies().putAll(cookies);
public DefaultRequestBodySpec cookies(
Consumer<MultiValueMap<String, String>> cookiesConsumer) {
Assert.notNull(cookiesConsumer, "'cookiesConsumer' must not be null");
cookiesConsumer.accept(this.cookies);
return this;
}

125
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/DefaultWebClientBuilder.java

@ -16,13 +16,20 @@ @@ -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 { @@ -46,12 +53,38 @@ class DefaultWebClientBuilder implements WebClient.Builder {
private MultiValueMap<String, String> defaultCookies;
private List<ExchangeFilterFunction> 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 { @@ -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<HttpHeaders> 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<MultiValueMap<String, String>> 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 { @@ -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<List<ExchangeFilterFunction>> 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 { @@ -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 <K, V> MultiValueMap<K, V> unmodifiableCopy(@Nullable MultiValueMap<K, V> original) {
if (original != null) {
return CollectionUtils.unmodifiableMultiValueMap(new LinkedMultiValueMap<>(original));
}
else {
return null;
}
}
private static <T> List<T> unmodifiableCopy(List<? extends T> list) {
return Collections.unmodifiableList(new ArrayList<>(list));
}
private UriBuilderFactory initUriBuilderFactory() {

67
spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClient.java

@ -21,6 +21,7 @@ import java.nio.charset.Charset; @@ -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 { @@ -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 { @@ -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<HttpHeaders> 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 { @@ -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<MultiValueMap<String, String>> cookiesConsumer);
/**
* Configure the {@link ClientHttpConnector} to use.
* <p>By default an instance of
@ -243,6 +263,21 @@ public interface WebClient { @@ -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<List<ExchangeFilterFunction>> filtersConsumer);
/**
* Configure the {@link ExchangeStrategies} to use.
* <p>By default {@link ExchangeStrategies#withDefaults()} is used.
@ -334,11 +369,15 @@ public interface WebClient { @@ -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<String, String> cookies);
S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
/**
* Set the value of the {@code If-Modified-Since} header.
@ -365,11 +404,15 @@ public interface WebClient { @@ -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<HttpHeaders> headersConsumer);
/**
* Exchange the request for a {@code ClientResponse} with full access

22
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/DefaultWebClientTests.java

@ -124,6 +124,28 @@ public class DefaultWebClientTests { @@ -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);

21
spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java

@ -52,11 +52,12 @@ public class WebClientIntegrationTests { @@ -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 { @@ -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<String> result = filteredClient.get()
.uri("/greeting?name=Spring")
@ -429,7 +433,9 @@ public class WebClientIntegrationTests { @@ -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 { @@ -462,6 +468,7 @@ public class WebClientIntegrationTests {
Assert.assertEquals(2, server.getRequestCount());
}
@SuppressWarnings("serial")
private static class MyException extends RuntimeException {

Loading…
Cancel
Save