HttpClient 教程 (二)

轉自:http://www.cnblogs.com/loveyakamoz/archive/2011/07/21/2112832.htmlhtml

第二章 鏈接管理java

HttpClient有一個對鏈接初始化和終止,還有在活動鏈接上I/O操做的完整控制。而鏈接操做的不少方面可使用一些參數來控制。node

2.1 鏈接參數

這些參數能夠影響鏈接操做:算法

  • 'http.socket.timeout':定義了套接字的毫秒級超時時間(SO_TIMEOUT),這就是等待數據,換句話說,在兩個連續的數據包之間最大的閒置時間。若是超時時間是0就解釋爲是一個無限大的超時時間。這個參數指望獲得一個java.lang.Integer類型的值。若是這個參數沒有被設置,那麼讀取操做就不會超時(無限大的超時時間)。
  • 'http.tcp.nodelay':決定了是否使用Nagle算法。Nagle算法視圖經過最小化發送的分組數量來節省帶寬。當應用程序但願下降網絡延遲並提升性能時,它們能夠關閉Nagle算法(也就是開啓TCP_NODELAY)。數據將會更早發送,增長了帶寬消耗的成文。這個參數指望獲得一個java.lang.Boolean類型的值。若是這個參數沒有被設置,那麼TCP_NODELAY就會開啓(無延遲)。
  • 'http.socket.buffer-size':決定了內部套接字緩衝使用的大小,來緩衝數據同時接收/傳輸HTTP報文。這個參數指望獲得一個java.lang.Integer類型的值。若是這個參數沒有被設置,那麼HttpClient將會分配8192字節的套接字緩存。
  • 'http.socket.linger':使用指定的秒數拖延時間來設置SO_LINGER。最大的鏈接超時值是平臺指定的。值0暗示了這個選項是關閉的。值-1暗示了使用了JRE默認的。這個設置僅僅影響套接字關閉操做。若是這個參數沒有被設置,那麼就假設值爲-1(JRE默認)。
  • 'http.connection.timeout':決定了直到鏈接創建時的毫秒級超時時間。超時時間的值爲0解釋爲一個無限大的時間。這個參數指望獲得一個java.lang.Integer類型的值。若是這個參數沒有被設置,鏈接操做將不會超時(無限大的超時時間)。
  • 'http.connection.stalecheck':決定了是否使用舊的鏈接檢查。當在一個鏈接之上執行一個請求而服務器端的鏈接已經關閉時,關閉舊的鏈接檢查可能致使在得到一個I/O錯誤風險時顯著的性能提高(對於每個請求,檢查時間能夠達到30毫秒)。這個參數指望獲得一個java.lang.Boolean類型的值。出於性能的關鍵操做,檢查應該被關閉。若是這個參數沒有被設置,那麼舊的鏈接將會在每一個請求執行以前執行。
  • 'http.connection.max-line-length':決定了最大請求行長度的限制。若是設置爲一個正數,任何HTTP請求行超過這個限制將會引起java.io.IOException異常。負數或零將會關閉這個檢查。這個參數指望獲得一個java.lang.Integer類型的值。若是這個參數沒有被設置,那麼就不強制進行限制了。
  • 'http.connection.max-header-count':決定了容許的最大HTTP頭部信息數量。若是設置爲一個正數,從數據流中得到的HTTP頭部信息數量超過這個限制就會引起java.io.IOException異常。負數或零將會關閉這個檢查。這個參數指望獲得一個java.lang.Integer類型的值。若是這個參數沒有被設置,那麼就不
  • 強制進行限制了。
  • 'http.connection.max-status-line-garbage':決定了在指望獲得HTTP響應狀態行以前可忽略請求行的最大數量。使用HTTP/1.1持久性鏈接,這個問題產生的破碎的腳本將會返回一個錯誤的Content-Length(有比指定的字節更多的發送)。不幸的是,在某些狀況下,這個不能在錯誤響應後來偵測,只能在下一次以前。因此HttpClient必須以這種方式跳過那些多餘的行。這個參數指望獲得一個java.lang.Integer類型的值。0是不容許在狀態行以前的全部垃圾/空行。使用java.lang.Integer#MAX_VALUE來設置不限制的數字。若是這個參數沒有被設置那就假設是不限制的。

