HttpClient4.5X使用-集成微服务

                            HttpClient4.5X使用-集成微服务

 

      1.什么是HttpClient

 

HTTP 协议可能是现在 Internet 上使用得最多、最重要的协议了,越来越多的 Java 应用程序需要直接通过 HTTP 协议来访问网络资源。虽然在 JDK 的 java net包中已经提供了访问 HTTP 协议的基本功能,但是对于大部分应用程序来说,JDK 库本身提供的功能还不够丰富和灵活。HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。HttpClient 已经应用在很多的项目中,比如 Apache Jakarta 上很著名的另外两个开源项目 Cactus 和 HTMLUnit 都使用了 HttpClient。现在HttpClient最新版本为 HttpClient 4.5 .6(官网)

        实际应用中我们通常使用在复杂soap协议类型的调用。Springboot一般推荐有RestTemplate 。  

        2. 整合 HttpClient

           1.配置类,主要配置httpClient 连接池的信息

@ConfigurationProperties(prefix = HttpClientProperties.PREFIX)
public class HttpClientProperties {
    public static final String PREFIX = "http.config";

    //默认连接池最大连接数
    private Integer httpMaxTotal = 300;

    //默认整个连接池默认最大连接数
    private Integer httpDefaultMaxPerRoute = 100;

    //设置请求超时 默认30s
    private Integer httpConnectTimeout = 30 * 1000;


    //设置请求响应超时时间  默认5min
    private Integer getHttpReadTimeout = 5 * 60 * 1000;


    //设置从连接池获取中获取到连接最长时间
    private Integer httpConnectionRequestTimeout = 500;

    //设置数据传输的最长时间
    private Integer httpSocketTimeout = 5 * 60 * 1000;

    //设置默认请求类型http 、https
    private String hostType ="http";

    public Integer getHttpMaxTotal() {
        return httpMaxTotal;
    }

    public void setHttpMaxTotal(Integer httpMaxTotal) {
        this.httpMaxTotal = httpMaxTotal;
    }

    public Integer getHttpDefaultMaxPerRoute() {
        return httpDefaultMaxPerRoute;
    }

    public void setHttpDefaultMaxPerRoute(Integer httpDefaultMaxPerRoute) {
        this.httpDefaultMaxPerRoute = httpDefaultMaxPerRoute;
    }

    public Integer getHttpConnectTimeout() {
        return httpConnectTimeout;
    }

    public void setHttpConnectTimeout(Integer httpConnectTimeout) {
        this.httpConnectTimeout = httpConnectTimeout;
    }

    public Integer getGetHttpReadTimeout() {
        return getHttpReadTimeout;
    }

    public void setGetHttpReadTimeout(Integer getHttpReadTimeout) {
        this.getHttpReadTimeout = getHttpReadTimeout;
    }

    public Integer getHttpConnectionRequestTimeout() {
        return httpConnectionRequestTimeout;
    }

    public void setHttpConnectionRequestTimeout(Integer httpConnectionRequestTimeout) {
        this.httpConnectionRequestTimeout = httpConnectionRequestTimeout;
    }

    public Integer getHttpSocketTimeout() {
        return httpSocketTimeout;
    }

    public void setHttpSocketTimeout(Integer httpSocketTimeout) {
        this.httpSocketTimeout = httpSocketTimeout;
    }

    public String getHostType() {
        return hostType;
    }

    public void setHostType(String hostType) {
        this.hostType = hostType;
    }
}

      2.初始化连接池信息

@Configuration
@ConditionalOnClass(HttpClientConfig.class)
@EnableConfigurationProperties(HttpClientProperties.class)
public class HttpClientConfig {

    @Autowired
    HttpClientProperties httpClientProperties;
    @Autowired
    private PoolingHttpClientConnectionManager manager;



