WebClient是WebFlux框架中重要的Http请求框架。同时也是Spring官方的Http请求工具,相当于SpringMVC框架中的RestTemplate。关于这个工具的详情,大家可参考下方的官方文档学习具体的使用方法
《WebClient》
在日常使用中,WebClient通常是使用WebClient.Builder来完成构建的。为了方便日常使用,笔者将日常使用到的场景封装了一个工具类,其中包含了获取WebClient.Builder的部分,和方便提取和转换一些信息(如Cookies等)的工具方法。
在这个部分,分别通过几种不同的方法获取了包含SSH认证和代理等信息的WebClient.Builder的方法
同时,下列方法中默认给Builder附上了一个超时时间配置,避免Socket异常导致请求被hung住。
/**
* 给了一个默认的WebClient,这个Client里面配置了默认请求超时时间
*
* @return 返回一个带超时时间的{@link WebClient.Builder}
*/
public static WebClient.Builder getDefaultWebClientBuilder() {
return getWebClientBuilder(DEFAULT_REQUEST_TIMEOUT);
}
/**
* [基础创建方法]
* 给了一个默认的WebClient,这个Client里面配置了指定了请求超时时间
*
* @param requestTimeOut 请求超时时间
* @return 返回一个带超时时间的{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilder(Duration requestTimeOut) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//重新定向开启
.followRedirect(true)
.responseTimeout(requestTimeOut)));
}
/**
* 给到一个带默认超时时间,并带有不校验任何SSL整数的WebClient
*
* @return 返回一个带默认超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust() {
return getWebClientBuilderWithSslTrust(DEFAULT_REQUEST_TIMEOUT);
}
/**
* 给到一个带超时时间,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust(Duration requestTimeOut) {
return getWebClientBuilderWithSslTrust(requestTimeOut, false);
}
/**
* [基础创建方法]
* 给到一个带超时时间,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param compressionEnabled 开启压缩?默认关闭
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust(Duration requestTimeOut, boolean compressionEnabled) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//重新定向开启
.followRedirect(true)
//这里注入了一个抛弃一切SSL认证的sslContext
.secure(sslContextSpec -> sslContextSpec.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)))
.responseTimeout(requestTimeOut)
.compress(compressionEnabled)
));
}
/**
* 给到一个带超时时间,带代理,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param proxyDO 代理实体
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrustAndPolicy(Duration requestTimeOut, ProxyDO proxyDO) {
return getWebClientBuilderWithSslTrustAndPolicy(requestTimeOut, proxyDO, false);
}
/**
* [基础创建方法]
* 给到一个带超时时间,带代理,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param proxyDO 代理实体
* @param compressionEnabled 开启压缩?默认关闭
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrustAndPolicy(Duration requestTimeOut, ProxyDO proxyDO, boolean compressionEnabled) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//这里注入了一个抛弃一切SSL认证的sslContext
.secure(sslContextSpec -> sslContextSpec.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)))
.responseTimeout(requestTimeOut)
.compress(compressionEnabled)
//重新定向开启
.followRedirect(true)
.tcpConfiguration(tcpClient -> tcpClient.proxy(
p -> {
ProxyProvider.Builder pb = p.type(ProxyProvider.Proxy.HTTP)
.address(InetSocketAddress.createUnresolved(proxyDO.getServiceAddress(), Integer.parseInt(proxyDO.getPort())));
if (StringUtils.isNotEmpty(proxyDO.getUserName())) {
pb.username(proxyDO.getUserName())
.password(v -> proxyDO.getPassword());
}
Long proxyTimeOutMillis = proxyDO.getProxyTimeOutMillis();
if (proxyTimeOutMillis != null && proxyTimeOutMillis > 0) {
pb.connectTimeoutMillis(proxyTimeOutMillis);
} else {
pb.connectTimeoutMillis(DEFAULT_PROXY_TIMEOUT_MILLIS);
}
}
))
));
}
当我们做一些http请求时,可能需要进行cookie复用,即上一个请求返回的cookie需要在下一个请求中带上。
但是WebClient的Response设计中有个较为纠结的地方,即:WebClient返回的ClientRespons中能够拿到的是MultiValueMap
如上的场景催生了下面的这个方法,即完成了cookies转换的操作
/**
* 将http相应中的Cookie转换为用于http请求中的cookie
* 方法中仅进行简单转换,不会对Cookie有效期等进行判断
*
* @param responseCookie 需要被转换的cookie
* @return 返回可以用于请求的Cookies
*/
public static MultiValueMap<String, String> transformResponseCookiesToRequestCookies(MultiValueMap<String, ResponseCookie> responseCookie) {
MultiValueMap<String, String> ret = new LinkedMultiValueMap<>();
if (responseCookie == null || responseCookie.size() == 0) {
return ret;
}
for (Map.Entry<String, List<ResponseCookie>> entity : responseCookie.entrySet()) {
String key = entity.getKey();
List<ResponseCookie> value = entity.getValue();
int size = value.size();
if (size == 0) {
continue;
}
List<String> cookies = new ArrayList<>(size);
for (ResponseCookie cookie : value) {
cookies.add(cookie.getValue());
}
ret.addAll(key, cookies);
}
return ret;
}
为了方便大家使用,我把完整的代码贴在此处
package com.demo.common.utils;
import com.demo.model.ProxyDO;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.netty.http.client.HttpClient;
import reactor.netty.tcp.ProxyProvider;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* WebClientUtils
*
* @author John Chen
* @since 2020/12/23
*/
public class WebClientUtils {
/**
* 默认3分钟超时时间
*/
private final static Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofMinutes(3L);
/**
* 默认代理超时时间
*/
private final static Long DEFAULT_PROXY_TIMEOUT_MILLIS = DEFAULT_REQUEST_TIMEOUT.toMillis();
//region 生成WebClient.Builder的方法
/**
* 给了一个默认的WebClient,这个Client里面配置了默认请求超时时间
*
* @return 返回一个带超时时间的{@link WebClient.Builder}
*/
public static WebClient.Builder getDefaultWebClientBuilder() {
return getWebClientBuilder(DEFAULT_REQUEST_TIMEOUT);
}
/**
* [基础创建方法]
* 给了一个默认的WebClient,这个Client里面配置了指定了请求超时时间
*
* @param requestTimeOut 请求超时时间
* @return 返回一个带超时时间的{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilder(Duration requestTimeOut) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//重新定向开启
.followRedirect(true)
.responseTimeout(requestTimeOut)));
}
/**
* 给到一个带默认超时时间,并带有不校验任何SSL整数的WebClient
*
* @return 返回一个带默认超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust() {
return getWebClientBuilderWithSslTrust(DEFAULT_REQUEST_TIMEOUT);
}
/**
* 给到一个带超时时间,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust(Duration requestTimeOut) {
return getWebClientBuilderWithSslTrust(requestTimeOut, false);
}
/**
* [基础创建方法]
* 给到一个带超时时间,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param compressionEnabled 开启压缩?默认关闭
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrust(Duration requestTimeOut, boolean compressionEnabled) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//重新定向开启
.followRedirect(true)
//这里注入了一个抛弃一切SSL认证的sslContext
.secure(sslContextSpec -> sslContextSpec.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)))
.responseTimeout(requestTimeOut)
.compress(compressionEnabled)
));
}
/**
* 给到一个带超时时间,带代理,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param proxyDO 代理实体
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrustAndPolicy(Duration requestTimeOut, ProxyDO proxyDO) {
return getWebClientBuilderWithSslTrustAndPolicy(requestTimeOut, proxyDO, false);
}
/**
* [基础创建方法]
* 给到一个带超时时间,带代理,并带有不校验任何SSL整数的WebClient
*
* @param requestTimeOut 超时时间
* @param proxyDO 代理实体
* @param compressionEnabled 开启压缩?默认关闭
* @return 返回一个带超时时间和默认全局信任的SSL请求校验器{@link WebClient.Builder}
*/
public static WebClient.Builder getWebClientBuilderWithSslTrustAndPolicy(Duration requestTimeOut, ProxyDO proxyDO, boolean compressionEnabled) {
if (requestTimeOut == null) {
requestTimeOut = DEFAULT_REQUEST_TIMEOUT;
}
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient
.create()
//这里注入了一个抛弃一切SSL认证的sslContext
.secure(sslContextSpec -> sslContextSpec.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE)))
.responseTimeout(requestTimeOut)
.compress(compressionEnabled)
//重新定向开启
.followRedirect(true)
.tcpConfiguration(tcpClient -> tcpClient.proxy(
p -> {
ProxyProvider.Builder pb = p.type(ProxyProvider.Proxy.HTTP)
.address(InetSocketAddress.createUnresolved(proxyDO.getServiceAddress(), Integer.parseInt(proxyDO.getPort())));
if (StringUtils.isNotEmpty(proxyDO.getUserName())) {
pb.username(proxyDO.getUserName())
.password(v -> proxyDO.getPassword());
}
Long proxyTimeOutMillis = proxyDO.getProxyTimeOutMillis();
if (proxyTimeOutMillis != null && proxyTimeOutMillis > 0) {
pb.connectTimeoutMillis(proxyTimeOutMillis);
} else {
pb.connectTimeoutMillis(DEFAULT_PROXY_TIMEOUT_MILLIS);
}
}
))
));
}
//endregion
/**
* 将http相应中的Cookie转换为用于http请求中的cookie
* 方法中仅进行简单转换,不会对Cookie有效期等进行判断
*
* @param responseCookie 需要被转换的cookie
* @return 返回可以用于请求的Cookies
*/
public static MultiValueMap<String, String> transformResponseCookiesToRequestCookies(MultiValueMap<String, ResponseCookie> responseCookie) {
MultiValueMap<String, String> ret = new LinkedMultiValueMap<>();
if (responseCookie == null || responseCookie.size() == 0) {
return ret;
}
for (Map.Entry<String, List<ResponseCookie>> entity : responseCookie.entrySet()) {
String key = entity.getKey();
List<ResponseCookie> value = entity.getValue();
int size = value.size();
if (size == 0) {
continue;
}
List<String> cookies = new ArrayList<>(size);
for (ResponseCookie cookie : value) {
cookies.add(cookie.getValue());
}
ret.addAll(key, cookies);
}
return ret;
}
}