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協議的程式碼程式。通常情況下,協議攔截器會將一個或多個頭訊息加入到接受或者傳送的訊息中。協議攔截器也可以操作訊息的內容實體——訊息內容的壓縮/解壓縮就是一個很好的例子。