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 2d41cb02e0..4606255b25 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 @@ -79,17 +79,21 @@ class DefaultWebClient implements WebClient { @Nullable private final MultiValueMap defaultCookies; + @Nullable + private final Consumer> defaultRequest; + private final DefaultWebClientBuilder builder; DefaultWebClient(ExchangeFunction exchangeFunction, @Nullable UriBuilderFactory factory, @Nullable HttpHeaders defaultHeaders, @Nullable MultiValueMap defaultCookies, - DefaultWebClientBuilder builder) { + @Nullable Consumer> defaultRequest, DefaultWebClientBuilder builder) { this.exchangeFunction = exchangeFunction; this.uriBuilderFactory = (factory != null ? factory : new DefaultUriBuilderFactory()); this.defaultHeaders = defaultHeaders; this.defaultCookies = defaultCookies; + this.defaultRequest = defaultRequest; this.builder = builder; } @@ -317,6 +321,9 @@ class DefaultWebClient implements WebClient { } private ClientRequest.Builder initRequestBuilder() { + if (defaultRequest != null) { + defaultRequest.accept(this); + } URI uri = (this.uri != null ? this.uri : uriBuilderFactory.expand("")); return ClientRequest.create(this.httpMethod, uri) .headers(headers -> headers.addAll(initHeaders())) 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 4e1dc131bc..682316cd91 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 @@ -57,6 +57,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder { @Nullable private MultiValueMap defaultCookies; + @Nullable + private Consumer> defaultRequest; + @Nullable private List filters; @@ -89,6 +92,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder { } this.defaultCookies = other.defaultCookies != null ? new LinkedMultiValueMap<>(other.defaultCookies) : null; + this.defaultRequest = other.defaultRequest; this.filters = other.filters != null ? new ArrayList<>(other.filters) : null; this.connector = other.connector; this.exchangeFunction = other.exchangeFunction; @@ -152,6 +156,13 @@ final class DefaultWebClientBuilder implements WebClient.Builder { return this.defaultCookies; } + @Override + public WebClient.Builder defaultRequest(Consumer> defaultRequest) { + this.defaultRequest = this.defaultRequest != null ? + this.defaultRequest.andThen(defaultRequest) : defaultRequest; + return this; + } + @Override public WebClient.Builder clientConnector(ClientHttpConnector connector) { this.connector = connector; @@ -201,7 +212,7 @@ final class DefaultWebClientBuilder implements WebClient.Builder { return new DefaultWebClient(filteredExchange, initUriBuilderFactory(), this.defaultHeaders != null ? unmodifiableCopy(this.defaultHeaders) : null, this.defaultCookies != null ? unmodifiableCopy(this.defaultCookies) : null, - new DefaultWebClientBuilder(this)); + this.defaultRequest, new DefaultWebClientBuilder(this)); } private ExchangeFunction initExchangeFunction() { 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 0878ad2fe4..116350b335 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 @@ -226,7 +226,7 @@ public interface WebClient { Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); /** - * A global option to specify a header to be added to every request, + * Global option to specify a header to be added to every request, * if the request does not already contain such a header. * @param header the header name * @param values the header values @@ -241,7 +241,7 @@ public interface WebClient { Builder defaultHeaders(Consumer headersConsumer); /** - * A global option to specify a cookie to be added to every request, + * Global option to specify a cookie to be added to every request, * if the request does not already contain such a cookie. * @param cookie the cookie name * @param values the cookie values @@ -255,6 +255,14 @@ public interface WebClient { */ Builder defaultCookies(Consumer> cookiesConsumer); + /** + * Provide a consumer to modify every request being built just before the + * call to {@link RequestHeadersSpec#exchange() exchange()}. + * @param defaultRequest the consumer to use for modifying requests + * @since 5.1 + */ + Builder defaultRequest(Consumer> defaultRequest); + /** * Add the given filter to the filter chain. * @param filter the filter to be added to the chain 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 a4e0f72c38..593412c3d6 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 @@ -30,6 +30,8 @@ import org.mockito.MockitoAnnotations; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import org.springframework.core.NamedInheritableThreadLocal; +import org.springframework.core.NamedThreadLocal; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -138,6 +140,33 @@ public class DefaultWebClientTests { verifyNoMoreInteractions(this.exchangeFunction); } + @Test + public void defaultRequest() { + + ThreadLocal context = new NamedThreadLocal<>("foo"); + + Map actual = new HashMap<>(); + ExchangeFilterFunction filter = (request, next) -> { + actual.putAll(request.attributes()); + return next.exchange(request); + }; + + WebClient client = this.builder + .defaultRequest(spec -> spec.attribute("foo", context.get())) + .filter(filter) + .build(); + + try { + context.set("bar"); + client.get().uri("/path").attribute("foo", "bar").exchange(); + } + finally { + context.remove(); + } + + assertEquals("bar", actual.get("foo")); + } + @Test(expected = IllegalArgumentException.class) public void bodyObjectPublisher() { Mono mono = Mono.empty();