基于HttpClient 4.3的可访问自签名HTTPS站点的新版工具类


HttpClient在当今Java应用中的位置越来越重要。从该项目的变迁过程我们不难发现,其已经从apache-commons众多的子项目中剥离,一跃成为如今的顶级项目,可见它的分量。然而随着项目的升级和架构的调整,很多以前常用的类和方法都已被打上了@Deprecated注解,作为一个有代码洁癖的程序猿,我们也有必要升级一下工具类,让代码更加整洁。

另外在项目中正好需要访问https协议的接口,而对应的服务器没有购买商业CA颁发的正式受信证书,只是做了个自签名(联想一下12306网站购票时提示的那个警告信息),默认情况下通过HttpClient访问会抛出异常,在本文中也给出了解决办法。

在HttpClient 4.x版本中引入了大量的构造器设计模式,很多的配置都不建议直接new出来,而且相关的API也有所改动,例如连接参数,以前是直接new出HttpConnectionParams对象后通过set方法逐一设置属性,现在有了构造器,可以通过如下方式进行构造:

ConnectionConfig.custom().setCharset(Charsets.toCharset(defaultEncoding)).build();

SocketConfig.custom().setSoTimeout(100000).build();

基本情况就是这样,接下来谈下如何使用新的HttpClient来访问自签名https接口。

参阅CSDN博主noodies代码,发现实现访问自签名https的要点就是建立一个自定义的SSLContext对象,该对象要有可以存储信任密钥的容器,还要有判断当前连接是否受信任的策略,以及在SSL连接工厂中取消对所有主机名的验证。他的代码将会在本文最后贴出来,以下代码均针对新HttpClient。

首先建立一个信任任何密钥的策略。代码很简单,不去考虑证书链和授权类型,均认为是受信任的:

 class AnyTrustStrategy implements TrustStrategy{
 
 @Override
 public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
  return true;
 }
 
}

HttpClient既能处理常规http协议,又能支持https,根源在于在连接管理器中注册了不同的连接创建工厂。当访问url的schema为http时,调用明文连接套节工厂来建立连接;当访问url的schema为https时,调用SSL连接套接字工厂来建立连接。对于http的连接我们不做修改,只针对使用SSL的https连接来进行自定义:

RegistryBuilder<ConnectionSocketFactory> registryBuilder = RegistryBuilder.<ConnectionSocketFactory>create();
ConnectionSocketFactory plainSF = new PlainConnectionSocketFactory();
registryBuilder.register("http", plainSF);
//指定信任密钥存储对象和连接套接字工厂
try {
 KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
 SSLContext sslContext = SSLContexts.custom().useTLS().loadTrustMaterial(trustStore, new AnyTrustStrategy()).build();
 LayeredConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
 registryBuilder.register("https", sslSF);
} catch (KeyStoreException e) {
 throw new RuntimeException(e);
} catch (KeyManagementException e) {
 throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
 throw new RuntimeException(e);
}
Registry<ConnectionSocketFactory> registry = registryBuilder.build();

在上述代码中可以看到,首先建立了一个密钥存储容器,随后让SSLContext开启TLS,并将密钥存储容器和信任任何主机的策略加载到该上下文中。构造SSL连接工厂时,将自定义的上下文和允许任何主机名通过校验的指令一并传入。最后将这样一个自定义的SSL连接工厂注册到https协议上。

前期准备已经完成,接下来我们要获得HttpClient对象:

//设置连接管理器
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager(registry);
connManager.setDefaultConnectionConfig(connConfig);
connManager.setDefaultSocketConfig(socketConfig);
//构建客户端
HttpClient client= HttpClientBuilder.create().setConnectionManager(connManager).build();

为了让我们的HttpClient具有多线程处理的能力,连接管理器选用了PoolingHttpClientConnectionManager,将协议注册信息传入连接管理器,最后再次利用构造器的模式创建出我们需要的HttpClient。随后的GET/POST请求发起方法http和https之间没有差异。

为了验证我们的代码是否成功,可以做下JUnit单元测试:

import sys
class progressbar(object):
    def __init__(self, finalcount, block_char='.'):
        self.finalcount = finalcount
        self.blockcount = 0
        self.block = block_char
        self.f = sys.stdout
        if not self.finalcount: return
        self.f.write('\n------------------ % Progress -------------------1\n')
        self.f.write('    1    2    3    4    5    6    7    8    9    0\n')
        self.f.write('----0----0----0----0----0----0----0----0----0----0\n')
    def progress(self, count):
        count = min(count, self.finalcount)
        if self.finalcount:
            percentcomplete = int(round(100.0*count/self.finalcount))
            if percentcomplete < 1: percentcomplete = 1
        else:
            percentcomplete=100
        blockcount = int(percentcomplete//2)
        if blockcount <= self.blockcount:
            return
        for i in range(self.blockcount, blockcount):
            self.f.write(self.block)
        self.f.flush()
        self.blockcount = blockcount
        if percentcomplete == 100:
            self.f.write("\n")

if __name__ == "__main__":
    from time import sleep
    pb = progressbar(8, "*")
    for count in range(1, 9):
        pb.progress(count)
        sleep(0.2)
    pb = progressbar(100)
    pb.progress(20)
    sleep(0.3)
    pb.progress(47)
    sleep(0.3)
    pb.progress(90)
    sleep(0.3)
    pb.progress(100)
    print "testing 1:"
    pb = progressbar(1)
    pb.progress(1)

执行后可以在控制台看到12306余票查询界面的html代码

为了方便大家使用,本人将封装好的代码上传到了帮客之家资源共享中,欢迎下载。

下载地址:

------------------------------------------分割线------------------------------------------

免费下载地址在 http://linux.bkjia.com/

用户名与密码都是www.bkjia.com

具体下载目录在 /2015年资料/1月/2日/基于HttpClient 4.3的可访问自签名HTTPS站点的新版工具类

下载方法见

------------------------------------------分割线------------------------------------------

最后参考了这篇文章代码提供了思路:

本文中的部分代码参阅了HttpClient 4.3.5官方文档的2.7节关于Connection socket factories的内容

Android使用HttpURLConnection下载图片

Adroid 4.0 HttpURLConnection抛异常解决方法

node.js+Android(使用HttpURLConnection和HttpClient)实现文件上传

在Android上用HttpURLConnection获取网页内容

Java使用HttpURLConnection上传文件

使用HttpClient实现文件的上传下载

Android 实现 HttpClient 请求Https

Android使用HttpClient下载图片

本文永久更新链接地址:

相关内容