天天看点

HttpClientFactory单例模式设计,方便以后直接使用

httpClient对象,一般推荐单例模式,这是一个可配置性很强的对象,同时也说明初始化需要很多复杂流程,初始化成本很高,以下为一个单例设计,以后有需要,不断完善注释信息,方便以后直接使用:

/**
 * @author yaoqiang
 * @date 2020/9/23 10:07
 * @description 对 {@link HttpClientWrapper} 做修改,不改动原类,重新编写
 * @see HttpClientWrapper
 */
public class HttpClientFactory {

    /**
     *
     * 单例设计  延迟初始化占位
     *
     * @return  单例 httpClient
     */
    public static CloseableHttpClient getInstance() {
        return HttpClientHolder.httpClient;
    }

    private static class HttpClientHolder {

        /**
         * 从连接池获取到连接的超时时间,如果是非连接池的话,该参数暂时没有发现有什么用处
         *
         * 连接池数量足够大,时间可以进一步压缩
         *
         */
        private final static int CONNECTION_REQUEST_TIMEOUT = 9000;

        /**
         * 获取链接超时时间,即使失败,也和服务器没有关系,因为没有连上,有可能是网络问题
         * 也就是,建立链接的最大时间,也就是完成三次握手,建立tcp链接所用的时间。
         *
         * 尽可能小一点,链接不上,直接重试,减少程序阻塞时间
         */
        private final static int CONNECT_TIMEOUT = 3000;

        /**
         *  指的是服务器响应超时 时间,和服务器有关。
         *  重点时图片上传,涉及到大容量,设置大一点增加容错率。
         *
         *  read()方法阻塞的最大时间,也是,两个packet的间隔时间。
         *
         *  如果链接taobao接口,
         *  这个失败会被记录进失败概率,如果失败概率过大,很容易就被限流,限时,这真的很坑
         *
         *  大一点,为对方服务器减压,如果次异常抛得过于频繁,可能会被拉入黑名单。
         *  链接上结果都不要,就开始重复请求,服务器压力大
         *
         */
        private final static int SOCKET_TIMEOUT = 5000;


        public final static CloseableHttpClient httpClient;

        static {

            httpClient = HttpClients
                    .custom()
                    .setRetryHandler(requestRetryHandler())
                    .setDefaultRequestConfig(requestConfig())
                    .setSSLSocketFactory(sslConnectionSocketFactory())
                    .setConnectionManager(poolingHttpClientConnectionManager())
                    // 设置为true,在client close的时候,不会执行一系列官方的关闭动作
                    //.setConnectionManagerShared(true)
                    .build();
        }


        /**
         * 测出超时重试机制为了防止超时不生效而设置
         * 如果直接放回false,不重试
         * 这里会根据情况进行判断是否重试
         */
        public static HttpRequestRetryHandler requestRetryHandler() {

            return (exception, executionCount, context) -> {

                if (executionCount >= 3) {// 如果已经重试了3次,就放弃
                    return false;
                }
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                if (exception instanceof InterruptedIOException) {
                    return true;
                }
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                if (exception instanceof SSLException) {
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的,就再次尝试
                return !(request instanceof HttpEntityEnclosingRequest);
            };

        }


        /**
         * 默认请求配置
         *
         * @return
         */
        private static RequestConfig requestConfig() {
            // 创建Http请求配置参数
            return RequestConfig.custom()
                    // 请求超时时间
                    .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
                    //  获取连接超时时间
                    .setConnectTimeout(CONNECT_TIMEOUT)
                    // 响应超时时间
                    .setSocketTimeout(SOCKET_TIMEOUT)
                    .build();
        }


        /**
         * 连接池管理
         *
         * @return
         */
        private static PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
            Registry<ConnectionSocketFactory> registry = RegistryBuilder
                    .<ConnectionSocketFactory>create()
                    .register("http", new PlainConnectionSocketFactory())
                    .register("https", sslConnectionSocketFactory())
                    .build();
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
            //设置连接池的最大连接数,即整个池子的大小;
            cm.setMaxTotal(60);
            //设置每一个路由的最大连接数,
            // 这里的路由是指IP+PORT,
            // 例如连接池大小(MaxTotal)设置为300,路由连接数设置为200(DefaultMaxPerRoute),
            // 对于www.a.com与www.b.com两个路由来说,发起服务的主机连接到每个路由的最大连接数(并发数)不能超过200,
            // 两个路由的总连接数不能超过300。
            cm.setDefaultMaxPerRoute(40);
            return cm;
        }

        /**
         * 跳过验证
         *
         * @return
         */
        private static SSLConnectionSocketFactory sslConnectionSocketFactory() {
            SSLContextBuilder builder = new SSLContextBuilder();
            try {
                return new SSLConnectionSocketFactory(builder.build(), new String[]{"TLSv1"}, null, NoopHostnameVerifier.INSTANCE);
            } catch (NoSuchAlgorithmException | KeyManagementException e) {
                e.printStackTrace();
            }
            throw new RuntimeException("创建 SSLConnectionSocketFactory 失败");
        }


    }

}