springboot2.X集成HttpClient 发送HTTPS 请求

Posted 陈先生的小板凳

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot2.X集成HttpClient 发送HTTPS 请求相关的知识,希望对你有一定的参考价值。

1)jar

 <!--httpclient  发送外部https/http 请求-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>

2)配置

httpclient:
  maxTotal: 300
  defaultMaxPerRoute: 100
  connectTimeout: 1000
  connectionRequestTimeout: 500
  socketTimeout: 100000000
  staleConnectionCheckEnabled: true
  #  keyStorePath: C:/Users/Administrator/Desktop/广州办映射服务器测试证书20181010/dakehu/keystore.jks #证书路径
  keyStorePath: /usr/https_cert/keystore.jks #证书路径
  keyStorepass: 123456 #证书密码`

3)编写 HttpClient 集成相应的配置

package com.bigcustomer.utils.httpUtil;

import lombok.Data;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContexts;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;

/**
 * @author :CX
 * @Date :Create in 2018/8/17 9:57
 * @Effect :
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "httpclient")
public class HttpClient {


    private Integer maxTotal;

    private Integer defaultMaxPerRoute;

    private Integer connectTimeout;

    private Integer connectionRequestTimeout;

    private Integer socketTimeout;

    private boolean staleConnectionCheckEnabled;

    //https 证书 路径
    private String keyStorePath;
    // 证书密码
    private String keyStorepass;

    @Bean(name = "sslcontext")
    public SSLContext getSslcontext() {
        SSLContext sc = null;
        FileInputStream instream = null;
        KeyStore trustStore = null;
        try {
            trustStore = KeyStore.getInstance("JKS");
            instream = new FileInputStream(new File(keyStorePath));
            trustStore.load(instream, keyStorepass.toCharArray());
            // 相信自己的CA和所有自签名的证书
            sc = SSLContexts.custom().loadKeyMaterial(trustStore, keyStorepass.toCharArray()).build();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                instream.close();
            } catch (IOException e) {
            }
        }
        return sc;
    }

    /**
     * 首先实例化一个连接池管理器,设置最大连接数、并发连接数
     *
     * @return
     */
    @Bean(name = "httpClientConnectionManager")
    public PoolingHttpClientConnectionManager getHttpClientConnectionManager(@Qualifier("sslcontext") SSLContext sslcontext) {
        // 设置协议http和https对应的处理socket链接工厂的对象
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.INSTANCE)
                .register("https", new SSLConnectionSocketFactory(sslcontext))
                .build();
        PoolingHttpClientConnectionManager httpClientConnectionManager =
                new PoolingHttpClientConnectionManager(socketFactoryRegistry);
//最大连接数
        httpClientConnectionManager.setMaxTotal(maxTotal);
//并发数
        httpClientConnectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute);
        return httpClientConnectionManager;
    }

    /**
     * 实例化连接池,设置连接池管理器。
     * 这里需要以参数形式注入上面实例化的连接池管理器
     *
     * @param httpClientConnectionManager
     * @return
     */
    @Bean(name = "httpClientBuilder")
    public HttpClientBuilder getHttpClientBuilder(@Qualifier("httpClientConnectionManager") PoolingHttpClientConnectionManager httpClientConnectionManager) {

//HttpClientBuilder中的构造方法被protected修饰,所以这里不能直接使用new来实例化一个HttpClientBuilder,可以使用HttpClientBuilder提供的静态方法create()来获取HttpClientBuilder对象
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();

        httpClientBuilder.setConnectionManager(httpClientConnectionManager);

        return httpClientBuilder;
    }

    /**
     * 注入连接池,用于获取httpClient
     *
     * @param httpClientBuilder
     * @return
     */
    @Bean
    public CloseableHttpClient getCloseableHttpClient(@Qualifier("httpClientBuilder") HttpClientBuilder httpClientBuilder) {
        return httpClientBuilder.build();
    }

    /**
     * Builder是RequestConfig的一个内部类
     * 通过RequestConfig的custom方法来获取到一个Builder对象
     * 设置builder的连接信息
     * 这里还可以设置proxy,cookieSpec等属性。有需要的话可以在此设置
     *
     * @return
     */
    @Bean(name = "builder")
    public RequestConfig.Builder getBuilder() {
        RequestConfig.Builder builder = RequestConfig.custom();
        return builder.setConnectTimeout(connectTimeout)
                .setConnectionRequestTimeout(connectionRequestTimeout)
                .setSocketTimeout(socketTimeout)
                .setStaleConnectionCheckEnabled(staleConnectionCheckEnabled);
    }

    /**
     * 使用builder构建一个RequestConfig对象
     *
     * @param builder
     * @return
     */
    @Bean(name = "requestConfig")
    public RequestConfig getRequestConfig(@Qualifier("builder") RequestConfig.Builder builder) {

        return builder.build();

    }

}

4)编写定时回收无效资源的类

package com.bigcustomer.utils.httpUtil;

