连接管理

HttpClient 有一个对连接初始化和终止,还有在活动连接上 I/O 操作的完整控制。而连接操作的很多方面可以使用一些参数来控制。

一、套接字工厂

HttpClientContext clientContext = HttpClientContext.create();
/**
 * 连接套接字 依靠 ConnectionSocketFactory接口去创建、初始化、连接。
 * ConnectionSocketFactory
 *      PlainConnectionSocketFactory 是创建和初始化原生(非加密的)套接字的默认工厂类。
 *      LayeredConnectionSocketFactory 也是一个接口, 是 ConnectionSocketFactory接口的扩展
 *      SSLConnectionSocketFactory SSL/TLS层的连接套接字, 可以建立SSL连接。
 * 注:HttpClient不适用任何自定义的加密功能,它完全依赖于标准的Java Cryptography(JCE) 和 安全套接字扩展(JSEE)。
 */
PlainConnectionSocketFactory sf = PlainConnectionSocketFactory.getSocketFactory();

// 创建并初始化套接字
Socket socket = sf.createSocket(clientContext);

int timeout = 1000;// ms
HttpHost target = new HttpHost("localhost");
InetSocketAddress remoteAddress = new InetSocketAddress("127.0.0.1", 8888);
// 连接套接字
sf.connectSocket(timeout, socket, target, remoteAddress, null,
        clientContext);

//套接字连接成功后,就可以使用Socket读写数据了, 一般不仅仅是读写数据,如果这样,比纯粹使用Socket网络编程还麻烦
// 获取输出流:OutputStream outputStream = socket.getOutputStream();
// 获取输入流:InputStream inputStream = socket.getInputStream();

二、SSL/TLS 的定制

SSL证书,也称为服务器SSL证书,是遵守SSL协议的一种数字证书由全球信任的证书颁发机构(CA)验证服务器身份后颁发将SSL证书安装在网站服务器上,可实现网站身份验证和数据加密传输双重功能。

/**
 * 使用带证书的定制 SSL访问
 * 程序中使用了my.store这个文件,这个文件不是网站的证书,而是一份包含自己密码的自己的证书库。
 * 这个文件是需要自己生成的,使用jdk中的keytool命令可以很方便的生成my.store文件。
 * 如何生成文件,请查看:https://www.cnblogs.com/myitnews/p/12206094.html
 */
public void createHttpSSL() throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, IOException, CertificateException {
     //是否绕过SSL
     boolean isSSL = false;
     //证书
     File creFile = new File("C:\\Users\\Administrator\\Desktop\\my.store");
     //证书库密码, 生成my.store时输入的口令
     String crePwd = "mypassword";
     /**
      * 1. 创建 SSL上下文对象
      */
     SSLContext sslContext = null;
     if (!isSSL) {
         sslContext = SSLContext.getInstance("SSLv3");
         // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法
         X509TrustManager x509TrustManager = new X509TrustManager() {
             @Override
             public X509Certificate[] getAcceptedIssuers() {
                 return null;
             }
             @Override
             public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
             }
             @Override
             public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
             }
         };
         sslContext.init(null, new TrustManager[] {x509TrustManager}, null);
     } else {
         if (null != creFile && creFile.length() > 0) {
             if (null != crePwd) {
                 KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                 keyStore.load(new FileInputStream(creFile), crePwd.toCharArray());
                 sslContext = SSLContexts.custom().loadTrustMaterial(keyStore, new TrustSelfSignedStrategy()).build();
             } else {
                 throw new SSLHandshakeException("密码为空");
             }
         }
     }

     /**
      * 2. 注册
      */
     Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
             .register("http", PlainConnectionSocketFactory.INSTANCE)
             .register("https", new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier()))
             .build();

     /**
      * 3. SSL注册到连接管理器中
      */
     PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(registry);
     connManager.setMaxTotal(1000);  // 连接池最大连接数
     connManager.setDefaultMaxPerRoute(20);  // 每个路由最大连接数

     /**
      * 4. 发送请求
      */
     CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connManager).build();
     //12306:https://kyfw.12306.cn/otn/login/init
     //支付宝:https://www.alipay.com/, 这里如果使用HttpPost会报403
     HttpGet httpPost = new HttpGet("https://www.alipay.com/");
     CloseableHttpResponse response = httpClient.execute(httpPost);
     System.out.println("响应行:" + response.getStatusLine());
     System.out.println("响应内容:"+EntityUtils.toString(response.getEntity(), Consts.UTF_8));
 }

三、主机名验证

/**
 * 主机名验证
 * 除了在 SSL/TSL协议层扮演信任验证和客户端认证角色之外,HttpClient能选择性的去验证
 * 目标主机名称是否和储存在服务器中X.509证书上的名称一致。一旦连接已经建立,验证过程能提供服务器信任材料的额外可靠性保证。
 * javax.net.ssl.HostnameVerifier接口体现了一种主机名验证策略。
 * javax.net.ssl.HostnameVerifier有两种实现HttpClient可以用来工作:
 *      DefaultHostnameVerifier:默认实现,遵从RFC2818。主机名必须符合指定证书上任意备选名称。
 *      NoopHostnameVerifier:该主机名验证器本质上会关闭主机名验证。它接受任何有效的和符合目标主机的SSL会话。
 *
 * 注意:主机名验证和 SSL信任验证不可混淆。
 */
public void hostVerifier() throws IOException {
    //每一个HttpClient会使用默认的DefaultHostnameVerifier实现,如果你想也可以指定实现
    //SSLContext sslContext = SSLContexts.createSystemDefault();
    //SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
    //HttpClients.custom().setSSLSocketFactory(sslsf);

    //HttpClient 4.4版本使用公共后缀列表来保证SSL证书中的通配符不会被误用,当应用在有一个共同顶级域名下的子域名的时候。
    //参考:https://publicsuffix.org/list/effective_tld_names.dat】。强烈建议制作一个列表的本地副本。

    PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(
            PublicSuffixMatcher.class.getResource("my-copy-effective_tld_names.dat"));
    DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
    // 禁用公共后缀验证
    // DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(null);

    HttpClientBuilder clientBuilder = HttpClients.custom();
    //绑定主机验证
    clientBuilder.setSSLHostnameVerifier(hostnameVerifier);

    CloseableHttpClient httpClient = clientBuilder.build();
}

 

posted @ 2020-01-17 11:18  codedot  阅读(543)  评论(0编辑  收藏  举报