001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.reactive.function.client;
018
019import java.nio.charset.Charset;
020import java.util.Map;
021import java.util.function.Consumer;
022import java.util.function.Function;
023import java.util.function.Predicate;
024
025import reactor.core.publisher.Flux;
026import reactor.core.publisher.Mono;
027
028import org.springframework.core.io.buffer.DataBuffer;
029import org.springframework.core.io.buffer.DataBufferUtils;
030import org.springframework.http.HttpHeaders;
031import org.springframework.http.HttpStatus;
032import org.springframework.lang.Nullable;
033import org.springframework.util.Assert;
034import org.springframework.web.reactive.function.BodyExtractors;
035
036/**
037 * Static factory methods providing access to built-in implementations of
038 * {@link ExchangeFilterFunction} for basic authentication, error handling, etc.
039 *
040 * @author Rob Winch
041 * @author Arjen Poutsma
042 * @author Sam Brannen
043 * @since 5.0
044 */
045public abstract class ExchangeFilterFunctions {
046
047        /**
048         * Name of the request attribute with {@link Credentials} for {@link #basicAuthentication()}.
049         * @deprecated as of Spring 5.1 in favor of using
050         * {@link HttpHeaders#setBasicAuth(String, String)} while building the request.
051         */
052        @Deprecated
053        public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE =
054                        ExchangeFilterFunctions.class.getName() + ".basicAuthenticationCredentials";
055
056
057        /**
058         * Consume up to the specified number of bytes from the response body and
059         * cancel if any more data arrives.
060         * <p>Internally delegates to {@link DataBufferUtils#takeUntilByteCount}.
061         * @param maxByteCount the limit as number of bytes
062         * @return the filter to limit the response size with
063         * @since 5.1
064         */
065        public static ExchangeFilterFunction limitResponseSize(long maxByteCount) {
066                return (request, next) ->
067                                next.exchange(request).map(response -> {
068                                        Flux<DataBuffer> body = response.body(BodyExtractors.toDataBuffers());
069                                        body = DataBufferUtils.takeUntilByteCount(body, maxByteCount);
070                                        return ClientResponse.from(response).body(body).build();
071                                });
072        }
073
074        /**
075         * Return a filter that generates an error signal when the given
076         * {@link HttpStatus} predicate matches.
077         * @param statusPredicate the predicate to check the HTTP status with
078         * @param exceptionFunction the function that to create the exception
079         * @return the filter to generate an error signal
080         */
081        public static ExchangeFilterFunction statusError(Predicate<HttpStatus> statusPredicate,
082                        Function<ClientResponse, ? extends Throwable> exceptionFunction) {
083
084                Assert.notNull(statusPredicate, "Predicate must not be null");
085                Assert.notNull(exceptionFunction, "Function must not be null");
086
087                return ExchangeFilterFunction.ofResponseProcessor(
088                                response -> (statusPredicate.test(response.statusCode()) ?
089                                                Mono.error(exceptionFunction.apply(response)) : Mono.just(response)));
090        }
091
092        /**
093         * Return a filter that applies HTTP Basic Authentication to the request
094         * headers via {@link HttpHeaders#setBasicAuth(String)} and
095         * {@link HttpHeaders#encodeBasicAuth(String, String, Charset)}.
096         * @param username the username
097         * @param password the password
098         * @return the filter to add authentication headers with
099         * @see HttpHeaders#encodeBasicAuth(String, String, Charset)
100         * @see HttpHeaders#setBasicAuth(String)
101         */
102        public static ExchangeFilterFunction basicAuthentication(String username, String password) {
103                String encodedCredentials = HttpHeaders.encodeBasicAuth(username, password, null);
104                return (request, next) ->
105                                next.exchange(ClientRequest.from(request)
106                                                .headers(headers -> headers.setBasicAuth(encodedCredentials))
107                                                .build());
108        }
109
110        /**
111         * Variant of {@link #basicAuthentication(String, String)} that looks up
112         * the {@link Credentials Credentials} in a
113         * {@link #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE request attribute}.
114         * @return the filter to use
115         * @see Credentials
116         * @deprecated as of Spring 5.1 in favor of using
117         * {@link HttpHeaders#setBasicAuth(String, String)} while building the request.
118         */
119        @Deprecated
120        public static ExchangeFilterFunction basicAuthentication() {
121                return (request, next) -> {
122                        Object attr = request.attributes().get(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE);
123                        if (attr instanceof Credentials) {
124                                Credentials cred = (Credentials) attr;
125                                return next.exchange(ClientRequest.from(request)
126                                                .headers(headers -> headers.setBasicAuth(cred.username, cred.password))
127                                                .build());
128                        }
129                        else {
130                                return next.exchange(request);
131                        }
132                };
133        }
134
135
136        /**
137         * Stores username and password for HTTP basic authentication.
138         * @deprecated as of Spring 5.1 in favor of using
139         * {@link HttpHeaders#setBasicAuth(String, String)} while building the request.
140         */
141        @Deprecated
142        public static final class Credentials {
143
144                private final String username;
145
146                private final String password;
147
148                /**
149                 * Create a new {@code Credentials} instance with the given username and password.
150                 * @param username the username
151                 * @param password the password
152                 */
153                public Credentials(String username, String password) {
154                        Assert.notNull(username, "'username' must not be null");
155                        Assert.notNull(password, "'password' must not be null");
156                        this.username = username;
157                        this.password = password;
158                }
159
160                /**
161                 * Return a {@literal Consumer} that stores the given username and password
162                 * as a request attribute of type {@code Credentials} that is in turn
163                 * used by {@link ExchangeFilterFunctions#basicAuthentication()}.
164                 * @param username the username
165                 * @param password the password
166                 * @return a consumer that can be passed into
167                 * {@linkplain ClientRequest.Builder#attributes(java.util.function.Consumer)}
168                 * @see ClientRequest.Builder#attributes(java.util.function.Consumer)
169                 * @see #BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE
170                 */
171                public static Consumer<Map<String, Object>> basicAuthenticationCredentials(String username, String password) {
172                        Credentials credentials = new Credentials(username, password);
173                        return (map -> map.put(BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE, credentials));
174                }
175
176                @Override
177                public boolean equals(@Nullable Object other) {
178                        if (this == other) {
179                                return true;
180                        }
181                        if (!(other instanceof Credentials)) {
182                                return false;
183                        }
184                        Credentials otherCred = (Credentials) other;
185                        return (this.username.equals(otherCred.username) && this.password.equals(otherCred.password));
186                }
187
188                @Override
189                public int hashCode() {
190                        return 31 * this.username.hashCode() + this.password.hashCode();
191                }
192        }
193
194}