1. 程式人生 > >HttpClient 學習筆記

HttpClient 學習筆記

HttpClient 是Apache Jakarta Common 下的子專案,可以用來提供高效的、最新的、功能豐富的支援

HTTP 協議的客戶端程式設計工具包,並且它支援 HTTP 協議最新的版本和建議。

HttpClient的主要功能:

1、實現了所有HTTP的方法(GET,POST,PUT,HEAD)等

2、支援自動轉向

3、支援HTTPS協議

4、支援代理伺服器等

HttpClient的範圍/特性:

1、是一個基於HttpCore的客戶端Http傳輸類庫

2、基於傳統的(阻塞)IO

3、與內容無關

HttpClient不能做的事情:

HttpClient不是瀏覽器,它是一個客戶端http協議傳輸類庫。HttpClient被用來發送和接受Http訊息。

HttpClient不會處理http訊息的內容,不會進行JavaScript解析,不會關心content type,如果沒有明確設定,

httpclient不會對請求進行格式化、重定向url,或其他任何和http訊息傳輸相關的功能。

一、

1.1HTTP請求

HttpClient最基本的功能就是執行Http方法。一個Http方法的執行涉及到一個或者多個Http請求、Http相應的互動,

通常這個過程都會自動被HttpClient處理,對使用者透明。使用者只需要提供Http請求物件,HttpClient就會將http請求

傳送給目標伺服器,並且接收伺服器的響應,如果http請求執行不成功,httpclient就會丟擲異常。

所有的Http請求都有一個請求行(request line),包括方法名、請求的URI和Http版本號。

HTTPClient支援HTTP/1.1這個版本定義的所有Http方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。

對於每一種http方法,HTTPClient都頂一個一個相應的類:

GET---HttpGet PUT---HttpPut

HEAD---HttpHead DELETE---HttpDelete

POST---HttpPost TRACE---HttpTrace

OPTIONS---HttpOptions

Request-URI即統一資源定位符,用來標明http請求中的資源。Http request URIs 包含協議名、主機名、主機埠

(可選)、資源路徑、query(可選)、片段資訊(可選)。

HTTPClient提供URIBuilder工具類來簡化URIs的建立和修改過程。

例子:

1.2HTTP響應

伺服器收到客戶端的http請求後,就會對其進行解析,然後把響應發給客戶端,這個響應就是Httpresponse。

HTTP響應第一行是協議版本,之後是數字狀態碼和相關聯的文字段。

例子:

1.3訊息頭

一個Http訊息可以包含一系列的訊息頭,用來對http訊息進行描述,比如訊息長度,訊息型別等。

HTTPClient提供了方法來獲取、新增、移除、列舉訊息頭。

例子:

最有效的獲取制定型別的訊息頭的方法還是使用HeaderIterator介面

例子:


其實就是用

HeaderIterator it=response.headerIterator("Set-Cookie");

while(it.hasNext()){

system.out.println(it.next());

}

替代了

Header h1 = response.getFirstHeader("Set-Cookie");  

System.out.println(h1);  

Header h2 = response.getLastHeader("Set-Cookie");  

System.out.println(h2);  

Header[] hs = response.getHeaders("Set-Cookie");  

System.out.println(hs.length);  

HeaderIterator也提供了非常便捷的方式,將Http訊息解析成單獨的訊息頭元素


1.4HTTP實體

Http訊息可以攜帶http實體,這個http實體既可以是http請求,也可以是http響應的。

Http實體,可以在某些http請求或者響應中發現,但不是必須的。

Http規範中定義了兩中包含請求的方法:POST和PUT。

HTTP響應一般會包含一個內容實體。當然這條規則也有異常情況,如Head方法的響應,204沒有內容,

304沒有修改或者205內容資源重置。

HttpClient根據來源的不同,劃分了三種不同的Http實體內容。

1、streamed流式:內容是通過流來接受或者在執行中產生的,特別是,streamed這一類包含從http響應中

獲取的實體內容。一般說來,streamed實體是不可重複的。

2、self-contained自我包含式:內容在記憶體中或通過獨立的連結或其他實體中獲得的。self-contained型別

的實體內容通常是可以重複的。這種型別的實體通常用於關閉http請求。

3、wrapping包裝式:這種型別的內容是從另外的http實體中獲取的。

當從Http響應中讀取內容時,上面的三種區分對於連線管理器來說是非常重要的。對於由應用程式建立而

且只使用HttpClient傳送的請求實體,streamed和self-contained兩種型別的不同那就不那麼重要了。

這種情況下,建議考慮如streamed流式這種不能重複的實體和可以重複的self-contained自我包含式實體。

1.4.1可以重複的實體

一個實體是可重複的,也就是說它包含的內容可以被多次讀取。這種多次讀取只有self-contained(自包含)

的實體能做到(比如ByteArrayEntity或者StringEntity)。

1.4.2使用Http實體

由於一個Http實體既可以表示二進位制內容,又可以表示文字內容,所以Http實體要支援字元編碼(為了支援後者

,即文字內容)。

