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 d6b25440362..778ecf2766d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -316,7 +316,8 @@ class DefaultWebClient implements WebClient { ClientRequest request = (this.inserter != null ? initRequestBuilder().body(this.inserter).build() : initRequestBuilder().build()); - return exchangeFunction.exchange(request).switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR); + return Mono.defer(() -> exchangeFunction.exchange(request)) + .switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR); } private ClientRequest.Builder initRequestBuilder() { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunction.java index a2e9bdf1355..1887a5cf7a7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunction.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,9 @@ import reactor.core.publisher.Mono; import org.springframework.util.Assert; /** - * Represents a function that filters an{@linkplain ExchangeFunction exchange function}. + * Represents a function that filters an {@linkplain ExchangeFunction exchange function}. + *

The filter is executed when a {@code Subscriber} subscribes to the + * {@code Publisher} returned by the {@code WebClient}. * * @author Arjen Poutsma * @since 5.0 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 b8b4f582885..56d8f1c8467 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import static org.mockito.Mockito.*; * Unit tests for {@link DefaultWebClient}. * * @author Rossen Stoyanchev + * @author Brian Clozel */ public class DefaultWebClientTests { @@ -56,14 +57,16 @@ public class DefaultWebClientTests { public void setup() { MockitoAnnotations.initMocks(this); this.exchangeFunction = mock(ExchangeFunction.class); - when(this.exchangeFunction.exchange(this.captor.capture())).thenReturn(Mono.empty()); + ClientResponse mockResponse = mock(ClientResponse.class); + when(this.exchangeFunction.exchange(this.captor.capture())).thenReturn(Mono.just(mockResponse)); this.builder = WebClient.builder().baseUrl("/base").exchangeFunction(this.exchangeFunction); } @Test public void basic() { - this.builder.build().get().uri("/path").exchange(); + this.builder.build().get().uri("/path") + .exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("/base/path", request.url().toString()); @@ -75,34 +78,31 @@ public class DefaultWebClientTests { public void uriBuilder() { this.builder.build().get() .uri(builder -> builder.path("/path").queryParam("q", "12").build()) - .exchange(); + .exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("/base/path?q=12", request.url().toString()); - verifyNoMoreInteractions(this.exchangeFunction); } @Test public void uriBuilderWithPathOverride() { this.builder.build().get() .uri(builder -> builder.replacePath("/path").build()) - .exchange(); + .exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("/path", request.url().toString()); - verifyNoMoreInteractions(this.exchangeFunction); } @Test public void requestHeaderAndCookie() { this.builder.build().get().uri("/path").accept(MediaType.APPLICATION_JSON) .cookies(cookies -> cookies.add("id", "123")) // SPR-16178 - .exchange(); + .exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("application/json", request.headers().getFirst("Accept")); assertEquals("123", request.cookies().getFirst("id")); - verifyNoMoreInteractions(this.exchangeFunction); } @Test @@ -111,12 +111,11 @@ public class DefaultWebClientTests { .defaultHeader("Accept", "application/json").defaultCookie("id", "123") .build(); - client.get().uri("/path").exchange(); + client.get().uri("/path").exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("application/json", request.headers().getFirst("Accept")); assertEquals("123", request.cookies().getFirst("id")); - verifyNoMoreInteractions(this.exchangeFunction); } @Test @@ -126,12 +125,14 @@ public class DefaultWebClientTests { .defaultCookie("id", "123") .build(); - client.get().uri("/path").header("Accept", "application/xml").cookie("id", "456").exchange(); + client.get().uri("/path") + .header("Accept", "application/xml") + .cookie("id", "456") + .exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("application/xml", request.headers().getFirst("Accept")); assertEquals("456", request.cookies().getFirst("id")); - verifyNoMoreInteractions(this.exchangeFunction); } @Test @@ -151,7 +152,8 @@ public class DefaultWebClientTests { try { context.set("bar"); - client.get().uri("/path").attribute("foo", "bar").exchange(); + client.get().uri("/path").attribute("foo", "bar") + .exchange().block(Duration.ofSeconds(10)); } finally { context.remove(); @@ -219,7 +221,7 @@ public class DefaultWebClientTests { this.builder.filter(filter).build() .get().uri("/path") .attribute("foo", "bar") - .exchange(); + .exchange().block(Duration.ofSeconds(10)); assertEquals("bar", actual.get("foo")); @@ -238,7 +240,7 @@ public class DefaultWebClientTests { this.builder.filter(filter).build() .get().uri("/path") .attribute("foo", null) - .exchange(); + .exchange().block(Duration.ofSeconds(10)); assertNull(actual.get("foo")); @@ -254,21 +256,40 @@ public class DefaultWebClientTests { .defaultCookie("id", "123")) .build(); - client.get().uri("/path").exchange(); + client.get().uri("/path").exchange().block(Duration.ofSeconds(10)); ClientRequest request = verifyAndGetRequest(); assertEquals("application/json", request.headers().getFirst("Accept")); assertEquals("123", request.cookies().getFirst("id")); - verifyNoMoreInteractions(this.exchangeFunction); } @Test public void switchToErrorOnEmptyClientResponseMono() { + ExchangeFunction exchangeFunction = mock(ExchangeFunction.class); + when(exchangeFunction.exchange(any())).thenReturn(Mono.empty()); + WebClient.Builder builder = WebClient.builder().baseUrl("/base").exchangeFunction(exchangeFunction); StepVerifier.create(builder.build().get().uri("/path").exchange()) .expectErrorMessage("The underlying HTTP client completed without emitting a response.") .verify(Duration.ofSeconds(5)); } + @Test + public void shouldApplyFiltersAtSubscription() { + WebClient client = this.builder + .filter((request, next) -> { + return next.exchange(ClientRequest + .from(request) + .header("Custom", "value") + .build()); + }) + .build(); + Mono exchange = client.get().uri("/path").exchange(); + verifyZeroInteractions(this.exchangeFunction); + exchange.block(Duration.ofSeconds(10)); + ClientRequest request = verifyAndGetRequest(); + assertEquals("value", request.headers().getFirst("Custom")); + } + private ClientRequest verifyAndGetRequest() { ClientRequest request = this.captor.getValue(); Mockito.verify(this.exchangeFunction).exchange(request);