Skip to content

Commit d25ae4b

Browse files
committed
Add advice on using exchange from an ExchangeFilterFunction
Closes gh-26819
1 parent 5740eaf commit d25ae4b

File tree

3 files changed

+65
-4
lines changed

3 files changed

+65
-4
lines changed

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -39,6 +39,13 @@ public interface ExchangeFilterFunction {
3939
* in the chain, to be invoked via
4040
* {@linkplain ExchangeFunction#exchange(ClientRequest) invoked} in order to
4141
* proceed with the exchange, or not invoked to shortcut the chain.
42+
*
43+
* <p><strong>Note:</strong> When a filter handles the response after the
44+
* call to {@link ExchangeFunction#exchange}, extra care must be taken to
45+
* always consume its content or otherwise propagate it downstream for
46+
* further handling, for example by the {@link WebClient}. Please, see the
47+
* reference documentation for more details on this.
48+
*
4249
* @param request the current request
4350
* @param next the next exchange function in the chain
4451
* @return the filtered response

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ public interface ExchangeFunction {
4343

4444
/**
4545
* Exchange the given request for a {@link ClientResponse} promise.
46+
*
47+
* <p><strong>Note:</strong> When a calling this method from an
48+
* {@link ExchangeFilterFunction} that handles the response in some way,
49+
* extra care must be taken to always consume its content or otherwise
50+
* propagate it downstream for further handling, for example by the
51+
* {@link WebClient}. Please, see the reference documentation for more
52+
* details on this.
53+
*
4654
* @param request the request to exchange
4755
* @return the delayed response
4856
*/

src/docs/asciidoc/web/webflux-webclient.adoc

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -889,9 +889,8 @@ a filter for basic authentication through a static factory method:
889889
.build()
890890
----
891891

892-
You can create a new `WebClient` instance by using another as a starting point. This allows
893-
insert or removing filters without affecting the original `WebClient`. Below is an example
894-
that inserts a basic authentication filter at index 0:
892+
Filters can be added or removed by mutating an existing `WebClient` instance, resulting
893+
in a new `WebClient` instance that does not affect the original one. For example:
895894

896895
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
897896
.Java
@@ -912,6 +911,53 @@ that inserts a basic authentication filter at index 0:
912911
.build()
913912
----
914913

914+
`WebClient` is a thin facade around the chain of filters followed by an
915+
`ExchangeFunction`. It provides a workflow to make requests, to encode to and from higher
916+
level objects, and it helps to ensure that response content is always consumed.
917+
When filters handle the response in some way, extra care must be taken to always consume
918+
its content or to otherwise propagate it downstream to the `WebClient` which will ensure
919+
the same. Below is a filter that handles the `UNAUTHORIZED` status code but ensures that
920+
any response content, whether expected or not, is released:
921+
922+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
923+
.Java
924+
----
925+
public ExchangeFilterFunction renewTokenFilter() {
926+
return (request, next) -> next.exchange(request).flatMap(response -> {
927+
if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
928+
return response.releaseBody()
929+
.then(renewToken())
930+
.flatMap(token -> {
931+
ClientRequest newRequest = ClientRequest.from(request).build();
932+
return next.exchange(newRequest);
933+
});
934+
} else {
935+
return Mono.just(response);
936+
}
937+
});
938+
}
939+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
940+
.Kotlin
941+
----
942+
fun renewTokenFilter(): ExchangeFilterFunction? {
943+
return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction ->
944+
next.exchange(request!!).flatMap { response: ClientResponse ->
945+
if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) {
946+
return@flatMap response.releaseBody()
947+
.then(renewToken())
948+
.flatMap { token: String? ->
949+
val newRequest = ClientRequest.from(request).build()
950+
next.exchange(newRequest)
951+
}
952+
} else {
953+
return@flatMap Mono.just(response)
954+
}
955+
}
956+
}
957+
}
958+
----
959+
960+
915961
916962
[[webflux-client-attributes]]
917963
== Attributes

0 commit comments

Comments
 (0)