Skip to content

Commit 1a05765

Browse files
committed
Defer ExchangeFilterFunction to subscription time
Previously fixed in 5.2 via d46359. Now also backported to 5.1.x. Closes gh-23909
1 parent ca3440c commit 1a05765

File tree

3 files changed

+54
-17
lines changed

3 files changed

+54
-17
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,8 @@ public Mono<ClientResponse> exchange() {
317317
ClientRequest request = (this.inserter != null ?
318318
initRequestBuilder().body(this.inserter).build() :
319319
initRequestBuilder().build());
320-
return exchangeFunction.exchange(request).switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR);
320+
return Mono.defer(() -> exchangeFunction.exchange(request))
321+
.switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR);
321322
}
322323

323324
private ClientRequest.Builder initRequestBuilder() {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,7 +23,9 @@
2323
import org.springframework.util.Assert;
2424

2525
/**
26-
* Represents a function that filters an{@linkplain ExchangeFunction exchange function}.
26+
* Represents a function that filters an {@linkplain ExchangeFunction exchange function}.
27+
* <p>The filter is executed when a {@code Subscriber} subscribes to the
28+
* {@code Publisher} returned by the {@code WebClient}.
2729
*
2830
* @author Arjen Poutsma
2931
* @since 5.0

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

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -34,13 +34,20 @@
3434
import org.springframework.http.HttpHeaders;
3535
import org.springframework.http.MediaType;
3636

37-
import static org.junit.Assert.*;
38-
import static org.mockito.Mockito.*;
37+
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertFalse;
39+
import static org.junit.Assert.assertNull;
40+
import static org.mockito.Mockito.any;
41+
import static org.mockito.Mockito.mock;
42+
import static org.mockito.Mockito.verifyNoMoreInteractions;
43+
import static org.mockito.Mockito.verifyZeroInteractions;
44+
import static org.mockito.Mockito.when;
3945

4046
/**
4147
* Unit tests for {@link DefaultWebClient}.
4248
*
4349
* @author Rossen Stoyanchev
50+
* @author Brian Clozel
4451
*/
4552
public class DefaultWebClientTests {
4653

@@ -56,14 +63,15 @@ public class DefaultWebClientTests {
5663
public void setup() {
5764
MockitoAnnotations.initMocks(this);
5865
this.exchangeFunction = mock(ExchangeFunction.class);
59-
when(this.exchangeFunction.exchange(this.captor.capture())).thenReturn(Mono.empty());
66+
ClientResponse mockResponse = mock(ClientResponse.class);
67+
when(this.exchangeFunction.exchange(this.captor.capture())).thenReturn(Mono.just(mockResponse));
6068
this.builder = WebClient.builder().baseUrl("/base").exchangeFunction(this.exchangeFunction);
6169
}
6270

6371

6472
@Test
6573
public void basic() {
66-
this.builder.build().get().uri("/path").exchange();
74+
this.builder.build().get().uri("/path").exchange().block(Duration.ofSeconds(10));
6775

6876
ClientRequest request = verifyAndGetRequest();
6977
assertEquals("/base/path", request.url().toString());
@@ -75,7 +83,8 @@ public void basic() {
7583
public void uriBuilder() {
7684
this.builder.build().get()
7785
.uri(builder -> builder.path("/path").queryParam("q", "12").build())
78-
.exchange();
86+
.exchange()
87+
.block(Duration.ofSeconds(10));
7988

8089
ClientRequest request = verifyAndGetRequest();
8190
assertEquals("/base/path?q=12", request.url().toString());
@@ -86,7 +95,8 @@ public void uriBuilder() {
8695
public void uriBuilderWithPathOverride() {
8796
this.builder.build().get()
8897
.uri(builder -> builder.replacePath("/path").build())
89-
.exchange();
98+
.exchange()
99+
.block(Duration.ofSeconds(10));
90100

91101
ClientRequest request = verifyAndGetRequest();
92102
assertEquals("/path", request.url().toString());
@@ -97,7 +107,8 @@ public void uriBuilderWithPathOverride() {
97107
public void requestHeaderAndCookie() {
98108
this.builder.build().get().uri("/path").accept(MediaType.APPLICATION_JSON)
99109
.cookies(cookies -> cookies.add("id", "123")) // SPR-16178
100-
.exchange();
110+
.exchange()
111+
.block(Duration.ofSeconds(10));
101112

102113
ClientRequest request = verifyAndGetRequest();
103114
assertEquals("application/json", request.headers().getFirst("Accept"));
@@ -111,7 +122,7 @@ public void defaultHeaderAndCookie() {
111122
.defaultHeader("Accept", "application/json").defaultCookie("id", "123")
112123
.build();
113124

114-
client.get().uri("/path").exchange();
125+
client.get().uri("/path").exchange().block(Duration.ofSeconds(10));
115126

116127
ClientRequest request = verifyAndGetRequest();
117128
assertEquals("application/json", request.headers().getFirst("Accept"));
@@ -126,7 +137,8 @@ public void defaultHeaderAndCookieOverrides() {
126137
.defaultCookie("id", "123")
127138
.build();
128139

129-
client.get().uri("/path").header("Accept", "application/xml").cookie("id", "456").exchange();
140+
client.get().uri("/path").header("Accept", "application/xml").cookie("id", "456")
141+
.exchange().block(Duration.ofSeconds(10));
130142

131143
ClientRequest request = verifyAndGetRequest();
132144
assertEquals("application/xml", request.headers().getFirst("Accept"));
@@ -151,7 +163,7 @@ public void defaultRequest() {
151163

152164
try {
153165
context.set("bar");
154-
client.get().uri("/path").attribute("foo", "bar").exchange();
166+
client.get().uri("/path").attribute("foo", "bar").exchange().block(Duration.ofSeconds(10));
155167
}
156168
finally {
157169
context.remove();
@@ -219,7 +231,8 @@ public void withStringAttribute() {
219231
this.builder.filter(filter).build()
220232
.get().uri("/path")
221233
.attribute("foo", "bar")
222-
.exchange();
234+
.exchange()
235+
.block(Duration.ofSeconds(10));
223236

224237
assertEquals("bar", actual.get("foo"));
225238

@@ -238,7 +251,8 @@ public void withNullAttribute() {
238251
this.builder.filter(filter).build()
239252
.get().uri("/path")
240253
.attribute("foo", null)
241-
.exchange();
254+
.exchange()
255+
.block(Duration.ofSeconds(10));
242256

243257
assertNull(actual.get("foo"));
244258

@@ -254,7 +268,7 @@ public void apply() {
254268
.defaultCookie("id", "123"))
255269
.build();
256270

257-
client.get().uri("/path").exchange();
271+
client.get().uri("/path").exchange().block(Duration.ofSeconds(10));
258272

259273
ClientRequest request = verifyAndGetRequest();
260274
assertEquals("application/json", request.headers().getFirst("Accept"));
@@ -264,11 +278,31 @@ public void apply() {
264278

265279
@Test
266280
public void switchToErrorOnEmptyClientResponseMono() {
281+
ExchangeFunction exchangeFunction = mock(ExchangeFunction.class);
282+
when(exchangeFunction.exchange(any())).thenReturn(Mono.empty());
283+
WebClient.Builder builder = WebClient.builder().baseUrl("/base").exchangeFunction(exchangeFunction);
267284
StepVerifier.create(builder.build().get().uri("/path").exchange())
268285
.expectErrorMessage("The underlying HTTP client completed without emitting a response.")
269286
.verify(Duration.ofSeconds(5));
270287
}
271288

289+
@Test // gh-23909
290+
public void shouldApplyFiltersAtSubscription() {
291+
WebClient client = this.builder
292+
.filter((request, next) ->
293+
next.exchange(ClientRequest
294+
.from(request)
295+
.header("Custom", "value")
296+
.build()))
297+
.build();
298+
Mono<ClientResponse> exchange = client.get().uri("/path").exchange();
299+
verifyZeroInteractions(this.exchangeFunction);
300+
exchange.block(Duration.ofSeconds(10));
301+
ClientRequest request = verifyAndGetRequest();
302+
assertEquals("value", request.headers().getFirst("Custom"));
303+
}
304+
305+
272306
private ClientRequest verifyAndGetRequest() {
273307
ClientRequest request = this.captor.getValue();
274308
Mockito.verify(this.exchangeFunction).exchange(request);

0 commit comments

Comments
 (0)