2.2 持久鏈接

從一個主機向另一個創建鏈接的過程是至關複雜的,並且包含了兩個終端之間的不少包的交換,它是至關費時的。鏈接握手的開銷是很重要的,特別是對小量的HTTP報文。若是打開的鏈接能夠被重用來執行屢次請求,那麼就能夠達到很高的數據吞吐量。瀏覽器

HTTP/1.1強調HTTP鏈接默認狀況能夠被重用於屢次請求。HTTP/1.0兼容的終端也可使用類似的機制來明確地交流它們的偏好來保證鏈接處於活動狀態,也使用它來處理多個請求。HTTP代理也能夠保持空閒鏈接處於一段時間的活動狀態,防止對相同目標主機的一個鏈接也許對隨後的請求須要。保持鏈接活動的能力一般被稱做持久性鏈接。HttpClient徹底支持持久性鏈接。緩存

2.3 HTTP鏈接路由

HttpClient可以直接或經過路由創建鏈接到目標主機,這會涉及多箇中間鏈接,也被稱爲跳。HttpClient區分路由和普通鏈接,通道和分層。通道鏈接到目標主機的多箇中間代理的使用也稱做是代理鏈。安全

普通路由由鏈接到目標或僅第一次的代理來建立。通道路由經過代理鏈到目標鏈接到第一通道來創建。沒有代理的路由不是通道的,分層路由經過已存在鏈接的分層協議來創建。協議僅僅能夠在到目標的通道上或在沒有代理的直接鏈接上分層。服務器

2.3.1 路由計算

RouteInfo接口表明關於最終涉及一個或多箇中間步驟或跳的目標主機路由的信息。HttpRoute是RouteInfo的具體實現,這是不能改變的(是不變的)。HttpTracker是可變的RouteInfo實現,由HttpClient在內部使用來跟蹤到最大路由目標的剩餘跳數。HttpTracker能夠在成功執行向路由目標的下一跳以後更新。HttpRouteDirector是一個幫助類,能夠用來計算路由中的下一跳。這個類由HttpClient在內部使用。網絡

HttpRoutePlanner是一個表明計算到基於執行上下文到給定目標完整路由策略的接口。HttpClient附帶兩個默認的HttpRoutePlanner實現。ProxySelectorRoutePlanner是基於java.net.ProxySelector的。默認狀況下,它會從系統屬性中或從運行應用程序的瀏覽器中選取JVM的代理設置。DefaultHttpRoutePlanner實現既不使用任何Java系統屬性,也不使用系統或瀏覽器的代理設置。它只基於HTTP以下面描述的參數計算路由。多線程

2.3.2 安全HTTP鏈接

若是信息在兩個不能由非認證的第三方進行讀取或修改的終端之間傳輸,HTTP鏈接能夠被認爲是安全的。SSL/TLS協議是用來保證HTTP傳輸安全使用最普遍的技術。而其它加密技術也能夠被使用。一般來講,HTTP傳輸是在SSL/TLS加密鏈接之上分層的。

2.4 HTTP路由參數

這些參數能夠影響路由計算:
  • 'http.route.default-proxy':定義能夠被不使用JRE設置的默認路由規劃者使用的代理主機。這個參數指望獲得一個HttpHost類型的值。若是這個參數沒有被設置,那麼就會嘗試直接鏈接到目標。
  • 'http.route.local-address':定義一個本地地址由全部默認路由規劃者來使用。有多個網絡接口的機器中,這個參數能夠被用於從鏈接源中選擇網絡接口。這個參數指望獲得一個java.net.InetAddress類型的值。若是這個參數沒有被設置,將會自動使用本地地址。
  • 'http.route.forced-route':定義一個由全部默認路由規劃者使用的強制路由。代替了計算路由,給定的強制路由將會被返回,儘管它指向一個徹底不一樣的目標主機。這個參數指望獲得一個HttpRoute類型的值。若是這個參數沒有被設置,那麼就使用默認的規則創建鏈接到目標服務器。

2.5 套接字工廠

