轉自:http://www.yeetrack.com/?p=779javascript
Http協議應該是互聯網中最重要的協議。持續增加的web服務、可聯網的家用電器等都在繼承並拓展着Http協議,向着瀏覽器以外的方向發展。html
雖然jdk中的java.net包中提供了一些基本的方法,經過http協議來訪問網絡資源,可是大多數場景下,它都不夠靈活和強大。HttpClient致力於填補這個空白,它能夠提供有效的、最新的、功能豐富的包來實現http客戶端。java
爲了拓展,HttpClient即支持基本的http協議,還支持http-aware客戶端程序,如web瀏覽器,Webservice客戶端,以及利用or拓展http協議的分佈式系統。web
一、HttpClient的範圍/特性算法
二、HttpClient不能作的事情apache
HttpClient最基本的功能就是執行Http方法。一個Http方法的執行涉及到一個或者多個Http請求/Http響應的交 互,一般這個過程都會自動被HttpClient處理,對用戶透明。用戶只須要提供Http請求對象,HttpClient就會將http請求發送給目標 服務器,而且接收服務器的響應,若是http請求執行不成功,httpclient就會拋出異樣。json
下面是個很簡單的http請求執行的例子:windows
全部的Http請求都有一個請求行(request line),包括方法名、請求的URI和Http版本號。api
HttpClient支持HTTP/1.1這個版本定義的全部Http方法:GET
,HEAD
,POST
,PUT
,DELETE
,TRACE和
OPTIONS。對於每一種http方法,HttpClient都定義了一個相應的類:
HttpGet,
HttpHead,
HttpPost,
HttpPut,
HttpDelete,
HttpTrace和
HttpOpquertions。數組
Request-URI即統一資源定位符,用來標明Http請求中的資源。Http request URIs包含協議名、主機名、主機端口(可選)、資源路徑、query(可選)和片斷信息(可選)。
HttpClient提供URIBuilder
工具類來簡化URIs的建立和修改過程。
上述代碼會在控制檯輸出:
服務器收到客戶端的http請求後,就會對其進行解析,而後把響應發給客戶端,這個響應就是HTTP response.HTTP響應第一行是協議版本,以後是數字狀態碼和相關聯的文本段。
一個Http消息能夠包含一系列的消息頭,用來對http消息進行描述,好比消息長度,消息類型等等。HttpClient提供了方法來獲取、添加、移除、枚舉消息頭。
最有效的獲取指定類型的消息頭的方法仍是使用HeaderIterator
接口。
上述代碼會在控制檯輸出:
HeaderIterator也提供很是便捷的方式,將Http消息解析成單獨的消息頭元素。
上述代碼會在控制檯輸出:
Http消息能夠攜帶http實體,這個http實體既能夠是http請求,也能夠是http響應的。Http實體,能夠在某些 http請求或者響應中發現,但不是必須的。Http規範中定義了兩種包含請求的方法:POST和PUT。HTTP響應通常會包含一個內容實體。固然這條 規則也有異常狀況,如Head方法的響應,204沒有內容,304沒有修改或者205內容資源重置。
HttpClient根據來源的不一樣,劃分了三種不一樣的Http實體內容。
當從Http響應中讀取內容時,上面的三種區分對於鏈接管理器來講是很是重要的。對於由應用程序建立並且只使用HttpClient發 送的請求實體,streamed和self-contained兩種類型的不一樣就不那麼重要了。這種狀況下,建議考慮如streamed流式這種不能重複 的實體,和能夠重複的self-contained自我包含式實體。
一個實體是可重複的,也就是說它的包含的內容能夠被屢次讀取。這種屢次讀取只有self contained(自包含)的實體能作到(好比ByteArrayEntity
或者StringEntity
)。
因爲一個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信息。
爲了確保系統資源被正確地釋放,咱們要麼管理Http實體的內容流、要麼關閉Http響應。
請注意HttpEntity
的writeTo(OutputStream)
方法,當Http實體被寫入到OutputStream後,也要確保釋放系統資源。若是這個方法內調用了HttpEntity
的getContent()
方法,那麼它會有一個java.io.InpputStream
的實例,咱們須要在finally中關閉這個流。
可是也有這樣的狀況,咱們只須要獲取Http響應內容的一小部分,而獲取整個內容並、實現鏈接的可重複性代價太大,這時咱們能夠經過關閉響應的方式來關閉內容輸入、輸出流。
HttpClient推薦使用HttpEntity
的getConent()
方法或者HttpEntity
的writeTo(OutputStream)
方法來消耗掉Http實體內容。HttpClient也提供了EntityUtils
這個類,這個類提供一些靜態方法能夠更容易地讀取Http實體的內容和信息。和以java.io.InputStream
流讀取內容的方式相比,EntityUtils提供的方法能夠以字符串或者字節數組的形式讀取Http實體。可是,強烈不推薦使用EntityUtils
這個類,除非目標服務器發出的響應是可信任的,而且http響應實體的長度不會過大。
有些狀況下,咱們但願能夠重複讀取Http實體的內容。這就須要把Http實體內容緩存在內存或者磁盤上。最簡單的方法就是把Http Entity轉化成BufferedHttpEntity
,這樣就把原Http實體的內容緩衝到了內存中。後面咱們就能夠重複讀取BufferedHttpEntity中的內容。
HttpClient提供了一些類,這些類能夠經過http鏈接高效地輸出Http實體內容。HttpClient提供的這幾個類涵蓋的常見的數據類型,如String,byte數組,輸入流,和文件類型:StringEntity
,ByteArrayEntity
,InputStreamEntity
,FileEntity
。
InputStreamEntity
只能從下層的數據流中讀取一次,因此它是不能重複的。推薦,經過繼承
HttpEntity
這個自包含的類來自定義HttpEntity類,而不是直接使用
InputStreamEntity
這個類。
FileEntity
就是一個很好的起點(FileEntity就是繼承的HttpEntity)。
不少應用程序須要模擬提交Html表單的過程,舉個例子,登錄一個網站或者將輸入內容提交給服務器。HttpClient提供了UrlEncodedFormEntity
這個類來幫助實現這一過程。
UrlEncodedFormEntity
實例會使用所謂的Url編碼的方式對咱們的參數進行編碼,產生的結果以下:
通常來講,推薦讓HttpClient本身根據Http消息傳遞的特徵來選擇最合適的傳輸編碼。固然,若是非要手動控制也是能夠的,能夠經過設置HttpEntity
的setChunked()
爲true。請注意:HttpClient僅會將這個參數當作是一個建議。若是Http的版本(如http 1.0)不支持內容分塊,那麼這個參數就會被忽略。
最簡單也是最方便的處理http響應的方法就是使用ResponseHandler
接口,這個接口中有handleResponse(HttpResponse response)
方法。使用這個方法,用戶徹底不用關心http鏈接管理器。當使用ResponseHandler
時,HttpClient會自動地將Http鏈接釋放給Http管理器,即便http請求失敗了或者拋出了異常。
對於Http請求執行過程來講,HttpClient
的接口有着必不可少的做用。HttpClient
接口沒有對Http請求的過程作特別的限制和詳細的規定,鏈接管理、狀態管理、受權信息和重定向處理這些功能都單獨實現。這樣用戶就能夠更簡單地拓展接口的功能(好比緩存響應內容)。
通常說來,HttpClient實際上就是一系列特殊的handler或者說策略接口的實現,這些handler(測試接口)負責着處 理Http協議的某一方面,好比重定向、認證處理、有關鏈接持久性和keep alive持續時間的決策。這樣就容許用戶使用自定義的參數來代替默認配置,實現個性化的功能。
HttpClient
已經實現了線程安全。因此但願用戶在實例化HttpClient時,也要支持爲多個請求使用。
當一個CloseableHttpClient
的實例再也不被使用,而且它的做用範圍即將失效,和它相關的鏈接必須被關閉,關閉方法能夠調用CloseableHttpClient
的close()
方法。
最初,Http被設計成一種無狀態的、面向請求-響應的協議。然而,在實際使用中,咱們但願可以在一些邏輯相關的請求-響應中,保持狀 態信息。爲了使應用程序能夠保持Http的持續狀態,HttpClient容許http鏈接在特定的Http上下文中執行。若是在持續的http請求中使 用了一樣的上下文,那麼這些請求就能夠被分配到一個邏輯會話中。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請求中傳遞會話上下文和狀態信息。
在下面的例子中,咱們在開頭設置的參數,會被保存在上下文中,而且會應用到後續的http請求中。
HttpClient會被拋出兩種類型的異常,一種是java.io.IOException
,當遇到I/O異常時拋出(socket超時,或者socket被重置);另外一種是HttpException
,表示Http失敗,如Http協議使用不正確。一般認爲,I/O錯誤時不致命、可修復的,而Http協議錯誤是致命了,不能自動修復的錯誤。
Http協議不能知足全部類型的應用場景,咱們須要知道這點。Http是個簡單的面向協議的請求/響應的協議,當初它被設計用來支持靜 態或者動態生成的內容檢索,以前歷來沒有人想過讓它支持事務性操做。例如,Http服務器成功接收、處理請求後,生成響應消息,而且把狀態碼發送給客戶 端,這個過程是Http協議應該保證的。可是,若是客戶端因爲讀取超時、取消請求或者系統崩潰致使接收響應失敗,服務器不會回滾這一事務。若是客戶端從新 發送這個請求,服務器就會重複的解析、執行這個事務。在一些狀況下,這會致使應用程序的數據損壞和應用程序的狀態不一致。
即便Http當初設計是不支持事務操做,可是它仍舊能夠做爲傳輸協議爲某些關鍵程序提供服務。爲了保證Http傳輸層的安全性,系統必須保證應用層上的http方法的冪等性。
HTTP/1.1規範中是這樣定義冪等方法的,Methods can also have the property of 「idempotence」 in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request。用其餘話來講,應用程序須要正確地處理同一方法屢次執行形成的影響。添加一個具備惟一性的id就能避免重複執行同一個邏輯請求,問題解 決。
請知曉,這個問題不僅是HttpClient纔會有,基於瀏覽器的應用程序也會遇到Http方法不冪等的問題。
HttpClient默認把非實體方法get
、head
方法看作冪等方法,把實體方法post
、put
方法看作非冪等方法。
默認狀況下,HttpClient會嘗試自動修復I/O異常。這種自動修復僅限於修復幾個公認安全的異常。
若是要自定義異常處理機制,咱們須要實現HttpRequestRetryHandler
接口。
有時候因爲目標服務器負載太高或者客戶端目前有太多請求積壓,http請求不能在指定時間內執行完畢。這時候終止這個請求,釋放阻塞I/O的進程,就顯得很必要。經過HttpClient執行的Http請求,在任何狀態下都能經過調用HttpUriRequest
的abort()
方法來終止。這個方法是線程安全的,而且能在任何線程中調用。當Http請求被終止了,本線程(即便如今正在阻塞I/O)也會經過拋出一個InterruptedIOException
異常,來釋放資源。
HTTP協議攔截器是一種實現一個特定的方面的HTTP協議的代碼程序。一般狀況下,協議攔截器會將一個或多個頭消息加入到接受或者發 送的消息中。協議攔截器也能夠操做消息的內容實體—消息內容的壓縮/解壓縮就是個很好的例子。一般,這是經過使用「裝飾」開發模式,一個包裝實體類用於裝 飾原來的實體來實現。一個攔截器能夠合併,造成一個邏輯單元。
協議攔截器能夠經過共享信息協做——好比處理狀態——經過HTTP執行上下文。協議攔截器可使用Http上下文存儲一個或者多個連續請求的處理狀態。
一般,只要攔截器不依賴於一個特定狀態的http上下文,那麼攔截執行的順序就無所謂。若是協議攔截器有相互依賴關係,必須以特定的順序執行,那麼它們應該按照特定的順序加入到協議處理器中。
協議處理器必須是線程安全的。相似於servlets,協議攔截器不該該使用變量實體,除非訪問這些變量是同步的(線程安全的)。
下面是個例子,講述了本地的上下文時如何在連續請求中記錄處理狀態的:
上面代碼在發送http請求時,會自動添加Count這個header,可使用wireshark抓包查看。
HttpClient會自動處理全部類型的重定向,除了那些Http規範明確禁止的重定向。See Other (status code 303) redirects on POST and PUT requests are converted to GET requests as required by the HTTP specification. 咱們可使用自定義的重定向策略來放鬆Http規範對Post方法重定向的限制。
HttpClient在請求執行過程當中,常常須要重寫請求的消息。 HTTP/1.0和HTTP/1.1都默認使用相對的uri路徑。一樣,原始的請求可能會被一次或者屢次的重定向。最終結對路徑的解釋可使用最初的請求和上下文。URIUtils
類的resolve
方法能夠用於將攔截的絕對路徑構建成最終的請求。這個方法包含了最後一個分片標識符或者原始請求。
兩個主機創建鏈接的過程是很複雜的一個過程,涉及到多個數據包的交換,而且也很耗時間。Http鏈接須要的三次握手開銷很大,這一開銷對於比較小的http消息來講更大。可是若是咱們直接使用已經創建好的http鏈接,這樣花費就比較小,吞吐率更大。
HTTP/1.1默認就支持Http鏈接複用。兼容HTTP/1.0的終端也能夠經過聲明來保持鏈接,實現鏈接複用。HTTP代理也可 以在必定時間內保持鏈接不釋放,方便後續向這個主機發送http請求。這種保持鏈接不釋放的狀況其實是創建的持久鏈接。HttpClient也支持持久 鏈接。
HttpClient既能夠直接、又能夠經過多箇中轉路由(hops)和目標服務器創建鏈接。HttpClient把路由分爲三種plain(明文 ),tunneled(隧道)和layered(分層)。隧道鏈接中使用的多箇中間代理被稱做代理鏈。
客戶端直接鏈接到目標主機或者只經過了一箇中間代理,這種就是Plain路由。客戶端經過第一個代理創建鏈接,經過代理鏈 tunnelling,這種狀況就是Tunneled路由。不經過中間代理的路由不可能時tunneled路由。客戶端在一個已經存在的鏈接上進行協議分 層,這樣創建起來的路由就是layered路由。協議只能在隧道—>目標主機,或者直接鏈接(沒有代理),這兩種鏈路上進行分層。
RouteInfo
接口包含了數據包發送到目標主機過程當中,通過的路由信息。HttpRoute
類繼承了RouteInfo
接口,是RouteInfo
的具體實現,這個類是不容許修改的。HttpTracker
類也實現了RouteInfo
接口,它是可變的,HttpClient會在內部使用這個類來探測到目標主機的剩餘路由。HttpRouteDirector
是個輔助類,能夠幫助計算數據包的下一步路由信息。這個類也是在HttpClient內部使用的。
HttpRoutePlanner
接口能夠用來表示基於http上下文狀況下,客戶端到服務器的路由計算策略。HttpClient有兩個HttpRoutePlanner
的實現類。SystemDefaultRoutePlanner
這個類基於java.net.ProxySelector
,它默認使用jvm的代理配置信息,這個配置信息通常來自系統配置或者瀏覽器配置。DefaultProxyRoutePlanner
這個類既不使用java自己的配置,也不使用系統或者瀏覽器的配置。它一般經過默認代理來計算路由信息。
爲了防止經過Http消息傳遞的信息不被未受權的第三方獲取、截獲,Http可使用SSL/TLS協議來保證http傳輸安全,這個協議是當前使用最廣的。固然也可使用其餘的加密技術。可是一般狀況下,Http信息會在加密的SSL/TLS鏈接上進行傳輸。
Http鏈接是複雜,有狀態的,線程不安全的對象,因此它必須被妥善管理。一個Http鏈接在同一時間只能被一個線程訪問。HttpClient使用一個叫作Http鏈接管理器的特殊實體類來管理Http鏈接,這個實體類要實現HttpClientConnectionManager
接口。Http鏈接管理器在新建http鏈接時,做爲工廠類;管理持久http鏈接的生命週期;同步持久鏈接(確保線程安全,即一個http鏈接同一時間只能被一個線程訪問)。Http鏈接管理器和ManagedHttpClientConnection
的實例類一塊兒發揮做用,ManagedHttpClientConnection
實 體類能夠看作http鏈接的一個代理服務器,管理着I/O操做。若是一個Http鏈接被釋放或者被它的消費者明確表示要關閉,那麼底層的鏈接就會和它的代 理進行分離,而且該鏈接會被交還給鏈接管理器。這是,即便服務消費者仍然持有代理的引用,它也不能再執行I/O操做,或者更改Http鏈接的狀態。
下面的代碼展現瞭如何從鏈接管理器中取得一個http鏈接:
若是要終止鏈接,能夠調用ConnectionRequest
的cancel()
方法。這個方法會解鎖被ConnectionRequest
類get()
方法阻塞的線程。
BasicHttpClientConnectionManager
是個簡單的鏈接管理器,它一次只能管理一個鏈接。儘管這個類是線程安全的,它在同一時間也只能被一個線程使用。BasicHttpClientConnectionManager
會盡可能重用舊的鏈接來發送後續的請求,而且使用相同的路由。若是後續請求的路由和舊鏈接中的路由不匹配,BasicHttpClientConnectionManager
就會關閉當前鏈接,使用請求中的路由從新創建鏈接。若是當前的鏈接正在被佔用,會拋出java.lang.IllegalStateException
異常。
相對BasicHttpClientConnectionManager
來講,PoolingHttpClientConnectionManager
是 個更復雜的類,它管理着鏈接池,能夠同時爲不少線程提供http鏈接請求。Connections are pooled on a per route basis.當請求一個新的鏈接時,若是鏈接池有有可用的持久鏈接,鏈接管理器就會使用其中的一個,而不是再建立一個新的鏈接。
PoolingHttpClientConnectionManager
維護的鏈接數在每一個路由基礎和總數上都有限制。默認,每一個路由基礎上的鏈接不超過2個,總鏈接數不能超過20。在實際應用中,這個限制可能會過小了,尤爲是當服務器也使用Http協議時。
下面的例子演示了若是調整鏈接池的參數:
當一個HttpClient的實例不在使用,或者已經脫離它的做用範圍,咱們須要關掉它的鏈接管理器,來關閉掉全部的鏈接,釋放掉這些鏈接佔用的系統資源。
當使用了請求鏈接池管理器(好比PoolingClientConnectionManager
)後,HttpClient就能夠同時執行多個線程的請求了。
PoolingClientConnectionManager
會根據它的配置來分配請求鏈接。若是鏈接池中的全部鏈接都被佔用了,那麼後續的請求就會被阻塞,直到有鏈接被釋放回鏈接池中。爲了防止永遠阻塞的狀況發生,咱們能夠把http.conn-manager.timeout
的值設置成一個整數。若是在超時時間內,沒有可用鏈接,就會拋出ConnectionPoolTimeoutException
異常。
即便HttpClient的實例是線程安全的,能夠被多個線程共享訪問,可是仍舊推薦每一個線程都要有本身專用實例的HttpContext。
下面是GetThread類的定義:
經典阻塞I/O模型的一個主要缺點就是隻有當組側I/O時,socket才能對I/O事件作出反應。當鏈接被管理器收回後,這個鏈接仍 然存活,可是卻沒法監控socket的狀態,也沒法對I/O事件作出反饋。若是鏈接被服務器端關閉了,客戶端監測不到鏈接的狀態變化(也就沒法根據鏈接狀 態的變化,關閉本地的socket)。
HttpClient爲了緩解這一問題形成的影響,會在使用某個鏈接前,監測這個鏈接是否已通過時,若是服務器端關閉了鏈接,那麼鏈接 就會失效。這種過期檢查並非100%有效,而且會給每一個請求增長10到30毫秒額外開銷。惟一一個可行的,且does not involve a one thread per socket model for idle connections的解決辦法,是創建一個監控線程,來專門回收因爲長時間不活動而被斷定爲失效的鏈接。這個監控線程能夠週期性的調用ClientConnectionManager
類的closeExpiredConnections()
方法來關閉過時的鏈接,回收鏈接池中被關閉的鏈接。它也能夠選擇性的調用ClientConnectionManager
類的closeIdleConnections()
方法來關閉一段時間內不活動的鏈接。
Http規範沒有規定一個持久鏈接應該保持存活多久。有些Http服務器使用非標準的Keep-Alive
頭消息和客戶端進行交互,服務器端會保持數秒時間內保持鏈接。HttpClient也會利用這個頭消息。若是服務器返回的響應中沒有包含Keep-Alive
頭消息,HttpClient會認爲這個鏈接能夠永遠保持。然而,不少服務器都會在不通知客戶端的狀況下,關閉必定時間內不活動的鏈接,來節省服務器資源。在某些狀況下默認的策略顯得太樂觀,咱們可能須要自定義鏈接存活策略。
Http鏈接使用java.net.Socket
類來傳輸數據。這依賴於ConnectionSocketFactory
接口來建立、初始化和鏈接socket。這樣也就容許HttpClient的用戶在代碼運行時,指定socket初始化的代碼。PlainConnectionSocketFactory
是默認的建立、初始化明文socket(不加密)的工廠類。
建立socket和使用socket鏈接到目標主機這兩個過程是分離的,因此咱們能夠在鏈接發生阻塞時,關閉socket鏈接。
LayeredConnectionSocketFactory
是ConnectionSocketFactory
的拓展接口。分層socket工廠類能夠在明文socket的基礎上建立socket鏈接。分層socket主要用於在代理服務器之間建立安全socket。HttpClient使用SSLSocketFactory
這個類實現安全socket,SSLSocketFactory
實現了SSL/TLS分層。請知曉,HttpClient沒有自定義任何加密算法。它徹底依賴於Java加密標準(JCE)和安全套接字(JSEE)拓展。
自定義的socket工廠類能夠和指定的協議(Http、Https)聯繫起來,用來建立自定義的鏈接管理器。
HttpClient使用SSLSocketFactory
來建立ssl鏈接。SSLSocketFactory
容許用戶高度定製。它能夠接受javax.net.ssl.SSLContext
這個類的實例做爲參數,來建立自定義的ssl鏈接。
除了信任驗證和在ssl/tls協議層上進行客戶端認證,HttpClient一旦創建起鏈接,就能夠選擇性驗證目標域名和存儲在X.509證書中的域名是否一致。這種驗證能夠爲服務器信任提供額外的保障。X509HostnameVerifier
接口表明主機名驗證的策略。在HttpClient中,X509HostnameVerifier
有三個實現類。重要提示:主機名有效性驗證不該該和ssl信任驗證混爲一談。
StrictHostnameVerifier
: 嚴格的主機名驗證方法和 java 1.4,1.5,1.6驗證方法相同。和IE6的方式也大體相同。這種驗證方式符合RFC 2818通配符。The hostname must match either the first CN, or any of the subject-alts. A wildcard can occur in the CN, and in any of the subject-alts.BrowserCompatHostnameVerifier
: 這種驗證主 機名的方法,和Curl及firefox一致。The hostname must match either the first CN, or any of the subject-alts. A wildcard can occur in the CN, and in any of the subject-alts.StrictHostnameVerifier
和BrowserCompatHostnameVerifier
方式惟一不一樣的地方就是,帶有通配符的域名(好比*.yeetrack.com),BrowserCompatHostnameVerifier
方式在匹配時會匹配全部的的子域名,包括 a.b.yeetrack.com .AllowAllHostnameVerifier
: 這種方式不對主機名進行驗證,驗證功能被關閉,是個空操做,因此它不會拋出javax.net.ssl.SSLException
異常。HttpClient默認使用BrowserCompatHostnameVerifier
的驗證方式。若是須要,咱們能夠手動執行驗證方式。
儘管,HttpClient支持複雜的路由方案和代理鏈,它一樣也支持直接鏈接或者只經過一跳的鏈接。
使用代理服務器最簡單的方式就是,指定一個默認的proxy參數。
咱們也可讓HttpClient去使用jre的代理服務器。
又或者,咱們也能夠手動配置RoutePlanner
,這樣就能夠徹底控制Http路由的過程。
最初,Http被設計成一個無狀態的,面向請求/響應的協議,因此它不能在邏輯相關的http請求/響應中保持狀態會話。因爲愈來愈多的系統使用http協議,其中包括http歷來沒有想支持的系統,好比電子商務系統。所以,http支持狀態管理就很必要了。
當時的web客戶端和服務器軟件領先者,網景(netscape)公司,最早在他們的產品中支持http狀態管理,而且制定了一些專有 規範。後來,網景經過發規範草案,規範了這一機制。這些努力促成 RFC standard track制定了標準的規範。可是,如今多數的應用的狀態管理機制都在使用網景公司的規範,而網景的規範和官方規定是不兼容的。所以全部的瀏覽器開發這都 被迫兼容這兩種協議,從而致使協議的不統一。
所謂的Http cookie就是一個token或者很短的報文信息,http代理和服務器能夠經過cookie來維持會話狀態。網景的工程師把它們稱做「magic cookie」。
HttpClient使用Cookie
接口來表明cookie。簡單說來,cookie就是一個鍵值對。通常,cookie也會包含版本號、域名、路徑和cookie有效期。
SetCookie
接口能夠表明服務器發給http代理的一個set-cookie響應頭,在瀏覽器中,這個set-cookie響應頭能夠寫入cookie,以便保持會話狀態。SetCookie2
接口對SetCookie
接口進行了拓展,添加了Set-Cookie2
方法。
ClientCookie
接口繼承了Cookie
接口,並進行了功能拓展,好比它能夠取出服務器發送過來的原始cookie的值。生成頭消息是很重要的,由於只有當cookie被指定爲Set-Cookie
或者Set-Cookie2
時,它才須要包括一些特定的屬性。
兼容網景的規範,可是不兼容官方規範的cookie,是版本0. 兼容官方規範的版本,將會是版本1。版本1中的Cookie可能和版本0工做機制有差別。
下面的代碼,建立了網景版本的Cookie:
下面的代碼,建立標準版本的Cookie。注意,標準版本的Cookie必須保留服務器發送過來的Cookie全部屬性。
下面的代碼,建立了Set-Cookie2
兼容cookie。
CookieSpec
接口表明了Cookie管理規範。Cookie管理規範規定了:
Set-Cookie
和Set-Cookie2
(可選)頭消息的規則HttpClient有下面幾種CookieSpec
規範:
咱們能夠在建立Http client的時候指定Cookie測試,若是須要,也能夠在執行http請求的時候,進行覆蓋指定。
若是咱們要自定義Cookie測試,就要本身實現CookieSpec
接口,而後建立一個CookieSpecProvider
接口來新建、初始化自定義CookieSpec
接口,最後把CookieSpecProvider
註冊到HttpClient中。一旦咱們註冊了自定義策略,就能夠像其餘標準策略同樣使用了。
HttpClient可使用任何存儲方式的cookie store,只要這個cookie store實現了CookieStore
接口。默認的CookieStore經過java.util.ArrayList
簡單實現了BasicCookieStore
。存在在BasicCookieStore
中的Cookie,當載體對象被當作垃圾回收掉後,就會丟失。若是必要,用戶能夠本身實現更爲複雜的方式。
在Http請求執行過程當中,HttpClient會自動向執行上下文中添加下面的狀態管理對象:
Lookup
對象 表明實際的cookie規範registry。在當前上下文中的這個值優先於默認值。CookieSpec
對象 表明實際的Cookie規範。CookieOrigin
對象 表明實際的origin server的詳細信息。CookieStore
對象 表示Cookie store。這個屬性集中的值會取代默認值。本地的HttpContext
對象能夠用來在Http請求執行前,自定義Http狀態管理上下文;或者測試 http請求執行完畢後上下文的狀態。咱們也能夠在不一樣的線程中使用不一樣的執行上下文。咱們在http請求層指定的cookie規範集和cookie store會覆蓋在http Client層級的默認值。
HttpClient既支持HTTP標準規範定義的認證模式,又支持一些普遍使用的非標準認證模式,好比NTLM和SPNEGO。
任何用戶認證的過程,都須要一系列的憑證來肯定用戶的身份。最簡單的用戶憑證能夠是用戶名和密碼這種形式。UsernamePasswordCredentials
這個類能夠用來表示這種狀況,這種憑據包含明文的用戶名和密碼。
這個類對於HTTP標準規範中定義的認證模式來講已經足夠了。
上述代碼會在控制檯輸出:
NTCredentials
是微軟的windows系統使用的一種憑據,包含username、password,還包括一系列其餘的屬性,好比用戶所在的域名。在Microsoft Windows的網絡環境中,同一個用戶能夠屬於不一樣的域,因此他也就有不一樣的憑據。
上述代碼輸出:
AutoScheme
接口表示一個抽象的面向挑戰/響應的認證方案。一個認證方案要支持下面的功能:
請注意:一個認證方案多是有狀態的,由於它可能涉及到一系列的挑戰/響應。
HttpClient實現了下面幾種AutoScheme
:
憑證providers旨在維護一套用戶的憑證,當須要某種特定的憑證時,providers就應該能產生這種憑證。認證的具體內容包 括主機名、端口號、realm name和認證方案名。當使用憑據provider的時候,咱們能夠很模糊的指定主機名、端口號、realm和認證方案,不用寫的很精確。由於,憑據 provider會根據咱們指定的內容,篩選出一個最匹配的方案。
只要咱們自定義的憑據provider實現了CredentialsProvider
這個接口,就能夠在HttpClient中使用。默認的憑據provider叫作BasicCredentialsProvider
,它使用java.util.HashMap
對CredentialsProvider
進行了簡單的實現。
上面代碼輸出:
HttpClient依賴AuthState
類去跟蹤認證過程當中的狀態的詳細信息。在Http請求過程當中,HttpClient建立兩個AuthState
實例:一個用於目標服務器認證,一個用於代理服務器認證。若是服務器或者代理服務器須要用戶的受權信息,AuthScope
、AutoScheme
和認證信息就會被填充到兩個AuthScope
實例中。經過對AutoState
的檢測,咱們能夠肯定請求的受權類型,肯定是否有匹配的AuthScheme
,肯定憑據provider根據指定的受權類型是否成功生成了用戶的受權信息。
在Http請求執行過程當中,HttpClient會向執行上下文中添加下面的受權對象:
Lookup
對象,表示使用的認證方案。這個對象的值能夠在本地上下文中進行設置,來覆蓋默認值。CredentialsProvider
對象,表示認證方案provider,這個對象的值能夠在本地上下文中進行設置,來覆蓋默認值。AuthState
對象,表示目標服務器的認證狀態,這個對象的值能夠在本地上下文中進行設置,來覆蓋默認值。AuthState
對象,表示代理服務器的認證狀態,這個對象的值能夠在本地上下文中進行設置,來覆蓋默認值。AuthCache
對象,表示認證數據的緩存,這個對象的值能夠在本地上下文中進行設置,來覆蓋默認值。咱們能夠在請求執行前,自定義本地HttpContext
對象來設置須要的http認證上下文;也能夠在請求執行後,再檢測HttpContext
的狀態,來查看受權是否成功。
從版本4.1開始,HttpClient就會自動緩存驗證經過的認證信息。可是爲了使用這個緩存的認證信息,咱們必須在同一個上下文中執行邏輯相關的請求。一旦超出該上下文的做用範圍,緩存的認證信息就會失效。
HttpClient默認不支持搶先認證,由於一旦搶先認證被誤用或者錯用,會致使一系列的安全問題,好比會把用戶的認證信息以明文的方式發送給未受權的第三方服務器。所以,須要用戶本身根據本身應用的具體環境來評估搶先認證帶來的好處和帶來的風險。
即便如此,HttpClient仍是容許咱們經過配置來啓用搶先認證,方法是提早填充認證信息緩存到上下文中,這樣,以這個上下文執行的方法,就會使用搶先認證。
從版本4.1開始,HttpClient就全面支持NTLMv一、NTLMv2和NTLM2認證。當人咱們能夠仍舊使用外部的NTLM引擎(好比Samba開發的JCIFS庫)做爲與Windows互操做性程序的一部分。
相比Basic
和Digest
認證,NTLM認證要明顯須要更多的計算開銷,性 能影響也比較大。這也多是微軟把NTLM協議設計成有狀態鏈接的主要緣由之一。也就是說,NTLM鏈接一旦創建,用戶的身份就會在其整個生命週期和它相 關聯。NTLM鏈接的狀態性使得鏈接持久性更加複雜,The stateful nature of NTLM connections makes connection persistence more complex, as for the obvious reason persistent NTLM connections may not be re-used by users with a different user identity. HttpClient中標準的鏈接管理器就能夠管理有狀態的鏈接。可是,同一會話中邏輯相關的請求,必須使用相同的執行上下文,這樣才能使用用戶的身份信 息。不然,HttpClient就會結束舊的鏈接,爲了獲取被NTLM協議保護的資源,而爲每一個HTTP請求,建立一個新的Http鏈接。更新關於 Http狀態鏈接的信息,點擊此處。
因爲NTLM鏈接是有狀態的,通常推薦使用比較輕量級的方法來處罰NTLM認證(如GET、Head方法),而後使用這個已經創建的鏈接在執行相對重量級的方法,尤爲是須要附件請求實體的請求(如POST、PUT請求)。
SPNEGO(Simple and Protected GSSAPI Megotiation Mechanism),當雙方均不知道對方能使用/提供什麼協議的狀況下,可使用SP認證協議。這種協議在Kerberos認證方案中常用。It can wrap other mechanisms, however the current version in HttpClient is designed solely with Kerberos in mind.
SPNEGO認證方案兼容Sun java 1.5及以上版本。可是強烈推薦jdk1.6以上。Sun的JRE提供的類就已經幾乎徹底能夠處理Kerberos和SPNEGO token。這就意味着,須要設置不少的GSS類。SpnegoScheme
是個很簡單的類,能夠用它來handle marshalling the tokens and 讀寫正確的頭消息。
最好的開始方法就是從示例程序中找到KerberosHttpClient.java
這個文件,嘗試讓它運行起來。運行過程有可能會出現不少問題,可是若是人品比較高可能會順利一點。這個文件會提供一些輸出,來幫咱們調試。
在Windows系統中,應該默認使用用戶的登錄憑據;固然咱們也可使用kinit
來覆蓋這個憑據,好比$JAVA_HOME\bin\kinit testuser@AD.EXAMPLE.NET
,這在咱們測試和調試的時候就顯得頗有用了。若是想用回Windows默認的登錄憑據,刪除kinit建立的緩存文件便可。
確保在krb5.conf文件中列出domain_realms
。這能解決不少沒必要要的問題。
下面的這份文檔是針對Windows系統的,可是不少信息一樣適合Unix。
org.ietf.jgss
這個類有不少的配置參數,這些參數大部分都在krb5.conf/krb5.ini
文件中配置。更多的信息,參考此處。
下面是一個基本的login.conf文件,使用於Windows平臺的IIS和JBoss Negotiation模塊。
系統配置文件java.security.auth.login.config
能夠指定login.conf
文件的路徑。login.conf
的內容可能會是下面的樣子:
若是沒有手動指定,系統會使用默認配置。若是要手動指定,能夠在java.security.krb5.conf
中設置系統變量,指定krb5.conf
的路徑。krb5.conf
的內容多是下面的樣子:
爲了容許Windows使用當前用戶的tickets,javax.security.auth.useSubjectCredsOnly
這個系統變量應該設置成false
,而且須要在Windows註冊表中添加allowtgtsessionkey
這個項,並且要allow session keys to be sent in the Kerberos Ticket-Granting Ticket.
Windows Server 2003和Windows 2000 SP4,配置以下:
Windows XP SP2 配置以下:
HttpClient從4.2開始支持快速api。快速api僅僅實現了HttpClient的基本功能,它只要用於一些不須要靈活性的簡單場景。例如,快速api不須要用戶處理鏈接管理和資源釋放。
下面是幾個使用快速api的例子:
通常狀況下,HttpClient的快速api不用用戶處理鏈接管理和資源釋放。可是,這樣的話,就必須在內存中緩存這些響應消息。爲了不這一狀況,建議使用使用ResponseHandler來處理Http響應。
HttpClient的緩存機制提供一個與HTTP/1.1標準兼容的緩存層 – 至關於Java的瀏覽器緩存。HttpClient緩存機制的實現遵循責任鏈(Chain of Responsibility)設計原則,默認的HttpClient是沒有緩存的,有緩存機制的HttpClient能夠用來臨時替代默認的 HttpClient,若是開啓了緩存,咱們的請求結果就會從緩存中獲取,而不是從目標服務器中獲取。若是在Get請求頭中設置了If-Modified-Since
或者If-None-Match
參數,那麼HttpClient會自動向服務器校驗緩存是否過時。
HTTP/1.1版本的緩存是語義透明的,意思是不管怎樣,緩存都不該該修改客戶端與服務器之間傳輸的請求/響應數據包。所以,在 existing compliant client-server relationship中使用帶有緩存的HttpClient也應該是安全的。雖然緩存是客戶端的一部分,可是從Http協議的角度來看,緩存機制是爲 了兼容透明的緩存代理。
最後,HttpClient緩存也支持RFC 5861規定的Cache-Control拓展(stale-if-error'和
stale-while-revalidate`)。
當開啓緩存的HttpClient執行一個Http請求時,會通過下面的步驟:
ByteArrayEntity
的BasicHttpResponse
對象,並將它返回給http請求。不然,HttpClient會向服務器從新校驗緩存。HttpClient的緩存機制和RFC-2626文檔規定是無條件兼容的。也就是說,只要指定了MUST
,MUST NOT
,SHOULD
或者SHOULD NOT
這些Http緩存規範,HttpClient的緩存層就會按照指定的方式進行緩存。即當咱們使用HttpClient的緩存機制時,HttpClient的緩存模塊不會產生異常動做。
下面的例子講述瞭如何建立一個基本的開啓緩存的HttpClient。而且配置了最大緩存1000個Object對象,每一個對象最大佔用8192字節數據。代碼中出現的數據,只是爲了作演示,而過不是推薦使用的配置。
有緩存的HttpClient繼承了非緩存HttpClient的全部配置項和參數(包括超時時間,鏈接池大小等配置項)。若是須要對緩存進行具體配置,能夠初始化一個CacheConfig
對象來自定義下面的參數:
Cache size
(緩存大小). 若是後臺存儲支持,咱們能夠指定緩存的最大條數,和每一個緩存中存儲的response的最大size。Public/private cacheing
(公用/私有 緩存). 默認狀況下,緩存模塊會把緩存當作公用的緩存,因此緩存機制不會緩存帶有受權頭消息或者指定Cache-Control:private
的響應。可是若是緩存只會被一個邏輯上的用戶使用(和瀏覽器餓緩存相似),咱們可能但願關閉緩存共享機制。Heuristic caching
(啓發式緩存)。即便服務器沒有明確設置緩存控制headers信 息,每一個RFC2616緩存也會存儲必定數目的緩存。這個特徵在HttpClient中默認是關閉的,若是服務器不設置控制緩存的header信息,可是 咱們仍然但願對響應進行緩存,就須要在HttpClient中打開這個功能。激活啓發式緩存,而後使用默認的刷新時間或者自定義刷新時間。更多啓發式緩存 的信息,能夠參考Http/1.1 RFC文檔的13.2.2小節,13.2.4小節。Background validation
(後臺校驗)。HttpClient的緩存機制支持RFC5861的stale-while-revalidate
指令,它容許必定數目的緩存在後臺校驗是否過時。咱們可能須要調整能夠在後臺工做的最大和最小的線程數,以及設置線程在回收前最大的空閒時間。當沒有足夠線程來校驗緩存是否過時時,咱們能夠指定排隊隊列的大小。默認,HttpClient緩存機制將緩存條目和緩存的response放在本地程序的jvm內存中。這樣雖然提供高性能,可是當咱們 的程序內存有大小限制的時候,這就會變得不太合理。由於緩存的生命中期很短,若是程序重啓,緩存就會失效。當前版本的HttpClient使用 EhCache和memchached來存儲緩存,這樣就支持將緩存放到本地磁盤或者其餘存儲介質上。若是內存、本地磁盤、外地磁盤,都不適合你的應用程 序,HttpClient也支持自定義存儲介質,只須要實現HttpCacheStorage
接口,而後在建立 HttpClient時,使用這個接口的配置。這種狀況,緩存會存儲在自定義的介質中,可是you will get to reuse all of the logic surrounding HTTP/1.1 compliance and cache handling. 通常來講,能夠建立出支持任何鍵值對指定存儲(相似Java Map接口)的HttpCacheStorage
,用於進行原子更新。
最後,經過一些額外的工做,還能夠創建起多層次的緩存結構;磁盤中的緩存,遠程memcached中的緩存,虛擬內存中的緩存,L1/L2處理器中的緩存等。
在特定條件下,也許須要來定製HTTP報文經過線路傳遞,越過了可能使用的HTTP參數來處理非標準不兼容行爲的方式。好比,對於Web爬蟲,它可能須要強制HttpClient接受格式錯誤的響應頭部信息,來搶救報文的內容。
一般插入一個自定義的報文解析器的過程或定製鏈接實現須要幾個步驟:
提供一個自定義LineParser/LineFormatter接口實現。若是須要,實現報文解析/格式化邏輯。
提過一個自定義的 HttpConnectionFactory 實現。替換須要自定義的默認請求/響應解析器,請求/響應格式化器。若是須要,實現不一樣的報文寫入/讀取代碼。
爲了建立新類的鏈接,提供一個自定義的ClientConnectionOperator接口實現。若是須要,實現不一樣的套接字初始化代碼。
若是它能夠從給定的執行上下文中來得到,UserTokenHandler接口的默認實現是使用主類的一個實例來表明HTTP鏈接的狀 態對象。UserTokenHandler將會使用基於如NTLM或開啓的客戶端認證SSL會話認證模式的用戶的主鏈接。若是兩者都不可用,那麼就不會返 回令牌。
FutureRequestExecutionService用HttpRequestFutureTask(繼承FutureTask)包裝request。這個類容許你取消Task以及保持跟蹤各項指標,如request duration。
futureRequestExecutionService的構造方法包括兩個參數:httpClient實例和 ExecutorService實例。當配置兩個參數的時候,您要使用的線程數等於最大鏈接數是很重要的。當線程比鏈接多的時候,鏈接可能會開始超時,因 爲沒有可用的鏈接。當鏈接多於線程時,futureRequestExecutionService不會使用全部的鏈接。
要安排一個請求,只需提供一個HttpUriRequest,HttpContext和ResponseHandler。由於request是由executor service處理的,而ResponseHandler的是強制性的。
預約的任務可能會被取消。若是任務還沒有執行,但僅僅是排隊等待執行,它根本就不會執行。若是任務在執行中且 mayInterruptIfRunning參數被設置爲true,請求中的abort()函數將被調用;不然response會簡單地忽略,但該請求將 被容許正常完成。任何後續調用task.get()會產生一個IllegalStateException。應當注意到,取消任務僅能夠釋放客戶端的資 源。該請求可能其實是在服務器端正常處理。
不用手動調用task.get(),您也能夠在請求完成時使用FutureCallback實例獲取回調。這裏採用的是和HttpAsyncClient相同的接口
FutureRequestExecutionService一般用於大量Web服務調用的應用程序之中。爲了便於例如監視或配置調整,FutureRequestExecutionService跟蹤了幾個指標。
HttpRequestFutureTask會提供一些方法來得到任務時間:從被安排,開始,直到結束。此外,請求和任務持續時間也是 可用的。這些指標都彙集在FutureRequestExecutionService中的FutureRequestExecutionMetrics 實例,能夠經過FutureRequestExecutionService.metrics()獲取。