1、HttpClient簡介
HttpClient相比傳統JDK自帶的URLConnection,增加了易用性和靈活性,它不僅使客戶端發送Http請求變得容易,而且也方便開發人員測試接口(基於Http協議的),提高了開發的效率,也方便提高代碼的健壯性。因此熟練掌握HttpClient是很重要的必修內容,掌握HttpClient后,相信對於Http協議的了解會更加深入。
org.apache.commons.httpclient.HttpClient與org.apache.http.client.HttpClient的區別Commons的HttpClient項目現在是生命的盡頭,不再被開發, 已被Apache HttpComponents項目HttpClient和HttpCore 模組取代,提供更好的性能和更大的靈活性。
2、HTTP的Keep-Alive
在前面的博客中http協議中,可以看到http的請求頭中可以設置connection可以設置為Keep-Alive,在HTTP/1.1使用Keep-Alive為默認值,如果需要關閉則需要手動關閉。
在HTTP 1.0以前,每個http請求都要求打開一個TCP socket連接,並且使用一次之后就斷開這個TCP連接,這會導致頻繁地創建和銷毀TCP。HTTP 1.1通過使用keep-alive可以改善這種狀態,即在一次TCP連接中可以持續發送多份數據而不會斷開連接,以此提高性能和提高http服務器的吞吐率(更少的tcp連接意味着更少的系統內核調用,socket的accept()和close()調用)。
2.1 HTTP的Keep-Alive
當保持長連接時,如何判斷一次請求已經完成?
Content-Length
Content-Length表示實體內容的長度。瀏覽器通過這個字段來判斷當前請求的數據是否已經全部接收。
所以,當瀏覽器請求的是一個靜態資源時,即服務器能明確知道返回內容的長度時,可以設置Content-Length來控制請求的結束。但當服務器並不知道請求結果的長度時,如一個動態的頁面或者數據,Content-Length就無法解決上面的問題,這個時候就需要用到Transfer-Encoding字段。
Transfer-Encoding
Transfer-Encoding是指傳輸編碼,在上面的問題中,當服務端無法知道實體內容的長度時,就可以通過指定Transfer-Encoding: chunked來告知瀏覽器當前的編碼是將數據分成一塊一塊傳遞的。當然, 還可以指定Transfer-Encoding: gzip, chunked表明實體內容不僅是gzip壓縮的,還是分塊傳遞的。最后,當瀏覽器接收到一個長度為0的chunked時, 知道當前請求內容已全部接收。
Keep-Alive timeout:
Httpd守護進程,一般都提供了keep-alive timeout時間設置參數。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。這個keepalive_timout時間值意味着:一個http產生的tcp連接在傳送完最后一個響應后,還需要hold住keepalive_timeout秒后,才開始關閉這個連接。
當httpd守護進程發送完一個響應后,理應馬上主動關閉相應的tcp連接,設置 keepalive_timeout后,httpd守護進程會想說:”再等等吧,看看瀏覽器還有沒有請求過來”,這一等,便是keepalive_timeout時間。如果守護進程在這個等待的時間里,一直沒有收到瀏覽器發過來http請求,則關閉這個http連接。
2.2 Tcp的Keep-Alive
tcp鏈接建立之后,如果應用程序或者上層協議一直不發送數據,或者隔很長時間才發送一次數據,當鏈接很久沒有數據報文傳輸時如何去確定對方還在線,到底是掉線了還是確實沒有數據傳輸,鏈接還需不需要保持,這種情況在TCP協議設計中是需要考慮到的。
TCP協議通過一種巧妙的方式去解決這個問題,當超過一段時間之后,TCP自動發送一個數據為空的報文給對方,如果對方回應了這個報文,說明對方還在線,鏈接可以繼續保持,如果對方沒有報文返回,並且重試了多次之后則認為鏈接丟失,沒有必要保持鏈接。
2.3 http keep-alive與tcp keep-alive
HTTP位於網絡協議棧的應用層,而TCP位於網絡協議棧的傳輸層,兩者的KEEP-ALIVE雖然名稱相同,但是作用不同。http keep-alive是為了讓tcp活得更久一點,以便在同一個連接上傳送多個http,提高socket的效率。而tcp keep-alive是TCP的一種檢測TCP連接狀況的保鮮機制。t檢測對端是否依然存活。
2.4 開啟Keep-Alive的優缺點
優點:Keep-Alive模式更加高效,因為避免了連接建立和釋放的開銷。
缺點:長時間的Tcp連接容易導致系統資源無效占用,浪費系統資源。
所以對於需要頻繁發送HTTP請求的應用,需要在客戶端開啟keep-alive,使用HTTP長連接。
3 HttpClient設置
httpClient = HttpClients.custom() //連接池配置 .setConnectionManager(poolingHttpClientConnectionManager) //requestConfig配置 .setDefaultRequestConfig(requestConfig) .disableCookieManagement() .disableConnectionState() .disableAuthCaching() //默認socketConfig配置 .setDefaultSocketConfig(socketConfig) //默認頭配置 .setDefaultHeaders(defaultHeaders) //重試handle .setRetryHandler(httpRequestRetryHandler) .build();
3.1 PoolingHttpClientConnectionManager連接池設置
兩個主機建立連接的過程是很復雜的一個過程,涉及到多個數據包的交換,並且也很耗時間。Http連接需要的三次握手開銷很大,這一開銷對於比較小的http消息來說更大。但是如果我們直接使用已經建立好的http連接,這樣花費就比較小,吞吐率更大。在高並發大量的請求網絡的時候,使用連接池能提升吞吐量。
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry); // 設置整個連接池的最大連接數 cm.setMaxTotal(maxTotal); // 設置每個route默認的最大連接數 cm.setDefaultMaxPerRoute(maxPerRoute); HttpHost httpHost = new HttpHost(hostname, port); // 設置某個route的最大連接數,優先於defaultMaxPerRoute。 cm.setMaxPerRoute(new HttpRoute(httpHost), maxRoute); //該方法關閉超過連接保持時間的連接,並從池中移除。 cm.closeExpiredConnections(); //該方法關閉空閑時間超過timeout的連接,空閑時間從交還給連接池時開始,不管是否已過期,超過空閑時間則關閉。 cm.closeIdleConnections(timeout,tunit);
connectionConfig配置
//消息約束 MessageConstraints messageConstraints = MessageConstraints.custom() .setMaxHeaderCount(200) .setMaxLineLength(2000) .build(); //Http connection相關配置 ConnectionConfig connectionConfig = ConnectionConfig.custom() .setMalformedInputAction(CodingErrorAction.IGNORE) .setUnmappableInputAction(CodingErrorAction.IGNORE) .setCharset(Consts.UTF_8) .setMessageConstraints(messageConstraints) .build(); //一般不修改HTTP connection相關配置,故不設置 //cm.setDefaultConnectionConfig(connectionConfig); //cm.setConnectionConfig(new HttpHost("somehost", 80), ConnectionConfig.DEFAULT);
具體源碼解析可參考:https://www.cnblogs.com/shoren/p/httpclient-leaseConnection.html
3.2 RequestConfig設置
主要用於獲取和配置一些外部的網絡環境
RequestConfig requestConfig = RequestConfig.custom() //設置從connectManager獲取Connection 超時時間 .setConnectionRequestTimeout(1000) //設置連接超時時間 .setConnectTimeout(10000) //請求獲取數據的超時時間 .setSocketTimeout(10000) //確定是否應自動處理身份驗證 .setAuthenticationEnabled(true) //確定循環重定向(重定向到相同位置)是否應該重定向 .setCircularRedirectsAllowed(false) //重定向的最大數目。對重定向次數的限制是為了防止無限循環 .setMaxRedirects(5) //確定是否應自動處理重定向 .setRedirectsEnabled(true) //確定是否應拒絕相對重定向。HTTP規范要求位置值是一個絕對URI .setRelativeRedirectsAllowed(true) //確定是否應自動解壓縮壓縮實體 .setContentCompressionEnabled(true) //確定用於HTTP狀態管理的cookie規范的名稱 .setCookieSpec("") //返回用於請求執行的本地地址。在具有多個網絡接口的計算機上,此參數可用於選擇其中的網絡接口連接產生。 .setLocalAddress() //代理配置 .setProxy() //在使用代理主機進行身份驗證時,確定支持的身份驗證方案的優先順序。 .setProxyPreferredAuthSchemes() //在使用目標主機進行身份驗證時,確定受支持的身份驗證模式的首選項順序 .setTargetPreferredAuthSchemes() .build();
3.3 SocketConfig配置
SocketConfig.custom() //開啟監視TCP連接是否有效 .setSoKeepAlive(false) //是否可以在一個進程關閉Socket后,即使它還沒有釋放端口,其它進程還可以立即重用端口 .setSoReuseAddress(true) //接收數據的等待超時時間,單位ms .setSoTimeout(10000) //是否立即發送數據,設置為true會關閉Socket緩沖,默認為false .setTcpNoDelay(false) .build();
3.4 defaultHeader配置
Collection<Header> defaultHeaders = new ArrayList<>(); defaultHeaders.add(new BasicHeader(HttpHeaders.ACCEPT_ENCODING, "gzip, deflate")); defaultHeaders.add(new BasicHeader(HttpHeaders.ACCEPT_LANGUAGE, "zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3")); defaultHeaders.add(new BasicHeader(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); defaultHeaders.add(new BasicHeader(HttpHeaders.CONNECTION, "keep-alive"));
3.5 HttpRequestRetryHandler配置
//禁用重試(參數:retryCount、requestSentRetryEnabled) HttpRequestRetryHandler requestRetryHandler = new DefaultHttpRequestRetryHandler(0, false); //自定義重試策略 httpRequestRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest(IOException exception,int executionCount, HttpContext context) { if (executionCount >= 3) {// 如果已經重試了3次,就放棄 return false; } if (exception instanceof NoHttpResponseException) {// 如果服務器丟掉了連接,那么就重試 return true; } if (exception instanceof SSLHandshakeException) {// 不要重試SSL握手異常 return false; } if (exception instanceof InterruptedIOException) {// 超時 return false; } if (exception instanceof UnknownHostException) {// 目標服務器不可達 return false; } if (exception instanceof ConnectTimeoutException) {// 連接被拒絕 return false; } if (exception instanceof SSLException) {// SSL握手異常 return false; } return false; }
}
其實我們在實際使用中也是使用默認的 socketConfig 和 connectionConfig。在實際應用中連接數相關配置(如maxTotal、maxPerRoute),還有請求相關的超時時間設置(如connectionTimeout、socketTimeout、connectionRequestTimeout)是比較重要的。
具體連接池原理參考文檔:
httpClient 4.3.x configuration 官方樣例
使用httpclient必須知道的參數設置及代碼寫法、存在的風險
---恢復內容結束---