使用Apache的HttpClient發送Http請求

使用Apache的HttpClient發送Http請求

1 基礎概念

1.1 HttpClient、TCP/IP、Socket的區別

HttpClient是Apache中一個開源的項目。它實現了HTTP標準中Client端的所有功能,使用它能夠很容易的進行HTTP信息的傳輸。

網絡從下往上分為:物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層和應用層。

TCP/IP協議:主要解決的是數據傳輸層面的事情,如果沒有規范的應用協議,數據能從網絡里的A節點傳到B節點,但是無法有效識別,建立在TCP/IP上的應用協議很多,比如:rpc、ftp等。所以,無論應用協議多強大,最終都要依靠傳輸層協議進行數據傳輸。

Http、Https:主要解決的是數據規范層面的事情,作用于應用層。而HTTPS是以安全為目標的HTTP通道,簡單講是HTTP的安全版,即HTTP下加入SSL層。

Socket:是tcp/ip的一個編程實現,在程序里,Http請求最終一定要綁定到一個具體的Socket連接進行上行和下行傳輸。Socket本身并不是協議,而是一個調用接口(API)。

1.2 HttpClient的介紹

對于簡單應用,HttpURLConnection完全可以滿足。但是對于

1)系統復雜度高,

2)性能要求高,

3)可靠性要求也高的應用,

則需要一個更強大的組件。HttpClient可以滿足這些條件。

2 HttpClient的使用

2.1 創建一個HttpClient對象

創建HttpClient對象的方式是使用CloseableHttpClient的builder類的HttpClientBuilder,先對一些屬性進行配置(采用裝飾者模式,不斷的setXxx().setXxx() .. 就行),再調用build方法來創建實例。

public InternalHttpClient(ClientExecChain execChain,
 HttpClientConnectionManager connManager, 
 HttpRoutePlanner routePlanner, 
 Lookup<CookieSpecProvider> cookieSpecRegistry,
 Lookup<AuthSchemeProvider> authSchemeRegistry, 
 CookieStore cookieStore, 
 CredentialsProvider credentialsProvider, 
 RequestConfig defaultConfig, 
 List<Closeable> closeables) {
 Args.notNull(execChain, "HTTP client exec chain");
 Args.notNull(connManager, "HTTP connection manager");
 Args.notNull(routePlanner, "HTTP route planner");
 this.execChain = execChain;
 this.connManager = connManager;
 this.routePlanner = routePlanner;
 this.cookieSpecRegistry = cookieSpecRegistry;
 this.authSchemeRegistry = authSchemeRegistry;
 this.cookieStore = cookieStore;
 this.credentialsProvider = credentialsProvider;
 this.defaultConfig = defaultConfig;
 this.closeables = closeables;
}

需要關注的幾個重要的初始化配置如下: HttpCLientConnectionManager、RequestConfig

2.2 創建RequestConfig

RequestConfig是對Request的一些配置,里面比較重要的有三個超時時間,默認情況下這三個時間都是0(如果不設置request的config,會在execute的過程中使用HttpClientParamConfig的getRequestConfig中使用默認參數進行設置,默認設置為-1),這也就意味著無限等待,很容易導致所有的請求阻塞在這個地方無限期等待。

這三個超時時間為:

A :connectionRequestTimeout—從連接池中取連接的超時時間

這個時間定義的是從ConnectionManager管理的連接池中取出連接的超時時間,如果連接池中沒有可用的連接,則request會被阻塞,最長等待connectionRequestTimeout的時間,如果還沒有服務,則拋出ConnectionPoolTimeoutException異常,不繼續等待。

B :connectTimeout—連接超時時間

這個時間定義了通過網絡與服務器建立連接的超時時間,也就是取得了連接池中某個連接之后接通目標url等待時間,會發生超時,拋出ConnectionTimeoutException異常。

C :socketTimeout—請求超時時間

這個時間定義了socket讀數據的超時時間,也就是連接到服務器之后到從服務器獲取響應數據需要等待的時間,或者說是連接上一個url之后到獲取response的返回等待時間。發生超時,會拋出SocketTimeoutException異常。

b38865f401418f8a.png

2.3 創建HttpClientConnectionManager

HttpClientConnectionManager是一個HTTP連接管理器,它負責新的HTTP連接的創建、管理連接的生命周期和保證一個HTTP連接在某一時刻只被一個線程使用。

關鍵的概念——Route

在HttpClient中,一個Route指運行環境機器 --> 目標機器(域名)的一條線路,也就是如果目標url的域名是同一個,那么它們的Route也是一樣的。

6884225c907714f2.png

