HttpClient实现https调用

  • 在HttpClient 4.x版本中引入了大量的构造器设计模式

  • https请求建立详解

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

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 registryBuilder = RegistryBuilder.create();

ConnectionSocketFactory plainSF =newPlainConnectionSocketFactory();

registryBuilder.register("http", plainSF);

//指定信任密钥存储对象和连接套接字工厂

try{

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

SSLContext sslContext = SSLContexts.custom().useTLS().loadTrustMaterial(trustStore,newAnyTrustStrategy()).build();

LayeredConnectionSocketFactory sslSF =newSSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

registryBuilder.register("https", sslSF);

}catch(KeyStoreException e) {

thrownewRuntimeException(e);

}catch(KeyManagementException e) {

thrownewRuntimeException(e);

}catch(NoSuchAlgorithmException e) {

thrownewRuntimeException(e);

}

Registry registry = registryBuilder.build();

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

//设置连接管理器

PoolingHttpClientConnectionManager connManager =newPoolingHttpClientConnectionManager(registry);

connManager.setDefaultConnectionConfig(connConfig);

connManager.setDefaultSocketConfig(socketConfig);

//构建客户端

HttpClient client= HttpClientBuilder.create().setConnectionManager(connManager).build();

为了让我们的HttpClient具有多线程处理的能力,连接管理器选用了PoolingHttpClientConnectionManager,将协议注册信息传入连接管理器,最后再次利用构造器的模式创建出我们需要的HttpClient。随后的GET/POST请求发起方法http和https之间没有差异。
为了验证我们的代码是否成功,可以做下JUnit单元测试:

@Test

publicvoiddoTest()throwsClientProtocolException, URISyntaxException, IOException{

HttpUtil util = HttpUtil.getInstance();

InputStream in = util.doGet("https://kyfw.12306.cn/otn/leftTicket/init");

String retVal = HttpUtil.readStream(in, HttpUtil.defaultEncoding);

System.out.println(retVal);

}

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

  • 有朋友反馈说提供的工具类中没有直接POST JSON对象的方法,下面我提供一下基础方法,供参考(此代码未包含在下文的共享资源中,请自行补充进去)
/**

* 基本Post请求

* @param url 请求url

* @param queryParams 请求头的查询参数

* @param json 直接放入post请求体中的文本(请使用JSON)

* @return

* @throws URISyntaxException

* @throws UnsupportedEncodingException

*/

publicHttpResponse doPostBasic(String url, Map queryParams, String json)throwsURISyntaxException, ClientProtocolException, IOException{

HttpPost pm =newHttpPost();

URIBuilder builder =newURIBuilder(url);

//填入查询参数

if(MapUtils.isNotEmpty(queryParams)){

builder.setParameters(HttpUtil.paramsConverter(queryParams));

}

pm.setURI(builder.build());

//填入post json数据

if(StringUtils.isNotBlank(json)){

//下面的ContentType完整类名为:org.apache.http.entity.ContentType

pm.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));

}

return client.execute(pm);

}

  • 下面再列举一个传入指定秘钥的https访问方法
public void initSSLConfigForTwoWay() throws Exception {

        HttpClientBuilder b = HttpClientBuilder.create();
        // 1 Import your own certificate
//		String demo_base_Path = System.getProperty("user.dir");
//		String demo_base_Path = getClass().getClassLoader().getResource("").getPath();
//		String selfcertpath = demo_base_Path +  Constant.SELFCERTPATH;
//		String trustcapath = demo_base_Path +  Constant.TRUSTCAPATH;

//		String selfcertpath = demo_base_Path + Constant.SELFCERTPATH;
//		String trustcapath = demo_base_Path + Constant.TRUSTCAPATH;

        KeyStore selfCert = KeyStore.getInstance("pkcs12");
        selfCert.load(getClass().getClassLoader().getResourceAsStream(NbConstant.CERT_FLODER + File.separatorChar + NbConstant.SELFCERTPATH),
                NbConstant.SELFCERTPWD.toCharArray());
        KeyManagerFactory kmf = KeyManagerFactory.getInstance("sunx509");
        kmf.init(selfCert, NbConstant.SELFCERTPWD.toCharArray());

        // 2 Import the CA certificate of the server,
        KeyStore caCert = KeyStore.getInstance("jks");
        caCert.load(getClass().getClassLoader().getResourceAsStream(NbConstant.CERT_FLODER + File.separatorChar + NbConstant.TRUSTCAPATH), NbConstant.TRUSTCAPWD.toCharArray());
        TrustManagerFactory tmf = TrustManagerFactory.getInstance("sunx509");
        tmf.init(caCert);

        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

        // 3 Set the domain name to not verify
        // (Non-commercial IoT platform, no use domain name access generally.)
        SSLSocketFactory ssf = new SSLSocketFactory(sc, new String[]{"TLSv1.2", "TLSv1.1", "TLSv1"}, null,
                SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

        // If the platform has already applied for a domain name which matches
        // the domain name in the certificate information, the certificate
        // domain name check can be enabled (open by default)
        // SSLSocketFactory ssf = new SSLSocketFactory(sc);

//		ClientConnectionManager ccm = this.getConnectionManager();
//		SchemeRegistry sr = ccm.getSchemeRegistry();
//		sr.register(new Scheme("https", 8743, ssf));


        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", ssf)
                .build();

        //      -- allows multi-threaded use
        PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        connMgr.setMaxTotal(200);
        connMgr.setDefaultMaxPerRoute(100);
        b.setConnectionManager(connMgr);

        // finally, build the HttpClient;
        //      -- done!


//		httpClient = new DefaultHttpClient(ccm);
        httpClient = b.build();
    }

相关推荐