From d25ae4b02c9ea4f20da4e816c2da662cc177eda5 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 21 Apr 2021 17:04:10 +0100 Subject: [PATCH] Add advice on using exchange from an ExchangeFilterFunction Closes gh-26819 --- .../client/ExchangeFilterFunction.java | 9 +++- .../function/client/ExchangeFunction.java | 8 +++ src/docs/asciidoc/web/webflux-webclient.adoc | 52 +++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) 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 12fb186a53..d11bc4eabc 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-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -39,6 +39,13 @@ public interface ExchangeFilterFunction { * in the chain, to be invoked via * {@linkplain ExchangeFunction#exchange(ClientRequest) invoked} in order to * proceed with the exchange, or not invoked to shortcut the chain. + * + *

Note: When a filter handles the response after the + * call to {@link ExchangeFunction#exchange}, extra care must be taken to + * always consume its content or otherwise propagate it downstream for + * further handling, for example by the {@link WebClient}. Please, see the + * reference documentation for more details on this. + * * @param request the current request * @param next the next exchange function in the chain * @return the filtered response diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunction.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunction.java index 79fe6f708c..bb69193e5e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunction.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFunction.java @@ -43,6 +43,14 @@ public interface ExchangeFunction { /** * Exchange the given request for a {@link ClientResponse} promise. + * + *

Note: When a calling this method from an + * {@link ExchangeFilterFunction} that handles the response in some way, + * extra care must be taken to always consume its content or otherwise + * propagate it downstream for further handling, for example by the + * {@link WebClient}. Please, see the reference documentation for more + * details on this. + * * @param request the request to exchange * @return the delayed response */ diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 4beaeab438..2c2e831e50 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -889,9 +889,8 @@ a filter for basic authentication through a static factory method: .build() ---- -You can create a new `WebClient` instance by using another as a starting point. This allows -insert or removing filters without affecting the original `WebClient`. Below is an example -that inserts a basic authentication filter at index 0: +Filters can be added or removed by mutating an existing `WebClient` instance, resulting +in a new `WebClient` instance that does not affect the original one. For example: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -912,6 +911,53 @@ that inserts a basic authentication filter at index 0: .build() ---- +`WebClient` is a thin facade around the chain of filters followed by an +`ExchangeFunction`. It provides a workflow to make requests, to encode to and from higher +level objects, and it helps to ensure that response content is always consumed. +When filters handle the response in some way, extra care must be taken to always consume +its content or to otherwise propagate it downstream to the `WebClient` which will ensure +the same. Below is a filter that handles the `UNAUTHORIZED` status code but ensures that +any response content, whether expected or not, is released: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + public ExchangeFilterFunction renewTokenFilter() { + return (request, next) -> next.exchange(request).flatMap(response -> { + if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) { + return response.releaseBody() + .then(renewToken()) + .flatMap(token -> { + ClientRequest newRequest = ClientRequest.from(request).build(); + return next.exchange(newRequest); + }); + } else { + return Mono.just(response); + } + }); + } +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + fun renewTokenFilter(): ExchangeFilterFunction? { + return ExchangeFilterFunction { request: ClientRequest?, next: ExchangeFunction -> + next.exchange(request!!).flatMap { response: ClientResponse -> + if (response.statusCode().value() == HttpStatus.UNAUTHORIZED.value()) { + return@flatMap response.releaseBody() + .then(renewToken()) + .flatMap { token: String? -> + val newRequest = ClientRequest.from(request).build() + next.exchange(newRequest) + } + } else { + return@flatMap Mono.just(response) + } + } + } + } +---- + + [[webflux-client-attributes]] == Attributes