HttpClient使用總結

1、使用方法

使用HttpClient發送請求、接收響應很簡單,通常須要以下幾步便可。
1. 建立HttpClient對象。
2. 建立請求方法的實例,並指定請求URL。若是須要發送GET請求,建立HttpGet對象;若是須要發送POST請求,建立HttpPost對象。
3. 若是須要發送請求參數,可調用HttpGet、HttpPost共同的setParams(HetpParams params)方法來添加請求參數;對於HttpPost對象而言,也可調用setEntity(HttpEntity entity)方法來設置請求參數。
4. 調用HttpClient對象的execute(HttpUriRequest request)發送請求,該方法返回一個HttpResponse。
5. 調用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可獲取服務器的響應頭;調用HttpResponse的getEntity()方法可獲取HttpEntity對象,該對象包裝了服務器的響應內容。程序可經過該對象獲取服務器的響應內容。
6. 釋放鏈接。不管執行方法是否成功,都必須釋放鏈接html


  
  
  
  
  1. try {
  2. // 建立一個默認的HttpClient
  3. HttpClient httpclient = new DefaultHttpClient();
  4. // 建立一個GET請求
  5. HttpGet request = new HttpGet( "www.google.com");
  6. // 發送GET請求,並將響應內容轉換成字符串
  7. String response = httpclient.execute(request, new BasicResponseHandler());
  8. Log.v( "response text", response);
  9. } catch (ClientProtocolException e) {
  10. e.printStackTrace();
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }

2、多線程的HttpClient

在實際項目中,咱們極可能在多處須要進行HTTP通訊,這時候咱們不須要爲每一個請求都建立一個新的HttpClient。如今咱們的應用程序使用同一個HttpClient來管理全部的Http請求,一旦出現併發請求,那麼必定會出現多線程的問題。這就好像咱們的瀏覽器只有一個標籤頁卻有多個用戶,A要上google,B要上baidu,這時瀏覽器就會忙不過來了。幸運的是,HttpClient提供了建立線程安全對象的API