LayeredSocketFactory是SocketFactory接口的擴展。分層的套接字工廠可HTTP鏈接內部使用java.net.Socket對象來處理數據在線路上的傳輸。它們依賴SocketFactory接口來建立,初始化和鏈接套接字。這會使得HttpClient的用戶能夠提供在運行時指定套接字初始化代碼的應用程序。PlainSocketFactory是建立和初始化普通的(不加密的)套接字的默認工廠。

建立套接字的過程和鏈接到主機的過程是不成對的,因此套接字在鏈接操做封鎖時能夠被關閉。

PlainSocketFactory sf = PlainSocketFactory.getSocketFactory();
Socket socket = sf.createSocket();
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 8080, null, -1, params);

2.5.1 安全套接字分層

LayeredSocketFactory是SocketFactory接口的擴展。分層的套接字工廠能夠建立在已經存在的普通套接字之上的分層套接字。套接字分層主要經過代理來建立安全的套接字。HttpClient附帶實現了SSL/TLS分層的SSLSocketFactory。請注意HttpClient不使用任何自定義加密功能。它徹底依賴於標準的Java密碼學(JCE)和安全套接字(JSEE)擴展。

2.5.2 SSL/TLS的定製

HttpClient使用SSLSocketFactory來建立SSL鏈接。SSLSocketFactory容許高度定製。它可使用javax.net.ssl.SSLContext的實例做爲參數,並使用它來建立定製SSL鏈接。

TrustManager easyTrustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// 哦,這很簡單!
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
//哦,這很簡單!
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
SSLSocket socket = (SSLSocket) sf.createSocket();
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 443, null, -1, params);
SSLSocketFactory的定製暗示出必定程度SSL/TLS協議概念的熟悉,這個詳細的解釋超出了本文檔的範圍。請參考Java的安全套接字擴展[http://java.sun.com/j2se/1.5.0/docs/guide/
security/jsse/JSSERefGuide.html],這是javax.net.ssl.SSLContext和相關工具的詳細描述。

2.5.3 主機名驗證

除了信任驗證和客戶端認證在SSL/TLS協議級上進行,一旦鏈接創建以後,HttpClient能可選地驗證目標主機名匹配存儲在服務器的X.509認證中的名字。這個認證能夠提供額外的服務器信任材料的真實保證。X509主機名驗證接口表明了主機名驗證的策略。HttpClient附帶了3個X509主機名驗證器。很重要的一點是:主機名驗證不該該混淆SSL信任驗證。
  • StrictHostnameVerifier:嚴格的主機名驗證在Sun Java 1.4,Sun Java 5和Sun Java 6中是相同的。並且也很是接近IE6。這個實現彷佛是兼容RFC 2818處理通配符的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現通配符。
  • BrowserCompatHostnameVerifier:主機名驗證器和Curl和Firefox的工做方式是相同的。主機名必須匹配第一個CN或任意的subject-alt。在CN和其它任意的subject-alt中可能會出現通配符。BrowserCompatHostnameVerifier和StrictHostnameVerifier的惟一不一樣是使用BrowserCompatHostnameVerifier匹配全部子域的通配符(好比」*.foo.com」),包括」a.b.foo.com」。
  • AllowAllHostnameVerifier:這個主機名驗證器基本上是關閉主機名驗證的。這個實現是一個空操做,並且不會拋出javax.net.ssl.SSLException異常。

每個默認的HttpClient使用BrowserCompatHostnameVerifier的實現。若是須要的話,它能夠指定不一樣的主機名驗證器實現。

SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

2.6 協議模式

Scheme類表明了一個協議模式,好比「http」或「https」同時包含一些協議屬性,好比默認端口,用來爲給定協議建立java.net.Socket實例的套接字工廠。SchemeRegistry類用來維持一組Scheme,當去經過請求URI創建鏈接時,HttpClient能夠從中選擇:

Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", sf, 443);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(https);

2.7 HttpClient代理配置

儘管HttpClient瞭解複雜的路由模式和代理鏈,它僅支持簡單直接的或開箱的跳式代理鏈接。

告訴HttpClient經過代理去鏈接到目標主機的最簡單方式是經過設置默認的代理參數:

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpHost proxy = new HttpHost("someproxy", 8080);
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);