    @Bean
    public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
        // 最大连接数
        poolingHttpClientConnectionManager.setMaxTotal(httpClientProperties.getHttpMaxTotal());
        // 每个主机的最大并发数
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(httpClientProperties.getHttpDefaultMaxPerRoute());
        return poolingHttpClientConnectionManager;
    }


    @Bean
    public IdleConnectionEvictor idleConnectionEvictor(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager ) {
        // 定期清理无效连接
        return new IdleConnectionEvictor(poolingHttpClientConnectionManager, 1L, TimeUnit.HOURS);
    }

    @Bean
    public RequestConfig requestConfig() {
        // 请求配置
        // 创建连接的最长时间
        return RequestConfig.custom().setConnectTimeout(httpClientProperties.getHttpConnectTimeout())
                // 从连接池中获取到连接的最长时间
                .setConnectionRequestTimeout(httpClientProperties.getHttpConnectTimeout())
                // 数据传输的最长时间
                .setSocketTimeout(httpClientProperties.getHttpSocketTimeout())
                .build();
    }

    /**
     * 通过httpClients直接获取
     * @param requestConfig 配置信息
     * @return
     */
    @Bean
    @Scope("prototype") //此处是多例模式,因为考虑连接池使用都会手动关闭,同时单例会存在未知问题
    public CloseableHttpClient closeableHttpClient(RequestConfig requestConfig) {
        return HttpClients.custom().setConnectionManager(this.manager).setDefaultRequestConfig(requestConfig).build();
    }


    /**
     * 通过HttpClientBuilder 间接获取
     * @param requestConfig 配置信息
     * @return
     */
    @Bean
    public HttpClientBuilder httpClientBuilder(PoolingHttpClientConnectionManager poolingHttpClientConnectionManager, RequestConfig requestConfig){
        return HttpClientBuilder.create().setConnectionManager(poolingHttpClientConnectionManager).setDefaultRequestConfig(requestConfig);
    }


}

         3.httpclientUtil

@Component
public class HttpClientUtil {

    @Autowired
    private CloseableHttpClient httpClient;


    /**
     * get请求用于普通接口调用
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @return
     * @throws Exception
     */
    public String doGet(String url,
                        Map<String, String> headers,
                        Map<String, String> params) {
//        //创建一个默认可关闭的httpclient对象
        //     CloseableHttpClient httpClient = HttpClients.createDefault();
        //定义httpResponse 对象
        CloseableHttpResponse response = null;
        //反馈信息
        String resultMsg = null;
        try {
            //设置请求地址
            URIBuilder builder = new URIBuilder(url);
            //拼接地址
            if (!CollectionUtils.isEmpty(params)) {
                params.forEach((k, v) -> {
                    builder.addParameter(k, v);
                });
            }
            //设置请求头
            HttpGet httpGet = new HttpGet(builder.build());
            if (!CollectionUtils.isEmpty(headers)) {
                headers.entrySet().stream().map(e -> {
                    httpGet.addHeader(e.getKey(), e.getValue());
                    return httpGet;
                });
            }
            // 执行请求
            response = httpClient.execute(httpGet);
            // 判断返回状态为200则给返回值赋值
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //构造反馈
                resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            //构造反馈SRM
            e.printStackTrace();
        } finally {
            //连接池关闭
            httpClientCloseResponse(httpClient, response);
        }
        return resultMsg;
    }

    /**
     * 无请求体post请求 post form
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数
     * @return
     * @throws Exception
     */
    public String doPost(String url,
                         Map<String, String> headers,
                         Map<String, String> params) {
        //创建一个默认可关闭的httpclient对象
        //      CloseableHttpClient httpClient = HttpClients.createDefault();
        //定义httpResponse 对象
        CloseableHttpResponse response = null;
        //反馈信息
        String resultMsg = null;
        try {
            //设置请求地址
            URIBuilder builder = new URIBuilder(url);
            //拼接地址
            if (!CollectionUtils.isEmpty(params)) {
                params.forEach((k, v) -> {
                    builder.addParameter(k, v);
                });
            }
            //设置请求头
            HttpPost httpPost = new HttpPost(builder.build());
            if (!CollectionUtils.isEmpty(headers)) {
                headers.forEach((k, v) -> {
                    httpPost.addHeader(k, v);
                });
            }
            // 执行请求
            response = httpClient.execute(httpPost);
            // 判断返回状态为200则给返回值赋值
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //构造反馈
                resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            //构造反馈SRM
            e.printStackTrace();
        } finally {
            //连接池关闭
            httpClientCloseResponse(httpClient, response);
        }
        return resultMsg;
    }

