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代碼
為了方便大家使用,本人將封裝好的代碼上傳到了Linux公社資源共享中,歡迎下載。
下載地址:
------------------------------------------分割線------------------------------------------
免費下載地址在 http://linux.linuxidc.com/
用戶名與密碼都是www.linuxidc.com
具體下載目錄在 /2015年資料/1月/2日/基於HttpClient 4.3的可訪問自簽名HTTPS站點的新版工具類
下載方法見 http://www.linuxidc.com/Linux/2013-07/87684.htm
------------------------------------------分割線------------------------------------------
最後參考了這篇文章代碼提供了思路:http://www.linuxidc.com/Linux/2015-01/111122.htm
本文中的部分代碼參閱了HttpClient 4.3.5官方文檔的2.7節關於Connection socket factories的內容
Android使用HttpURLConnection下載圖片 http://www.linuxidc.com/Linux/2014-05/101854.htm
Adroid 4.0 HttpURLConnection拋異常解決方法 http://www.linuxidc.com/Linux/2013-03/81085.htm
node.js+Android(使用HttpURLConnection和HttpClient)實現文件上傳 http://www.linuxidc.com/Linux/2012-02/53532.htm
在Android上用HttpURLConnection獲取網頁內容 http://www.linuxidc.com/Linux/2011-08/41211.htm
Java使用HttpURLConnection上傳文件 http://www.linuxidc.com/Linux/2014-08/104946.htm
使用HttpClient實現文件的上傳下載 http://www.linuxidc.com/Linux/2014-07/104303.htm
Android 實現 HttpClient 請求Https http://www.linuxidc.com/Linux/2014-05/102306.htm
Android使用HttpClient下載圖片 http://www.linuxidc.com/Linux/2014-05/101855.htm