java

  
  
  
  
  1. public class CustomerHttpClient {
  2. private static final String CHARSET = HTTP.UTF_8;
  3. /**
  4. * 最大鏈接數
  5. */
  6. public final static int MAX_TOTAL_CONNECTIONS = 800;
  7. /**
  8. * 獲取鏈接的最大等待時間
  9. */
  10. public final static int WAIT_TIMEOUT = 60000;
  11. /**
  12. * 每一個路由最大鏈接數
  13. */
  14. public final static int MAX_ROUTE_CONNECTIONS = 400;
  15. /**
  16. * 鏈接超時時間
  17. */
  18. public final static int CONNECT_TIMEOUT = 10000;
  19. /**
  20. * 讀取超時時間
  21. */
  22. public final static int READ_TIMEOUT = 10000;
  23. private static HttpClient customerHttpClient;
  24. private CustomerHttpClient() {
  25. }
  26. public static synchronized HttpClient getHttpClient() {
  27. if ( null == customerHttpClient) {
  28. HttpParams params = new BasicHttpParams();
  29. // 設置一些基本參數
  30. HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
  31. HttpProtocolParams.setContentCharset(params,
  32. CHARSET);
  33. HttpProtocolParams.setUseExpectContinue(params, true);
  34. HttpProtocolParams
  35. .setUserAgent(
  36. params,
  37. "Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) "
  38. + "AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
  39. // 超時設置
  40. /* 從鏈接池中取鏈接的超時時間 */
  41. ConnManagerParams.setTimeout(params, WAIT_TIMEOUT);
  42. /* 鏈接超時 */
  43. HttpConnectionParams.setConnectionTimeout(params, CONNECT_TIMEOUT);
  44. /* 請求超時 */
  45. HttpConnectionParams.setSoTimeout(params, READ_TIMEOUT);
  46. // 設置最大鏈接數
  47. ConnManagerParams.setMaxTotalConnections(params, MAX_TOTAL_CONNECTIONS);
  48. // 設置每一個路由最大鏈接數
  49. ConnPerRouteBean connPerRoute = new ConnPerRouteBean(MAX_ROUTE_CONNECTIONS);
  50. ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
  51. // 設置咱們的HttpClient支持HTTP和HTTPS兩種模式
  52. SchemeRegistry schReg = new SchemeRegistry();
  53. schReg.register( new Scheme( "http", PlainSocketFactory
  54. .getSocketFactory(), 80));
  55. schReg.register( new Scheme( "https", SSLSocketFactory
  56. .getSocketFactory(), 443));
  57. // 使用線程安全的鏈接管理來建立HttpClient
  58. ClientConnectionManager conMgr = new ThreadSafeClientConnManager(
  59. params, schReg);
  60. customerHttpClient = new DefaultHttpClient(conMgr, params);
  61. }
  62. return customerHttpClient;
  63. }
  64. }

一、超時配置

上面的代碼提到了3種超時設置,比較容易搞混,HttpClient的3種超時說明

數據庫

  
  
  
  
  1. /* 從鏈接池中取鏈接的超時時間 */
  2. ConnManagerParams.setTimeout(params, 1000);
  3. /* 鏈接超時 */
  4. HttpConnectionParams.setConnectionTimeout(params, 2000);
  5. /* 請求超時 */
  6. HttpConnectionParams.setSoTimeout(params, 4000);

第一行設置ConnectionPoolTimeout:這定義了從ConnectionManager管理的鏈接池中取出鏈接的超時時間,此處設置爲1秒。
第二行設置ConnectionTimeout:  這定義了經過網絡與服務器創建鏈接的超時時間。Httpclient包中經過一個異步線程去建立與服務器的socket鏈接,這就是該socket鏈接的超時時間,此處設置爲2秒。
第三行設置SocketTimeout:    這定義了Socket讀數據的超時時間,即從服務器獲取響應數據須要等待的時間,此處設置爲4秒。

以上3種超時分別會拋出ConnectionPoolTimeoutException,ConnectionTimeoutException與SocketTimeoutException。 

二、線程池配置

ThreadSafeClientConnManager默認使用了鏈接池
apache

  
  
  
  
  1. //設置最大鏈接數
  2. ConnManagerParams.setMaxTotalConnections(httpParams, 10);
  3. //設置最大路由鏈接數
  4. ConnPerRouteBean connPerRoute = new ConnPerRouteBean( 10);
  5. ConnManagerParams.setMaxConnectionsPerRoute(httpParams, connPerRoute);

比較特別的是 每一個路由(route)最大鏈接數。什麼是一個route?這裏route的概念能夠理解爲運行環境機器到目標機器的一條線路。舉例來講,咱們使用HttpClient的實現來分別請求 www.baidu.com 的資源和 www.bing.com 的資源那麼他就會產生兩個route。這裏爲何要特別提到route最大鏈接數這個參數呢,由於這個參數的默認值爲2,若是不設置這個參數值默認狀況下對於同一個目標機器的最大併發鏈接只有2個!這意味着若是你正在執行一個針對某一臺目標機器的抓取任務的時候,哪怕你設置鏈接池的最大鏈接數爲200,可是實際上仍是隻有2個鏈接在工做,其餘剩餘的198個鏈接都在等待,都是爲別的目標機器服務的。



三、工具類

有了單例的HttpClient對象,咱們就能夠把一些經常使用的發出GET和POST請求的代碼也封裝起來,寫進咱們的工具類中了。POST請求示例:瀏覽器


  
  
  
  
  1. private static final String TAG = "CustomerHttpClient";
  2. public static String post(String url, NameValuePair... params) {
  3. try {
  4. // 編碼參數
  5. List<NameValuePair> formparams = new ArrayList<NameValuePair>(); // 請求參數
  6. for (NameValuePair p : params) {
  7. formparams.add(p);
  8. }
  9. UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams,
  10. HTTP.UTF_8);
  11. // 建立POST請求
  12. HttpPost request = new HttpPost(url);
  13. request.setEntity(entity);
  14. // 發送請求
  15. HttpClient client = getHttpClient();
  16. HttpResponse response = client.execute(request);
  17. if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
  18. throw new RuntimeException( "請求失敗");
  19. }
  20. HttpEntity resEntity = response.getEntity();
  21. return (resEntity == null) ? null : EntityUtils.toString(resEntity, CHARSET);
  22. } catch (UnsupportedEncodingException e) {
  23. Log.w(TAG, e.getMessage());
  24. return null;
  25. } catch (ClientProtocolException e) {
  26. Log.w(TAG, e.getMessage());
  27. return null;
  28. } catch (IOException e) {
  29. throw new RuntimeException( "鏈接失敗", e);
  30. }
  31. } 