也能夠構建HttpClient使用標準的JRE代理選擇器來得到代理信息:

DefaultHttpClient httpclient = new DefaultHttpClient();
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
httpclient.getConnectionManager().getSchemeRegistry(),
ProxySelector.getDefault());
httpclient.setRoutePlanner(routePlanner);

另一種選擇,能夠提供一個定製的RoutePlanner實現來得到HTTP路由計算處理上的複雜的控制:

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setRoutePlanner(new HttpRoutePlanner() {
public HttpRoute determineRoute(HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
});

2.8 HTTP鏈接管理器

2.8.1 鏈接操做器

鏈接操做是客戶端的低層套接字或能夠經過外部實體,一般稱爲鏈接操做的被操做的狀態的鏈接。OperatedClientConnection接口擴展了HttpClientConnection接口並且定義了額外的控制鏈接套接字的方法。ClientConnectionOperator接口表明了建立實例和更新那些對象低層套接字的策略。實現類最有可能利用SocketFactory來建立java.net.Socket實例。ClientConnectionOperator接口可讓HttpClient的用戶提供一個鏈接操做的定製策略和提供可選實現OperatedClientConnection接口的能力。

2.8.2 管理鏈接和鏈接管理器

HTTP鏈接是複雜的,有狀態的,線程不安全的對象須要正確的管理以便正確地執行功能。HTTP鏈接在同一時間僅僅只能由一個執行線程來使用。HttpClient採用一個特殊實體來管理訪問HTTP鏈接,這被稱爲HTTP鏈接管理器,表明了ClientConnectionManager接口。一個HTTP鏈接管理器的目的是做爲工廠服務於新的HTTP鏈接,管理持久鏈接和同步訪問持久鏈接來確保同一時間僅有一個線程能夠訪問一個鏈接。

內部的HTTP鏈接管理器和OperatedClientConnection實例一塊兒工做,可是它們爲服務消耗器ManagedClientConnection提供實例。ManagedClientConnection扮演鏈接之上管理狀態控制全部I/O操做的OperatedClientConnection實例的包裝器。它也抽象套接字操做,提供打開和更新去建立路由套接字便利的方法。ManagedClientConnection實例瞭解產生它們到鏈接管理器的連接,並且基於這個事實,當再也不被使用時,它們必須返回到管理器。ManagedClientConnection類也實現了ConnectionReleaseTrigger接口,能夠被用來觸發釋放鏈接返回給管理器。一旦釋放鏈接操做被觸發了,被包裝的鏈接從ManagedClientConnection包裝器中脫離,OperatedClientConnection實例被返回給管理器。儘管服務消耗器仍然持有ManagedClientConnection實例的引用,它也再也不去執行任何I/O操做或有意無心地改變的OperatedClientConnection狀態。

這裏有一個從鏈接管理器中獲取鏈接的示例:

HttpParams params = new BasicHttpParams();
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
ClientConnectionManager connMrg = new SingleClientConnManager(params, sr);
// 請求新鏈接。這多是一個很長的過程。
ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
// 等待鏈接10秒
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
// 用鏈接在作有用的事情。當完成時釋放鏈接。
conn.releaseConnection();
} catch (IOException ex) {
// 在I/O error之上終止鏈接。
conn.abortConnection();
throw ex;
}

若是須要,鏈接請求能夠經過調用來ClientConnectionRequest#abortRequest()方法過早地中斷。這會解鎖在ClientConnectionRequest#getConnection()方法中被阻止的線程。

一旦響應內容被徹底消耗後,BasicManagedEntity包裝器類能夠用來保證自動釋放低層的鏈接。HttpClient內部使用這個機制來實現透明地對全部從HttpClient#execute()方法中得到響應釋放鏈接:

ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
BasicHttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader();
conn.receiveResponseEntity(response);
HttpEntity entity = response.getEntity();
if (entity != null) {
BasicManagedEntity managedEntity = new BasicManagedEntity(entity, conn, true);
// 替換實體
response.setEntity(managedEntity);
}
// 使用響應對象作有用的事情。當響應內容被消耗後這個鏈接將會自動釋放。
} catch (IOException ex) {
//在I/O error之上終止鏈接。
conn.abortConnection();
throw ex;
}