當需要執行一個完整內容的Http請求或者Http請求已經成功,伺服器要傳送響應到客戶端時,Http實體就會被建立。

如果要從Http實體中讀取內容,我們可以利用HTTPEntity類的getContent方法來獲取實體的輸入流

(java.io.InputStream),或者利用HTTPEntity的writeTo(OutputStream)方法來獲取輸出流,

這個方法會把所有的內容寫入到給定的流中。

當實體類已經被接受後,我們可以利用HTTPEntity的getContentType()和getContentLength()方法來讀取

Content-Type和Content-Length兩個頭訊息(如果有的話)。由於Content-Type包含mime-types的字元編碼,

比如text/plain或者text/html,HTTPEntity類的getContentEncoding()方法就是讀取這個編碼的。

如果頭資訊不存在,getContentLength()會返回-1,getContentType()會返回NULL。如果Content-Type資訊存在,

就會返回一個Header類

當為傳送訊息建立Http實體時,需要同時附加meta資訊。


1.5確保底層的資源連結

為了確保系統資源被正確地釋放,我們要麼管理Http實體的內容流、要麼關閉Http響應。

關閉Http實體內容流和關閉Http響應的區別在於,前者通過消耗掉Http實體內容來保持相關的http連線,

然而後者會立即關閉、丟棄http連線。

請注意HttpEntity的writeTo(OutputStream)方法,當Http實體被寫入到OutputStream後,也要確保釋放

系統資源。如果這個方法呼叫了HTTPEntity的getContent()方法,name它會有一個java.io.InputStream的

例項,我們需要在finally中關閉這個流。

但是也有這樣的情況,我們只需要獲取Http響應內容的一小部分,而獲取整個內容並、實現連線的可重複

性代價太大,這時我們可以通過關閉響應的方式來關閉內容輸入、輸出流。

1、關閉Http實體內容流

2、關閉Http響應,關閉Http響應後,連線變得不可用,所有的資源都將被釋放。

1.6消耗HTTP實體內容

HttpClient推薦使用HTTPEntity的getContent()方法或者HTTPEntity的writeTo(OutputStream)方法來消耗

掉HTTP實體內容。HttpClient也提供了EntityUtils這個類,這個類提供一些靜態方法可以容易的讀取Http實

體的內容和資訊。和以java.io.InputStream流讀取內容的方式比較,EntityUtils提供的方法可以以字串或

者位元組陣列的形式讀取Http實體。但是,不推薦使用EntityUtils這個類,除非目標伺服器發出的響應是可以信

任的,並且http響應實體的長度不會過大。

有些情況下,我們希望可以重複讀取Http實體的內容。這就需要把Http實體內容快取在記憶體或磁碟上。

最簡單的方法就是把HttpEntity轉換成BufferedHttpEntity,這樣就把原Http實體的內容緩衝到了記憶體中。

後面我們就可以重複讀取BufferedHttpEntity中的內容。


1.7建立HTTP實體內容

HttpClient提供了一些類,這些類可以通過http高效的輸出Http實體內容。

HttpClient提供的這幾個類涵蓋的常見的資料型別,如String,byte陣列,輸入流和檔案類

型:StringEntity,ByteArrayEntity,InputStreamEntity,FileEntity。

請注意由於InputStreamEntity只能從下層資料流中讀取一次,所以它是不能重複的。推薦,通過繼承HttpEntity這一個自包含的類來自定義HttpEntity類,而不是直接使用InputStreamEntity這個類。FileEntity就是一個很好的起點(FileEntity就是繼承的HttpEntity)。

1.8HTML表單

通過HttpClient提供的UrlEncodedFormEntity這個類來幫助實現這一過程


UrlEncodedFormEntity例項會使用所謂的Url編碼的方式對我們的引數進行編碼

1.9內容分塊

一般來說,推薦讓HttpClient自己根據Http訊息傳遞的特徵來選擇最合適的傳輸編碼。當然,如果非要手動控制也是可以的,可以通過設定HttpEntity的setChunked()為true。請注意:HttpClient僅會將這個引數

看成是一個建議。如果Http版本(如http1.0)不支援內容分塊,那麼這個引數就會被忽略


1.9Response Handlers

推薦使用ResponseHandler介面處理http響應,這個介面中有handleResponse(HttpResponse response)方法。使用這個方法,使用者完全不用擔心http連線管理器。當使用ResponseHandler時,HttpClient會自動的將Http連線釋放給Http管理器,即使http請求失敗了或者丟擲了異常。


2.0HttpClient介面

HttpClient介面沒有對Http請求的過程做特別的限制和詳細的規定,連線管理、狀態管理、授權管理和重定向處理這些功能都能單獨實現。HttpClient實際上就是一系列特殊的handler或者說策略介面的實現,這些handler(測試介面)負責處理http協議的某一方面,比如重定向、認證處理、有關連線永續性和keepalive持續時間的決策。這樣就允許使用者使用自定義的引數來代替預設配置,實現個性化功能。


2.1HttpClient的執行緒安全性

HttpClient已經實現了執行緒安全。所以在例項化HttpClient時,也要支援為多個請求使用。