    /**
     * 请求体post请求 post
     *
     * @param url     请求地址
     * @param headers 请求头
     * @param params  参数信息
     * @param body    请求体
     * @return 反馈结果
     * @throws Exception
     */
    public String doPost(String url,
                         Map<String, String> headers,
                         Map<String, String> params,
                         String body) {
        //创建一个默认可关闭的httpclient对象
        //   CloseableHttpClient httpClient = HttpClients.createDefault();
        //定义httpResponse 对象
        CloseableHttpResponse response = null;
        //反馈信息
        String resultMsg = null;
        try {
            //设置请求地址
            URIBuilder builder = new URIBuilder(url);
            //拼接地址
            if (!CollectionUtils.isEmpty(params)) {
                List<NameValuePair> nameValuePairs = params.entrySet().stream().map(entry -> new BasicNameValuePair(entry.getKey(), entry.getValue())).collect(Collectors.toList());
                builder.setParameters(nameValuePairs);
            }
            //设置请求头
            HttpPost httpPost = new HttpPost(builder.build());
            if (!CollectionUtils.isEmpty(headers)) {
                headers.forEach((k, v) -> {
                    httpPost.addHeader(k, v);
                });
            }
            //设置请求体
            if (StringUtils.isNotBlank(body)) {
                httpPost.setEntity(new StringEntity(body, "utf-8"));
            }
            // 执行请求
            response = httpClient.execute(httpPost);
            // 判断返回状态为200则给返回值赋值
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                //构造反馈
                resultMsg = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
            //构造反馈SRM
            e.printStackTrace();
        } finally {
            //连接池关闭
            httpClientCloseResponse(httpClient, response);
        }
        return resultMsg;

    }


    /**
     * 构建请求的 url
     *
     * @param uri
     * @param querys
     * @return
     * @throws UnsupportedEncodingException
     */
    private String buildUrl(String uri, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(uri);
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }
        return sbUrl.toString();
    }


    /**
     * 将结果转换成JSONObject
     *
     * @param httpResponse
     * @return
     * @throws IOException
     */
    public JSONObject getJson(HttpResponse httpResponse) throws IOException {
        HttpEntity entity = httpResponse.getEntity();
        String resp = EntityUtils.toString(entity, "UTF-8");
        EntityUtils.consume(entity);
        return JSON.parseObject(resp);
    }

    /**
     * 关闭连接池反馈
     *
     * @param httpResponse
     */
    public void httpClientCloseResponse(CloseableHttpClient httpClient, CloseableHttpResponse httpResponse) {
        try {
            if (httpResponse != null) {
                httpResponse.close();
            }
            httpClient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

  3.为什么使用HttpClient并且使用连接池

           日常调用时候我们经常碰见这样的问题:
Exception in thread "main" javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.ssl.Alerts.getSSLException(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
	at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
	at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
	at sun.security.ssl.ClientHandshaker.serverCertificate(Unknown Source)
	at sun.security.ssl.ClientHandshaker.processMessage(Unknown Source)
	at sun.security.ssl.Handshaker.processLoop(Unknown Source)
	at sun.security.ssl.Handshaker.process_record(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(Unknown Source)
	at com.hx.net.Test3.main(Test3.java:21)
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.validator.PKIXValidator.doBuild(Unknown Source)
	at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)
	at sun.security.validator.Validator.validate(Unknown Source)
	at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)
	at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)
	at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)
	... 12 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
	at sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source)
	at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)
	at java.security.cert.CertPathBuilder.build(Unknown Source)
	... 18 more

  证书认证问题SSL,网上处理方式参差不齐。因此通过 查看PoolingHttpClientConnectionManager实例化源码我们发现

 private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
        return RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory())
                .build();
    }

    public PoolingHttpClientConnectionManager() {
        this(getDefaultRegistry());
    }

  httpclient 连接池已经对ssl证书初始化注册是有非常良好的集成。

 
posted @ 2020-11-20 14:10  咸鱼皮蛋  阅读(365)  评论(0编辑  收藏  举报