2.8.3 簡單鏈接管理器

SingleClientConnManager是一個簡單的鏈接管理器,在同一時間它僅僅維護一個鏈接。儘管這個類是線程安全的,但它應該被用於一個執行線程。SingleClientConnManager對於同一路由的後續請求會盡可能重用鏈接。而若是持久鏈接的路由不匹配鏈接請求的話,它也會關閉存在的鏈接以後對給定路由再打開一個新的。若是鏈接已經被分配,將會拋出java.lang.IllegalStateException異常。

對於每一個默認鏈接,HttpClient使用SingleClientConnManager。

2.8.4 鏈接池管理器

ThreadSafeClientConnManager是一個複雜的實現來管理客戶端鏈接池,它也能夠從多個執行線程中服務鏈接請求。對每一個基本的路由,鏈接都是池管理的。對於路由的請求,管理器在池中有可用的持久性鏈接,將被從池中租賃鏈接服務,而不是建立一個新的鏈接。

ThreadSafeClientConnManager維護每一個基本路由的最大鏈接限制。每一個默認的實現對每一個給定路由將會建立不超過兩個的併發鏈接,而總共也不會超過20個鏈接。對於不少真實的應用程序,這個限制也證實很大的制約,特別是他們在服務中使用HTTP做爲傳輸協議。鏈接限制,也可使用HTTP參數來進行調整。

這個示例展現了鏈接池參數是如何來調整的:

HttpParams params = new BasicHttpParams();
// 增長最大鏈接到200
ConnManagerParams.setMaxTotalConnections(params, 200);
// 增長每一個路由的默認最大鏈接到20
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);
// 對localhost:80增長最大鏈接到50
HttpHost localhost = new HttpHost("locahost", 80);
connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);

2.8.5 鏈接管理器關閉

當一個HttpClient實例再也不須要時,並且即將走出使用範圍,那麼關閉鏈接管理器來保證由管理器保持活動的全部鏈接被關閉,由鏈接分配的系統資源被釋放是很重要的。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
if (entity != null) {
entity.consumeContent();
}
httpclient.getConnectionManager().shutdown();

2.9 鏈接管理參數

這些是能夠用於定製標準HTTP鏈接管理器實現的參數:
  • 'http.conn-manager.timeout':定義了當從ClientConnectionManager中檢索ManagedClientConnection實例時使用的毫秒級的超時時間。這個參數指望獲得一個java.lang.Long類型的值。若是這個參數沒有被設置,鏈接請求就不會超時(無限大的超時時間)。
  • 'http.conn-manager.max-per-route':定義了每一個路由鏈接的最大數量。這個限制由客戶端鏈接管理器來解釋,並且應用於獨立的管理器實例。這個參數指望獲得一個ConnPerRoute類型的值。
  • 'http.conn-manager.max-total':定義了總共鏈接的最大數目。這個限制由客戶端鏈接管理器來解釋,並且應用於獨立的管理器實例。這個參數指望獲得一個java.lang.Integer類型的值。

2.10 多線程執行請求

當配備鏈接池管理器時,好比ThreadSafeClientConnManager,HttpClient能夠同時被用來執行多個請求,使用多線程執行。

ThreadSafeClientConnManager將會分配基於它的配置的鏈接。若是對於給定路由的全部鏈接都被租出了,那麼鏈接的請求將會阻塞,直到一個鏈接被釋放回鏈接池。它能夠經過設置'http.conn-manager.timeout'爲一個正數來保證鏈接管理器不會在鏈接請求執行時無限期的被阻塞。若是鏈接請求不能在給定的時間週期內被響應,將會拋出ConnectionPoolTimeoutException異常。

HttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);
// 執行GET方法的URI
String[] urisToGet = {
"http://www.domain1.com/",
"http://www.domain2.com/",
"http://www.domain3.com/",
"http://www.domain4.com/"
};
// 爲每一個URI建立一個線程
GetThread[] threads = new GetThread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
HttpGet httpget = new HttpGet(urisToGet[i]);
threads[i] = new GetThread(httpClient, httpget);
}
// 開始執行線程
for (int j = 0; j < threads.length; j++) {
threads[j].start();
}
// 合併線程
for (int j = 0; j < threads.length; j++) {
threads[j].join();
}
 
