-
在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();
}