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}