場景:調用外部系統接口的http請求
要求:
1:可能是http請求,也可能是https請求
2:需要加入連接池的概念,不能每次發起請求都新建一個連接(每次連接握手三次,效率太低)
准備使用httpclient 4.5的版本
HTTPClient的特性
1. 基於標准、純凈的Java語言。實現了Http1.0和Http1.1
2. 以可擴展的面向對象的結構實現了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE)。
3. 支持HTTPS協議。
4. 通過Http代理建立透明的連接。
5. 利用CONNECT方法通過Http代理建立隧道的https連接。
6. Basic, Digest, NTLMv1, NTLMv2, NTLM2 Session, SNPNEGO/Kerberos認證方案。
7. 插件式的自定義認證方案。
8. 便攜可靠的套接字工廠使它更容易的使用第三方解決方案。
9. 連接管理器支持多線程應用。支持設置最大連接數,同時支持設置每個主機的最大連接數,發現並關閉過期的連接。
10. 自動處理Set-Cookie中的Cookie。
11. 插件式的自定義Cookie策略。
12. Request的輸出流可以避免流中內容直接緩沖到socket服務器。
13. Response的輸入流可以有效的從socket服務器直接讀取相應內容。
14. 在http1.0和http1.1中利用KeepAlive保持持久連接。
15. 直接獲取服務器發送的response code和 headers。
16. 設置連接超時的能力。
17. 實驗性的支持http1.1 response caching。
18. 源代碼基於Apache License 可免費獲取。
在HttpClient 4.x版本中引入了大量的構造器設計模式,很多的配置都不建議直接new出來,而且相關的API也有所改動,例如連接參數,以前是直接new出HttpConnectionParams對象后通過set方法逐一設置屬性,現在有了構造器,可以通過如下方式進行構造:
- ConnectionConfig.custom().setCharset(Charsets.toCharset(defaultEncoding)).build();
- SocketConfig.custom().setSoTimeout(100000).build();
實現訪問自簽名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之間沒有差異。