四、線程池技術

4.1 長鏈接和短鏈接

所謂長鏈接是指客戶端與服務器端一旦創建鏈接之後,能夠進行屢次數據傳輸而不需從新創建鏈接,而短鏈接則每次數據傳輸都須要客戶端和服務器端創建一次鏈接。

長鏈接的優點在於省去了每次數據傳輸鏈接創建的時間開銷,可以大幅度提升數據傳輸的速度,對於P2P應用十分適合。
短鏈接每次數據傳輸都須要創建鏈接,咱們知道HTTP協議的傳輸層協議是TCP協議,TCP鏈接的創建和釋放分別須要進行3次握手和4次握手,頻繁的創建鏈接即增長了時間開銷,同時頻繁的建立和銷燬Socket一樣是對服務器端資源的浪費。

對於諸如Web網站之類的B2C應用,併發請求量大,每個用戶又不需頻繁的操做的場景下,維護大量的長鏈接對服務器無疑是一個巨大的考驗。而此時,短鏈接可能更加適用。
而對於須要頻繁發送HTTP請求的應用,須要在客戶端使用HTTP長鏈接。

安全

4.二、鏈接池

鏈接池管理的對象是長鏈接。鏈接池技術做爲建立和管理鏈接的緩衝池技術,目前已普遍用於諸如數據庫鏈接等長鏈接的維護和管理中,可以有效減小系統的響應時間,節省服務器資源開銷。其優點主要有兩個:其一是減小建立鏈接的資源開銷,其二是資源的訪問控制。

       
HTTP鏈接是無狀態的,這樣很容易給咱們形成HTTP鏈接是短鏈接的錯覺,實際上HTTP1.1默認便是持久鏈接,HTTP1.0也能夠經過在請求頭中設置Connection:keep-alive使得鏈接爲長鏈接。

服務器

4.三、HttpConnection

沒有鏈接池的概念,多少次請求就會創建多少個IO,在訪問量巨大的狀況下服務器的IO可能會耗盡。

網絡

4.四、HttpClient3

也有鏈接池的東西在裏頭,使用MultiThreadedHttpConnectionManager,大體過程以下:

多線程

  
  
  
  
  1. MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
  2. HttpClient client = new HttpClient(connectionManager);... // 在某個線程中。
  3. GetMethod get = new GetMethod( "http://jakarta.apache.org/");
  4. try {
  5. client.executeMethod(get); // print response to stdout
  6. System.out.println(get.getResponseBodyAsStream());
  7. } finally {
  8. // be sure the connection is released back to the connection
  9. managerget.releaseConnection();
  10. }



能夠看出來,它的方式與jdbc鏈接池的使用方式相近,比較不爽的就是須要手動調用releaseConnection去釋放鏈接。對每個HttpClient.executeMethod須有一個method.releaseConnection()與之匹配。

4.五、HttpClient4

HTTP Client4.0的ThreadSafeClientConnManager實現了HTTP鏈接的池化管理,其管理鏈接的基本單位是Route(路由),每一個路由上都會維護必定數量的HTTP鏈接。這裏的Route的概念能夠理解爲客戶端機器到目標機器的一條線路,例如使用HttpClient的實現來分別請求 www.163.com 的資源和 www.sina.com 的資源就會產生兩個route。缺省條件下對於每一個Route,HttpClient僅維護2個鏈接,總數不超過20個鏈接,顯然對於大多數應用來說,都是不夠用的,能夠經過設置HTTP參數進行調整。

併發

  
  
  
  
  1. HttpParams params = new BasicHttpParams();
  2. //將每一個路由的最大鏈接數增長到200
  3. ConnManagerParams.setMaxTotalConnections(params, 200);
  4. // 將每一個路由的默認鏈接數設置爲20
  5. ConnPerRouteBean connPerRoute = new ConnPerRouteBean( 20);
  6. // 設置某一個IP的最大鏈接數
  7. HttpHost localhost = new HttpHost( "locahost", 80);
  8. connPerRoute.setMaxForRoute( new HttpRoute(localhost), 50);
  9. ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
  10. SchemeRegistry schemeRegistry = new SchemeRegistry();
  11. schemeRegistry.register(
  12. new Scheme( "http", PlainSocketFactory.getSocketFactory(), 80));
  13. schemeRegistry.register(
  14. new Scheme( "https", SSLSocketFactory.getSocketFactory(), 443));
  15. ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
  16. HttpClient httpClient = new DefaultHttpClient(cm, params);


     

