WebFlux实战——WebClent简单实用的工具类封装

3 篇文章 0 订阅
2 篇文章 0 订阅

简述

WebClientWebFlux框架中重要的Http请求框架。同时也是Spring官方的Http请求工具,相当于SpringMVC框架中的RestTemplate。关于这个工具的详情,大家可参考下方的官方文档学习具体的使用方法
《WebClient》
在日常使用中,WebClient通常是使用WebClient.Builder来完成构建的。为了方便日常使用,笔者将日常使用到的场景封装了一个工具类,其中包含了获取WebClient.Builder的部分,和方便提取和转换一些信息(如Cookies等)的工具方法。

具体方法

获取WebClient.Builder类的方法集合

在这个部分,分别通过几种不同的方法获取了包含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(
                        /*
                        这里使用ConnectionProvider.builder().build()进行构建
                        构建出的PooledConnectionProvider用于HttpClient的连接池
                        注意,不要使用HttpClient.create()方法,大批量调用会导致OOM的问题
                         */
                        ConnectionProvider.builder(Thread.currentThread().getStackTrace()[2].getClass().getSimpleName() + "_" + proxyDO.getProxyContentStr()).build()
                )
                //重新定向开启
                .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(
                        /*
                        这里使用ConnectionProvider.builder().build()进行构建
                        构建出的PooledConnectionProvider用于HttpClient的连接池
                        注意,不要使用HttpClient.create()方法,大批量调用会导致OOM的问题
                         */
                        ConnectionProvider.builder(Thread.currentThread().getStackTrace()[2].getClass().getSimpleName() + "_" + proxyDO.getProxyContentStr()).build()
                )
                //重新定向开启
                .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);
                            }
                        }
                ))
        ));
    }

其它工具方法

ResponseCookie转MultiValueMap<String, String> 方法

当我们做一些http请求时,可能需要进行cookie复用,即上一个请求返回的cookie需要在下一个请求中带上。
但是WebClient的Response设计中有个较为纠结的地方,即:WebClient返回的ClientRespons中能够拿到的是MultiValueMap<String, ResponseCookie>实体,而在RequestHeadersSpec#cookies(即在构建http请求时指定cookie的地方)方法中,需要给定的入参是一个MultiValueMap<String, String>>
如上的场景催生了下面的这个方法,即完成了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.ly.train.sb.common.utils;

import com.ly.train.sb.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;
    }


}
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术流奶爸奶爸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值