1. 程式人生 > >基於HttpClient 4.3的可訪問自簽名HTTPS站點的新版工具類

基於HttpClient 4.3的可訪問自簽名HTTPS站點的新版工具類

本文出處:http://blog.csdn.net/chaijunkun/article/details/40145685,轉載請註明。由於本人不定期會整理相關博文,會對相應內容作出完善。因此強烈建議在原始出處檢視此文。

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單元測試:

@Test
public void doTest() throws ClientProtocolException, 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程式碼

*2017年2月15日補充:有朋友反饋說提供的工具類中沒有直接POST JSON物件的方法,下面我提供一下基礎方法,供參考(此程式碼未包含在下文的共享資源中,請自行補充進去)

/**
* 基本Post請求
* @param url 請求url
* @param queryParams 請求頭的查詢引數
* @param json 直接放入post請求體中的文字(請使用JSON)
* @return
* @throws URISyntaxException 
* @throws UnsupportedEncodingException 
*/
public HttpResponse doPostBasic(String url, Map<String, String> queryParams, String json) throws URISyntaxException, ClientProtocolException, IOException{
	HttpPost pm = new HttpPost();
	URIBuilder builder = new URIBuilder(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);
}

上述只是一個簡單的功能實現,更好的形式是將json引數換成Object data,然後在方法內部呼叫Object轉JSON字串的元件方法進行轉換。關於JSON元件的使用,請參閱我的另一篇博文:http://blog.csdn.net/chaijunkun/article/details/8257209

為了方便大家使用,本人將封裝好的程式碼上傳到了CSDN資源共享中,歡迎下載。

下載地址:http://download.csdn.net/detail/chaijunkun/8046331

最後感謝CSDN博主noodies的程式碼提供了思路:http://blog.csdn.net/noodies/article/details/17240805

本文中的部分程式碼參閱了HttpClient 4.3.5官方文件的2.7節關於Connection socket factories的內容