static class GetThread extends Thread {
private final HttpClient httpClient;
private final HttpContext context;
private final HttpGet httpget;
public GetThread(HttpClient httpClient, HttpGet httpget) {
this.httpClient = httpClient;
this.context = new BasicHttpContext();
this.httpget = httpget;
}
@Override
public void run() {
try {
HttpResponse response = this.httpClient.execute(this.httpget, this.context);
HttpEntity entity = response.getEntity();
if (entity != null) {
// 對實體作些有用的事情...
// 保證鏈接能釋放回管理器
entity.consumeContent();
}
} catch (Exception ex) {
this.httpget.abort();
}
}
}

2.11 鏈接收回策略

一個經典的阻塞I/O模型的主要缺點是網絡套接字僅當I/O操做阻塞時才能夠響應I/O事件。當一個鏈接被釋放返回管理器時,它能夠被保持活動狀態而卻不能監控套接字的狀態和響應任何I/O事件。若是鏈接在服務器端關閉,那麼客戶端鏈接也不能去偵測鏈接狀態中的變化和關閉本端的套接字去做出適當響應。

HttpClient經過測試鏈接是不是過期的來嘗試去減輕這個問題,這已經再也不有效了,由於它已經在服務器端關閉了,以前使用執行HTTP請求的鏈接。過期的鏈接檢查也並非100%的穩定,反而對每次請求執行還要增長10到30毫秒的開銷。惟一可行的而不涉及到每一個對空閒鏈接的套接字模型線程解決方案,是使用專用的監控線程來收回由於長時間不活動而被認爲是過時的鏈接。監控線程能夠週期地調用ClientConnectionManager#closeExpiredConnections()方法來關閉全部過時的鏈接,從鏈接池中收回關閉的鏈接。它也能夠選擇性調用ClientConnectionManager#closeIdleConnections()方法來關閉全部已經空閒超過給定時間週期的鏈接。

public static class IdleConnectionMonitorThread extends Thread {
private final ClientConnectionManager connMgr;
private volatile boolean shutdown;
public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
super();
this.connMgr = connMgr;
}
@Override
public void run() {
try {
while (!shutdown) {
synchronized (this) {
wait(5000);
// 關閉過時鏈接
connMgr.closeExpiredConnections();
// 可選地,關閉空閒超過30秒的鏈接
connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
}
}
} catch (InterruptedException ex) {
// 終止
}
}
public void shutdown() {
shutdown = true;
synchronized (this) {
notifyAll();
}
}
}

2.12 鏈接保持活動的策略

HTTP規範沒有肯定一個持久鏈接可能或應該保持活動多長時間。一些HTTP服務器使用非標準的頭部信息Keep-Alive來告訴客戶端它們想在服務器端保持鏈接活動的週期秒數。若是這個信息可用,HttClient就會利用這個它。若是頭部信息Keep-Alive在響應中不存在,HttpClient假設鏈接無限期的保持活動。然而許多現實中的HTTP服務器配置了在特定不活動週期以後丟掉持久鏈接來保存系統資源,每每這是不通知客戶端的。若是默認的策略證實是過於樂觀的,那麼就會有人想提供一個定製的保持活動策略。

 

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
// 兌現'keep-alive'頭部信息
HeaderElementIterator it = new BasicHeaderElementIterator(
response.headerIterator(HTTP.CONN_KEEP_ALIVE));
while (it.hasNext()) {
HeaderElement he = it.nextElement();
String param = he.getName();
String value = he.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
try {
return Long.parseLong(value) * 1000;
} catch(NumberFormatException ignore) {
}
}
}
HttpHost target = (HttpHost) context.getAttribute(
ExecutionContext.HTTP_TARGET_HOST);
if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
// 只保持活動5秒
return 5 * 1000;
} else {
// 不然保持活動30秒
return 30 * 1000;
}
}
}); 
相關文章
相關標籤/搜索