import org.apache.http.conn.HttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * @author :CX
 * @Date :Create in 2018/8/17 10:00
 * @Effect : 定时回收没有使用的链接 交还给连接池
 */
@Component
public class IdleConnectionEvictor extends Thread {

    @Autowired
    private HttpClientConnectionManager httpClientConnectionManager;
    private volatile boolean shutdown;

    public IdleConnectionEvictor() {
        super();
        super.start();
    }

    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
// 关闭失效的连接
                    httpClientConnectionManager.closeExpiredConnections();
                }
            }
        } catch (InterruptedException ex) {
// 结束
        }
    }

    //关闭清理无效连接的线程
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
}

5) 工具类

package com.bigcustomer.utils.httpUtil;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.bigcustomer.configs.BaseConfig;
import com.bigcustomer.configs.KeyConfig;
import com.bigcustomer.utils.ExternalHelpUtile;
import com.bigcustomer.utils.SinUtil;
import huashitech.kissucomponent.redis.RedisUtil;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;


/**
 * @author :CX
 * @Date :Create in 2018/8/17 10:02
 * @Effect :
 */
@Component
public class HttpsUtils {
    private static Logger logger = LoggerFactory.getLogger(HttpsUtils.class);
    static CloseableHttpClient httpClient;
    static RequestConfig requestConfig;
    static CloseableHttpResponse httpResponse;

    private static BaseConfig baseConfig;
    private static ExternalHelpUtile utile;
    private static KeyConfig config;
    private static SinUtil sinUtil;
    private static RedisUtil redisUtil;
    private static String encoding = "utf-8";

    @Autowired
    public void init(KeyConfig config, BaseConfig baseConfig, RedisUtil redisUtil,
                     ExternalHelpUtile utile, SinUtil sinUtil,
                     CloseableHttpClient httpClient
                   , RequestConfig requestConfig) {
        HttpsUtils.config = config;
        HttpsUtils.baseConfig = baseConfig;
        HttpsUtils.redisUtil = redisUtil;
        HttpsUtils.utile = utile;
        HttpsUtils.sinUtil = sinUtil;
        HttpsUtils.httpClient = httpClient;
        HttpsUtils.requestConfig = requestConfig;

    }

   //https 封装方法
    private static Map<String , Object> baseSendHttpsPost(Map<String, Object> par, String url, String key) throws ClientProtocolException, IOException {
        //请求map
        LinkedHashMap<String, Object> requestMap = new LinkedHashMap<>();
        requestMap.put("mac", null);// 签名会去掉mac
        requestMap.put("agentcode", config.getAgentcode());
        requestMap.put("msgbody", par);

        // 签名并对mac 重新赋值
        String mac = sinUtil.createMac(requestMap, key);
        requestMap.put("mac", mac);
        String parStr = JSON.toJSONString(requestMap);
        logger.info("参数 :" + parStr);
        try {

            //创建post方式请求对象
            HttpPost httpPost = new HttpPost(url);

            //装填参数
            StringEntity stringEntity = null;
            if (null != par) {
                stringEntity = new StringEntity(parStr,encoding);
                httpPost.setEntity(stringEntity);
            }

            logger.info("创建请求httpsPost-URL={},params={}", url, parStr);
            //设置header信息
            //指定报文头【Content-type】、【User-Agent】
            httpPost.setHeader("Content-Type", "application/json;charset="+encoding);
//            httpPost.setHeader("Content-Length", params.length() + "");

            //执行请求操作,并拿到结果(同步阻塞)
            CloseableHttpResponse response = httpClient.execute(httpPost);
            //获取结果实体
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                //按指定编码转换结果实体为String类型
                String body = EntityUtils.toString(entity, encoding);
                logger.info(url + "接口返回报文是:/n" + body);
                return JSON.parseObject(body, LinkedHashMap.class , Feature.OrderedField);
            }
            EntityUtils.consume(entity);
            if(response != null ){
                //释放链接
                response.close();
            }
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (null != httpResponse) {
                    httpResponse.close();
                }
                logger.info("请求流关闭完成");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

  
}

注意)=========================================================

1.JAVA 使用的证书后缀为JKS , 如果不是需要将证书转换为.jks文件

2.需要在jre中导入证书,而jre(1.8)并不会处理证书链, 必须一个个导入, 如果使用的是中间证书所签发的证书直接导入中间证书即可,

  不必导入根(ROOT)证书 , 如果是根证书直接签发的证书 , 则必须导入根证书

 

 

以上是关于springboot2.X集成HttpClient 发送HTTPS 请求的主要内容,如果未能解决你的问题,请参考以下文章

Springboot2.x集成Redis哨兵模式

搞定全局ID生成器:SpringBoot2.x 集成百度 uidgenerator

又一神操作,SpringBoot2.x 集成百度 uidgenerator搞定全局ID

SpringBoot2.x集成分布式搜索引擎Elasticsearch

SpringBoot2.x集成knife4j

SpringBoot2.x 集成 Spring Security