HttpClientConnectionManager有兩種具體實現:

A : BasicHttpClientConnectionManager:每次只管理一個connection,但由于它只管理一個連接,所以只能被一個線程使用,它在管理連接的時候如果發現有相同的Route請求,會復用之前已經創建的連接,如果新來的請求不能復用之前的連接,它會關閉現有連接并重新打開它來相應新的請求。

B:PoolingHttpClientConnectionManager:與A不同(這個是默認設置),它管理著一個連接池,可以同時為多個線程服務,每次新來一個請求,如果在連接池中已經存在Route相同并且可用的connection,連接池就會直接復用這個connection;當不存在Route相同的connection,就新建一個connection為之服務;如果連接池已滿,則請求會等待直到被服務或者超時。

整個PoolingHttpClientConnectionManager生命周期從HttpClient初始化開始到整個應用結束調用httpClient.close()。 在PoolingHttpClientConnectionManager的配置中有兩個最大連接數量,分別控制著總的最大連接數量和每個route的最大連接數量。如果沒有顯式設置,默認每個route只允許最多2個connection,總的connection數量不超過20。

b924831b9ed63d6e.png

2.4 創建一個Request對象以及執行Request請求

HttpClient支持所有HTTP1.1中所有定義的請求類型:GET、HEAD、POST、PUT、DELETE、TRACE和OPTIONS。對使用的類為HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace和HttpOptions。Request的對象建立很簡單,一般用目標url來構造就好了。下面是一個HttpPost的創建代碼:

HttpPost httpPost = new HttpPost(url);

執行request請求的方式是:

httpclient.execute(HttpUriRequest request)

重點強調的是:HttpClient允許http連接在特定的Http上下文中執行,HttpContext是跟一個連接相關聯的,所以它也只能屬于一個線程,如果沒有特別設定,在execute的過程中,HttpClient會自動為每一個connection new一個HttpClientHttpContext。

final HttpClientContext localContext 
 = HttpClientContext.adapt(context != null ? context : new BasicHttpContext);

整個執行execute的過程如下:

e6d9a9c3f447283a.png

3 重點關注

3.1 連接池管理

連接池的獲取的完整邏輯是:在每收到一個route請求后,連接池都會建立一個以這個route為key的子連接池,當有一個新的連接請求到來的時候,它會優先匹配已經存在的子連接池們,如果之前已經有過以這個route為key的子連接池,那么就會去試圖取這個子連接池中狀態為available的連接,如果此時有可用的連接,則將取得的available連接狀態改為leased的,取連接成功。 如果此時子連接池沒有可用連接,那再看是否達到了所設置的最大連接數和每個route所允許的最大連接數的上限,如果還有余量則new一個新的連接,或者取得lastUsedConnection,關閉這個連接、把連接從原來所在的子連接池刪除,再lease取連接成功。 如果此時的情況不允許再new一個新的連接,就把這個請求連接的請求放入一個queue中排隊等待,直到得到一個連接或者超時才會從queue中刪去。

2e2aea7d6967b6b3.png

3.2 連接池回收

目前連接池的回收機制有二種:

第一種設置 HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); httpClientBuilder.evictExpiredConnections();//制定過期連接,httpClientBuilder.evictIdleConnections(60, TimeUnit.SECONDS); 用來關閉閑置連接,它會啟動一個守護線程進行清理工作。用戶可以通過builder#evictIdleConnections開啟該組件,并通過builder#setmaxIdleTime設置最大空閑時間。

第二種設置:初始化PoolingHttpClientConnectionManager設置每個連接的生命周期 PoolingHttpClientConnectionManager connManager =new PoolingHttpClientConnectionManager(socketFactoryRegistry, null, null, null, 360, TimeUnit.SECONDS);

3.3 keep-alive

在HttpClient.execute得到response之后的相關代碼中,它會先取出response的keep-alive頭來設置connection是否resuable以及存活的時間。如果服務器返回的響應中包含了Connection:Keep-Alive(默認有的),但沒有包含Keep-Alive時長的頭消息,HttpClient認為這個連接可以永遠保持,也就是長連接。

3.4 常見問題

有時會出現線程阻塞,經過上面的介紹,主要原因是:

一:沒有增加連接池的回收機制,造成長連接一直保持。

二:未設置connectionRequestTimeout,造成請求在連接池中一直等待,未在服務器中拋出異常。

4 小結

本篇文章簡單介紹了發送Http請求——Apache的HttpClient,由于純手打,難免會有紕漏,如果發現錯誤的地方,請第一時間告訴我,這將是我進步的一個很重要的環節。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容