2.2HttpClient的記憶體分配

當一個CloseableHttpClient的例項不再被使用,並且他的作用範圍即將失效,和它相關的連線必須要被關閉,關閉的方法可以呼叫CloseableHttpClient的close()方法。

2.3Http執行上下文

Http最初是一種無狀態、面向請求-響應的協議。實際中,我們希望能夠在一些邏輯相關的請求-響應中,保持狀態資訊。為了使應用程式可以保持Http的持續狀態,HttpClient允許http連線在特定的Http上下文中執行。如果在持續的http請求中使用了同樣的上下文,name這些請求就可以被分配到一個邏輯會話中。Http上下文就和一個java.util.Map<String,Object>功能類似。它實際是一個任意命名的值的集合。應用程式可以在Http請求執行前填充上下文的值,也可以在請求執行完畢後檢查上下文。

HttpContext可以包含任意型別的物件,因此如果再多執行緒中共享上下文會不安全。推薦每個執行緒都包含自己的http上下文

Http請求執行過程中,HttpClient會自動新增下面的屬性到Http上下文中:

HttpConnection的例項,表示客戶端與伺服器之間的連線

HttpHost的例項,表示要連線的目標伺服器

HttpRoute的例項,表示全部的連線路由器

HttpRequest的例項,表示Http請求。在執行上下文中,最終的HttpRequest物件會代表http訊息的狀態。Http/1.0和Http/1.1都預設使用相對的uri。但是如果使用了非隧道模式的代理伺服器,就會使用絕對路徑的uri。

HTTPResponse的例項,表示Http響應

java.lang.Boolean物件,表示是否請求被成功的傳送給目標伺服器

RequestConfig物件,表示http request的配置資訊

java.util.List<Uri>物件,表示Http響應中的所有重定向地址

我們可以使用HttpClientContext這個介面卡來簡化和上下文互動的過程


同一個邏輯會話中的多個Http請求,應該使用相同的Http上下文來執行,這樣就可以自動的在http請求中傳遞會話上下文和狀態資訊。


2.4異常處理

HttpClient會丟擲兩種型別的異常,一種是java.io.IOException,當遇到I/O異常時丟擲(socket超時,或者socket被重置);另一種是HTTPException,表示Http失敗,如Http協議使用不正確。通常認為,I/O錯誤時不致命、可修復的,而Http協議錯誤是致命了,不能自動修復的錯誤。

2.5Http傳輸安全

Http協議不能滿足所有型別的應用場景,Http是個簡單的面向協議的請求/響應的協議,當初它被設計用來支援靜態或者動態生成的內容檢索,之前從來沒有人想過讓它支援事務性操作。例如:Http伺服器成功接收、處理請求後,生成響應訊息,並且把狀態碼傳送給客戶端,這個過程是http協議應該保證的。但是,如果客戶端由於讀取超時、取消請求或者系統崩潰導致接受響應失敗,伺服器不會回滾這一事務。如果客戶端重新發送這個請求,伺服器就會重複的解析、執行這個事務。在一些情況下,這會導致應用程式的資料損壞和應用程式的狀態不一致。

即使Http當初設計是不支援事務操作,但是它仍可以作為傳輸協議的某些關鍵程式提供服務。為了保證Http傳輸層的安全性,系統必須保證應用層上的http方法的冪等性。

2.5.1方法的冪等性

應用程式需要正確的處理同一方法多次執行造成的影響。新增一個具有唯一性的id就能避免重複執行同一個邏輯請求。

HttpClient預設把非實體方法get、head方法看做冪等方法,把實體方法post、put方法看作是非冪等方法。

2.5.2異常自動修復

HttpClient預設情況下會自動修復I/O異常。這種自動修復僅限於修復幾個公認安全的異常

HttpClient不會嘗試修復任何邏輯或者http協議錯誤(即從HTTPException衍生出來的異常)

如果首次執行失敗,HttpClient會自動再次傳送冪等的方法

HttpClient會自動再次傳送遇到transport異常的方法,前提是Http請求仍舊保持著連線(如http請求沒有全部發送到目標伺服器,HttpClient會再次嘗試傳送)

2.5.3請求重試Handler

如果要自定義異常處理機制,需要實現HttpRequestHandler介面

2.6終止請求

由於目標伺服器負載過高,或者客戶端目前有太多請求積壓,http請求不能在指定時間內執行完畢。這時候終止這個請求,釋放阻塞I/O的程序,是非常必要的。通過HttpClient執行的Http請求,在任何狀態下都能通過呼叫HttpUriRequest的abort()方法來終止。這個方法是執行緒安全的,並且能在任何執行緒中呼叫。當Http請求被終止了,本執行緒(即使現在正在阻塞I/O)也會通過丟擲一個InterruptedIOException異常,來釋放資源。

2.7Http協議攔截器

Http協議攔截器是一種實現一個特定的方面的Http協議的程式碼程式。通常情況下,協議攔截器會將一個或多個頭訊息加入到接受或者傳送的訊息中。協議攔截器也可以操作訊息的內容實體——訊息內容的壓縮/解壓縮就是一個很好的例子。