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 6e96bf4ce1..a2e9bdf135 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 @@ -23,7 +23,7 @@ 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}. * * @author Arjen Poutsma * @since 5.0 @@ -33,32 +33,31 @@ public interface ExchangeFilterFunction { /** * Apply this filter to the given request and exchange function. - *

The given {@linkplain ExchangeFunction exchange function} represents the next entity - * in the chain, and can be {@linkplain ExchangeFunction#exchange(ClientRequest) invoked} - * in order to proceed to the exchange, or not invoked to block the chain. - * @param request the request + *

The given {@linkplain ExchangeFunction} represents the next entity + * 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. + * @param request the current request * @param next the next exchange function in the chain * @return the filtered response */ Mono filter(ClientRequest request, ExchangeFunction next); /** - * Return a composed filter function that first applies this filter, and then applies the - * {@code after} filter. - * @param after the filter to apply after this filter is applied - * @return a composed filter that first applies this function and then applies the - * {@code after} function + * Return a composed filter function that first applies this filter, and + * then applies the given {@code "after"} filter. + * @param afterFilter the filter to apply after this filter + * @return the composed filter */ - default ExchangeFilterFunction andThen(ExchangeFilterFunction after) { - Assert.notNull(after, "ExchangeFilterFunction must not be null"); - return (request, next) -> { - ExchangeFunction nextExchange = exchangeRequest -> after.filter(exchangeRequest, next); - return filter(request, nextExchange); - }; + default ExchangeFilterFunction andThen(ExchangeFilterFunction afterFilter) { + Assert.notNull(afterFilter, "ExchangeFilterFunction must not be null"); + return (request, next) -> + filter(request, afterRequest -> afterFilter.filter(afterRequest, next)); } /** - * Apply this filter to the given exchange function, resulting in a filtered exchange function. + * Apply this filter to the given {@linkplain ExchangeFunction}, resulting + * in a filtered exchange function. * @param exchange the exchange function to filter * @return the filtered exchange function */ @@ -68,25 +67,25 @@ public interface ExchangeFilterFunction { } /** - * Adapt the given request processor function to a filter function that only operates on the - * {@code ClientRequest}. - * @param requestProcessor the request processor - * @return the filter adaptation of the request processor + * Adapt the given request processor function to a filter function that only + * operates on the {@code ClientRequest}. + * @param processor the request processor + * @return the resulting filter adapter */ - static ExchangeFilterFunction ofRequestProcessor(Function> requestProcessor) { - Assert.notNull(requestProcessor, "Function must not be null"); - return (request, next) -> requestProcessor.apply(request).flatMap(next::exchange); + static ExchangeFilterFunction ofRequestProcessor(Function> processor) { + Assert.notNull(processor, "ClientRequest Function must not be null"); + return (request, next) -> processor.apply(request).flatMap(next::exchange); } /** - * Adapt the given response processor function to a filter function that only operates on the - * {@code ClientResponse}. - * @param responseProcessor the response processor - * @return the filter adaptation of the request processor + * Adapt the given response processor function to a filter function that + * only operates on the {@code ClientResponse}. + * @param processor the response processor + * @return the resulting filter adapter */ - static ExchangeFilterFunction ofResponseProcessor(Function> responseProcessor) { - Assert.notNull(responseProcessor, "Function must not be null"); - return (request, next) -> next.exchange(request).flatMap(responseProcessor); + static ExchangeFilterFunction ofResponseProcessor(Function> processor) { + Assert.notNull(processor, "ClientResponse Function must not be null"); + return (request, next) -> next.exchange(request).flatMap(processor); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java index 04dcf26a69..81af001081 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/ExchangeFilterFunctions.java @@ -32,8 +32,8 @@ import org.springframework.http.HttpStatus; import org.springframework.util.Assert; /** - * Implementations of {@link ExchangeFilterFunction} that provide various useful request filter - * operations, such as basic authentication, error handling, etc. + * Static factory methods providing access to built-in implementations of + * {@link ExchangeFilterFunction} for basic authentication, error handling, etc. * * @author Rob Winch * @author Arjen Poutsma @@ -42,93 +42,84 @@ import org.springframework.util.Assert; public abstract class ExchangeFilterFunctions { /** - * Name of the {@link ClientRequest} attribute that contains the - * {@link Credentials}, as used by {@link #basicAuthentication()}. + * Name of the {@linkplain ClientRequest#attributes() request attribute} that + * contains the {@link Credentials} used by {@link #basicAuthentication()}. */ public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE = ExchangeFilterFunctions.class.getName() + ".basicAuthenticationCredentials"; /** - * Return a filter that adds an Authorization header for HTTP Basic Authentication, based on - * the given username and password. + * Return a filter for HTTP Basic Authentication that adds an authorization + * header, based on the given user and password. *

Note that Basic Authentication only supports characters in the * {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. - * @param username the username to use - * @param password the password to use - * @return the {@link ExchangeFilterFunction} that adds the Authorization header - * @throws IllegalArgumentException if either {@code username} or {@code password} contain - * characters that cannot be encoded to ISO-8859-1 + * @param user the user + * @param password the password + * @return the filter for basic authentication + * @throws IllegalArgumentException if either {@code user} or + * {@code password} contain characters that cannot be encoded to ISO-8859-1. */ - public static ExchangeFilterFunction basicAuthentication(String username, String password) { - Assert.notNull(username, "'username' must not be null"); + public static ExchangeFilterFunction basicAuthentication(String user, String password) { + Assert.notNull(user, "'user' must not be null"); Assert.notNull(password, "'password' must not be null"); - checkIllegalCharacters(username, password); - return basicAuthenticationInternal(r -> Optional.of(new Credentials(username, password))); + checkIllegalCharacters(user, password); + return basicAuthenticationInternal(request -> Optional.of(new Credentials(user, password))); } /** - * Return a filter that adds an Authorization header for HTTP Basic Authentication, based on - * the {@link Credentials} provided in the - * {@linkplain ClientRequest#attributes() request attributes}. If the attribute is not found, - * no authorization header is added. - *

Note that Basic Authentication only supports characters in the - * {@link StandardCharsets#ISO_8859_1 ISO-8859-1} character set. - * @return the {@link ExchangeFilterFunction} that adds the Authorization header + * Variant of {@link #basicAuthentication(String, String)} that looks up + * the {@link Credentials Credentials} provided in a + * {@linkplain ClientRequest#attributes() request attribute}, or if the + * attribute is not found, the authorization header is not added. + * @return the filter for basic authentication * @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE * @see Credentials#basicAuthenticationCredentials(String, String) */ public static ExchangeFilterFunction basicAuthentication() { - return basicAuthenticationInternal( - request -> request.attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE).map(o -> (Credentials)o)); + return basicAuthenticationInternal(request -> + request.attribute(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE) + .map(credentials -> (Credentials) credentials)); } private static ExchangeFilterFunction basicAuthenticationInternal( Function> credentialsFunction) { - return ExchangeFilterFunction.ofRequestProcessor( - clientRequest -> credentialsFunction.apply(clientRequest).map( - credentials -> { - ClientRequest authorizedRequest = ClientRequest.from(clientRequest) - .headers(headers -> headers.set(HttpHeaders.AUTHORIZATION, - authorization(credentials))) - .build(); - return Mono.just(authorizedRequest); - }) - .orElse(Mono.just(clientRequest))); - } - - private static String authorization(Credentials credentials) { - String credentialsString = credentials.username + ":" + credentials.password; - byte[] credentialBytes = credentialsString.getBytes(StandardCharsets.ISO_8859_1); - byte[] encodedBytes = Base64.getEncoder().encode(credentialBytes); - String encodedCredentials = new String(encodedBytes, StandardCharsets.ISO_8859_1); - return "Basic " + encodedCredentials; + return ExchangeFilterFunction.ofRequestProcessor(request -> + credentialsFunction.apply(request) + .map(credentials -> Mono.just(insertAuthorizationHeader(request, credentials))) + .orElse(Mono.just(request))); } - /* - * Basic authentication only supports ISO 8859-1, see - * https://stackoverflow.com/questions/702629/utf-8-characters-mangled-in-http-basic-auth-username#703341 - */ private static void checkIllegalCharacters(String username, String password) { + + // Basic authentication only supports ISO 8859-1, see + // https://stackoverflow.com/questions/702629/utf-8-characters-mangled-in-http-basic-auth-username#703341 + CharsetEncoder encoder = StandardCharsets.ISO_8859_1.newEncoder(); if (!encoder.canEncode(username) || !encoder.canEncode(password)) { throw new IllegalArgumentException( "Username or password contains characters that cannot be encoded to ISO-8859-1"); } - } + private static ClientRequest insertAuthorizationHeader(ClientRequest request, Credentials credentials) { + return ClientRequest.from(request).headers(headers -> { + String credentialsString = credentials.username + ":" + credentials.password; + byte[] credentialBytes = credentialsString.getBytes(StandardCharsets.ISO_8859_1); + byte[] encodedBytes = Base64.getEncoder().encode(credentialBytes); + String encodedCredentials = new String(encodedBytes, StandardCharsets.ISO_8859_1); + headers.set(HttpHeaders.AUTHORIZATION, "Basic " + encodedCredentials); + }).build(); + } /** - * Return a filter that returns a given {@link Throwable} as response if the given + * Return a filter that generates an error signal when the given * {@link HttpStatus} predicate matches. - * @param statusPredicate the predicate that should match the - * {@linkplain ClientResponse#statusCode() response status} - * @param exceptionFunction the function that returns the exception - * @return the {@link ExchangeFilterFunction} that returns the given exception if the predicate - * matches + * @param statusPredicate the predicate to check the HTTP status with + * @param exceptionFunction the function that to create the exception + * @return the filter to generate an error signal */ public static ExchangeFilterFunction statusError(Predicate statusPredicate, Function exceptionFunction) { @@ -137,20 +128,16 @@ public abstract class ExchangeFilterFunctions { Assert.notNull(exceptionFunction, "Function must not be null"); return ExchangeFilterFunction.ofResponseProcessor( - clientResponse -> { - if (statusPredicate.test(clientResponse.statusCode())) { - return Mono.error(exceptionFunction.apply(clientResponse)); - } - else { - return Mono.just(clientResponse); - } - } + response -> statusPredicate.test(response.statusCode()) ? + Mono.error(exceptionFunction.apply(response)) : + Mono.just(response) ); } /** - * Represents a combination of username and password, as used by {@link #basicAuthentication()}. + * Stores user and password for HTTP basic authentication. + * @see #basicAuthentication() * @see #basicAuthenticationCredentials(String, String) */ public static final class Credentials { @@ -159,6 +146,7 @@ public abstract class ExchangeFilterFunctions { private final String password; + /** * Create a new {@code Credentials} instance with the given username and password. * @param username the username @@ -171,23 +159,25 @@ public abstract class ExchangeFilterFunctions { this.password = password; } + /** - * Return a consumer that stores the given username and password in the - * {@linkplain ClientRequest.Builder#attributes(java.util.function.Consumer) request - * attributes} as a {@code Credentials} object. - * @param username the username + * Return a {@literal Consumer} that stores the given user and password + * as a request attribute of type {@code Credentials} that is in turn + * used by {@link ExchangeFilterFunctions#basicAuthentication()}. + * @param user the user * @param password the password - * @return a consumer that adds the given credentials to the attribute map + * @return a consumer that can be passed into + * {@linkplain ClientRequest.Builder#attributes(java.util.function.Consumer)} * @see ClientRequest.Builder#attributes(java.util.function.Consumer) * @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE */ - public static Consumer> basicAuthenticationCredentials(String username, String password) { - Credentials credentials = new Credentials(username, password); - checkIllegalCharacters(username, password); - - return attributes -> attributes.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials); + public static Consumer> basicAuthenticationCredentials(String user, String password) { + Credentials credentials = new Credentials(user, password); + checkIllegalCharacters(user, password); + return map -> map.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials); } + @Override public boolean equals(Object o) { if (this == o) { @@ -197,7 +187,6 @@ public abstract class ExchangeFilterFunctions { Credentials other = (Credentials) o; return this.username.equals(other.username) && this.password.equals(other.password); - } return false; } @@ -206,7 +195,6 @@ public abstract class ExchangeFilterFunctions { public int hashCode() { return 31 * this.username.hashCode() + this.password.hashCode(); } - } }