能夠配置的HTTP參數有:
1)  http.conn-manager.timeout 當某一線程向鏈接池請求分配線程時,若是鏈接池已經沒有能夠分配的鏈接時,該線程將會被阻塞,直至http.conn-manager.timeout超時,拋出ConnectionPoolTimeoutException。
2)  http.conn-manager.max-per-route 每一個路由的最大鏈接數;
3)  http.conn-manager.max-total 總的鏈接數;

4.六、過時長鏈接

鏈接的有效性檢測是全部鏈接池都面臨的一個通用問題,大部分HTTP服務器爲了控制資源開銷,並不會永久的維護一個長鏈接,而是一段時間就會關閉該鏈接。放回鏈接池的鏈接,若是在服務器端已經關閉,客戶端是沒法檢測到這個狀態變化而及時的關閉Socket的。這就形成了線程從鏈接池中獲取的鏈接不必定是有效的。這個問題的一個解決方法就是在每次請求以前檢查該鏈接是否已經存在了過長時間,可能已過時。可是這個方法會使得每次請求都增長額外的開銷。HTTP Client4.0的ThreadSafeClientConnManager 提供了closeExpiredConnections()方法和closeIdleConnections()方法來解決該問題。前一個方法是清除鏈接池中全部過時的鏈接,至於鏈接何時過時能夠設置,設置方法將在下面提到,然後一個方法則是關閉必定時間空閒的鏈接,可使用一個單獨的線程完成這個工做。

  
  
  
  
  1. public static class IdleConnectionMonitorThread extends Thread {
  2. private final ClientConnectionManager connMgr;
  3. private volatile boolean shutdown;
  4. public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
  5. super();
  6. this.connMgr = connMgr;
  7. }
  8. @Override
  9. public void run() {
  10. try {
  11. while (!shutdown) {
  12. synchronized ( this) {
  13. wait( 5000);
  14. // 關閉過時的鏈接
  15. connMgr.closeExpiredConnections();
  16. // 關閉空閒時間超過30秒的鏈接
  17. connMgr.closeIdleConnections( 30, TimeUnit.SECONDS);
  18. }
  19. }
  20. } catch (InterruptedException ex) {
  21. // terminate
  22. }
  23. }
  24. public void shutdown() {
  25. shutdown = true;
  26. synchronized ( this) {
  27. notifyAll();
  28. }


剛纔提到,客戶端能夠設置鏈接的過時時間,能夠經過HttpClient的setKeepAliveStrategy方法設置鏈接的過時時間,這樣就能夠配合closeExpiredConnections()方法解決鏈接池中鏈接失效的。

  
  
  
  
  1. DefaultHttpClient httpclient = new DefaultHttpClient();
  2. httpclient.setKeepAliveStrategy( new ConnectionKeepAliveStrategy() {
  3. public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
  4. // Honor 'keep-alive' header
  5. HeaderElementIterator it = new BasicHeaderElementIterator(
  6. response.headerIterator(HTTP.CONN_KEEP_ALIVE));
  7. while (it.hasNext()) {
  8. HeaderElement he = it.nextElement();
  9. String param = he.getName();
  10. String value = he.getValue();
  11. if (value != null && param.equalsIgnoreCase( "timeout")) {
  12. try {
  13. return Long.parseLong(value) * 1000;
  14. } catch(NumberFormatException ignore) {
  15. }
  16. }
  17. }
  18. HttpHost target = (HttpHost) context.getAttribute(
  19. ExecutionContext.HTTP_TARGET_HOST);
  20. if ( "www.163.com".equalsIgnoreCase(target.getHostName())) {
  21. // 對於163這個路由的鏈接,保持5秒
  22. return 5 * 1000;
  23. } else {
  24. // 其餘路由保持30秒
  25. return 30 * 1000;
  26. }
  27. }
  28. })
相關文章
相關標籤/搜索