Http 協議應該是互聯網中最重要的協議。持續增加的 web 服務、可聯網的家用電器等都在繼承並拓 展着 Http 協議,向着瀏覽器以外的方向發展。html
雖然 jdk 中的 java.net 包中提供了一些基本的方法,經過 http 協議來訪問網絡資源,可是大多數場 景下,它都不夠靈活和強大。HttpClient 致力於填補這個空白,它能夠提供有效的、最新的、功能豐 富的包來實現 http 客戶端java
如下是一個簡單的請求例子:web
@Test public void test01() throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://www.baidu.com/"); CloseableHttpResponse response = httpclient.execute(httpget); try { System.out.println(response); } catch (Exception e) { } finally { response.close(); } }
1.1.1. HTTP 請求
json
全部的 Http 請求都有一個請求行(request line),包括方法名、請求的 URI 和 Http 版本號。數組
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(可選)和片斷信息(可選)。 緩存
HttpGet httpget =
new HttpGet("http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq= f&oq=");
HttpClient 提供 URIBuilder 工具類來簡化 URIs 的建立和修改過程。 安全
@Test public void test02() throws Exception { URI uri = new URIBuilder() .setScheme("http") .setHost("www.google.com") .setPath("/search") .setParameter("q", "httpclient") .setParameter("btnG", "Google Search") .setParameter("aq", "f") .setParameter("oq", "") .build(); HttpGet httpget = new HttpGet(uri); System.out.println(httpget.getURI()); }
運行輸出:http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=服務器
服務器收到客戶端的http請求後,就會對其進行解析,而後把響應發給客戶端,這個響應就是HTTP response.HTTP響應第一行是HTTP版本號,而後是響應狀態碼和響應內容。網絡
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); System.out.println(response.getProtocolVersion()); System.out.println(response.getStatusLine().getStatusCode()); System.out.println(response.getStatusLine().getReasonPhrase()); System.out.println(response.getStatusLine().toString());
上述代碼會在控制檯輸出:
HTTP/1.1 200 OK HTTP/1.1 200 OK
一個Http消息能夠包含一系列的消息頭,用來對http消息進行描述,好比消息長度,消息類型等等。HttpClient提供了API來獲取、添加、修改、遍歷消息頭。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com"); response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\""); 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);
上述代碼會在控制檯輸出
Set-Cookie: c1=a; path=/; domain=yeetrack.com
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
2
最有效的獲取指定類型的消息頭的方法仍是使用HeaderIterator
接口。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"localhost\"");
HeaderIterator it = response.headerIterator("Set-Cookie");
while (it.hasNext()) {
System.out.println(it.next());
}
上述代碼會在控制檯輸出:
Set-Cookie: c1=a; path=/; domain=yeetrack.com
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
HeaderIterator也提供很是便捷的方式,將Http消息解析成單獨的消息頭元素。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie", "c1=a; path=/; domain=yeetrack.com");
response.addHeader("Set-Cookie", "c2=b; path=\"/\", c3=c; domain=\"yeetrack.com\"");
HeaderElementIterator it = new BasicHeaderElementIterator(response.headerIterator("Set-Cookie"));
while (it.hasNext()) {
HeaderElement elem = it.nextElement();
System.out.println(elem.getName() + " = " + elem.getValue());
NameValuePair[] params = elem.getParameters();
for (int i = 0; i < params.length; i++) {
System.out.println(" " + params[i]);
}
}
上述代碼會在控制檯輸出:
c1 = a path=/ domain=yeetrack.com c2 = b path=/ c3 = c domain=yeetrack.com
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。
1.1.4.1. 可重複的實體
一個實體是可重複的,也就是說它的包含的內容能夠被屢次讀取。這種屢次讀取只有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信息。
StringEntity myEntity = new StringEntity("important message", ContentType.create("text/plain", "UTF-8")); System.out.println(myEntity.getContentType()); System.out.println(myEntity.getContentLength()); System.out.println(EntityUtils.toString(myEntity)); System.out.println(EntityUtils.toByteArray(myEntity).length);
上述代碼會在控制檯輸出:
Content-Type: text/plain; charset=utf-8 17 important message 17
爲了確保系統資源被正確地釋放,咱們要麼管理Http實體的內容流、要麼關閉Http響應。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://www.baidu.com/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); try { // do something useful } finally { instream.close(); } } } finally { response.close(); }
關閉Http實體內容流和關閉Http響應的區別在於,前者經過消耗掉Http實體內容來保持相關的http鏈接,而後後者會當即關閉、丟棄http鏈接。
請注意HttpEntity
的writeTo(OutputStream)
方法,當Http實體被寫入到OutputStream後,也要確保釋放系統資源。若是這個方法內調用了HttpEntity
的getContent()
方法,那麼它會有一個java.io.InpputStream
的實例,咱們須要在finally中關閉這個流。
可是也有這樣的狀況,咱們只須要獲取Http響應內容的一小部分,而獲取整個內容並、實現鏈接的可重複性代價太大,這時咱們能夠經過關閉響應的方式來關閉內容輸入、輸出流。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://www.baidu.com/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream instream = entity.getContent(); int byteOne = instream.read(); int byteTwo = instream.read(); // Do not need the rest } } finally { response.close(); }
上面的代碼執行後,鏈接變得不可用,全部的資源都將被釋放。
HttpClient推薦使用HttpEntity
的getConent()
方法或者HttpEntity
的writeTo(OutputStream)
方法來消耗掉Http實體內容。HttpClient也提供了EntityUtils
這個類,這個類提供一些靜態方法能夠更容易地讀取Http實體的內容和信息。和以java.io.InputStream
流讀取內容的方式相比,EntityUtils提供的方法能夠以字符串或者字節數組的形式讀取Http實體。可是,強烈不推薦使用EntityUtils
這個類,除非目標服務器發出的響應是可信任的,而且http響應實體的長度不會過大。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://www.yeetrack.com/"); CloseableHttpResponse response = httpclient.execute(httpget); try { HttpEntity entity = response.getEntity(); if (entity != null) { long len = entity.getContentLength(); if (len != -1 && len < 2048) { System.out.println(EntityUtils.toString(entity)); } else { // Stream content out } } } finally { response.close(); }
有些狀況下,咱們但願能夠重複讀取Http實體的內容。這就須要把Http實體內容緩存在內存或者磁盤上。最簡單的方法就是把Http Entity轉化成BufferedHttpEntity
,這樣就把原Http實體的內容緩衝到了內存中。後面咱們就能夠重複讀取BufferedHttpEntity中的內容。
CloseableHttpResponse response = <...> HttpEntity entity = response.getEntity(); if (entity != null) { entity = new BufferedHttpEntity(entity); }
HttpClient提供了一個類,這些類能夠經過http鏈接高效地輸出Http實體內容。(原文是HttpClient provides several classes that can be used to efficiently stream out content though HTTP connections.感受thought應該是throught)HttpClient提供的這幾個類涵蓋的常見的數據類型,如String,byte數組,輸入流,和文件類型:StringEntity
,ByteArrayEntity
,InputStreamEntity
,FileEntity
。
File file = new File("somefile.txt"); FileEntity entity = new FileEntity(file, ContentType.create("text/plain", "UTF-8")); HttpPost httppost = new HttpPost("http://www.yeetrack.com/action.do"); httppost.setEntity(entity); }
請注意因爲InputStreamEntity
只能從下層的數據流中讀取一次,因此它是不能重複的。推薦,經過繼承HttpEntity
這個自包含的類來自定義HttpEntity類,而不是直接使用InputStreamEntity
這個類。FileEntity
就是一個很好的起點(FileEntity就是繼承的HttpEntity)。
不少應用程序須要模擬提交Html表單的過程,舉個例子,登錄一個網站或者將輸入內容提交給服務器。HttpClient提供了UrlEncodedFormEntity
這個類來幫助實現這一過程。
List<NameValuePair> formparams = new ArrayList<NameValuePair>(); formparams.add(new BasicNameValuePair("param1", "value1")); formparams.add(new BasicNameValuePair("param2", "value2")); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8); HttpPost httppost = new HttpPost("http://www.yeetrack.com/handler.do"); httppost.setEntity(entity);
UrlEncodedFormEntity
實例會使用所謂的Url編碼的方式對咱們的參數進行編碼,產生的結果以下:
param1=value1¶m2=value2
通常來講,推薦讓HttpClient本身根據Http消息傳遞的特徵來選擇最合適的傳輸編碼。固然,若是非要手動控制也是能夠的,能夠經過設置HttpEntity
的setChunked()
爲true。請注意:HttpClient僅會將這個參數當作是一個建議。若是Http的版本(如http 1.0)不支持內容分塊,那麼這個參數就會被忽略。
StringEntity entity = new StringEntity("important message", ContentType.create("plain/text", Consts.UTF_8)); entity.setChunked(true); HttpPost httppost = new HttpPost("http://www.yeetrack.com/acrtion.do"); httppost.setEntity(entity);
最簡單也是最方便的處理http響應的方法就是使用ResponseHandler
接口,這個接口中有handleResponse(HttpResponse response)
方法。使用這個方法,用戶徹底不用關心http鏈接管理器。當使用ResponseHandler
時,HttpClient會自動地將Http鏈接釋放給Http管理器,即便http請求失敗了或者拋出了異常。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpget = new HttpGet("http://www.yeetrack.com/json"); ResponseHandler<JsonObject> rh = new ResponseHandler<JsonObject>() { @Override public JsonObject handleResponse( final HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() >= 300) { throw new HttpResponseException( statusLine.getStatusCode(), statusLine.getReasonPhrase()); } if (entity == null) { throw new ClientProtocolException("Response contains no content"); } Gson gson = new GsonBuilder().create(); ContentType contentType = ContentType.getOrDefault(entity); Charset charset = contentType.getCharset(); Reader reader = new InputStreamReader(entity.getContent(), charset); return gson.fromJson(reader, JsonObject.class); } }; //設置responseHandler,當執行http方法時,就會返回MyJsonObject對象。 JsonObject myjson = httpclient.execute(httpget, rh);
對於Http請求執行過程來講,HttpClient
的接口有着必不可少的做用。HttpClient
接口沒有對Http請求的過程作特別的限制和詳細的規定,鏈接管理、狀態管理、受權信息和重定向處理這些功能都單獨實現。這樣用戶就能夠更簡單地拓展接口的功能(好比緩存響應內容)。
通常說來,HttpClient實際上就是一系列特殊的handler或者說策略接口的實現,這些handler(測試接口)負責着處理Http協議的某一方面,好比重定向、認證處理、有關鏈接持久性和keep alive持續時間的決策。這樣就容許用戶使用自定義的參數來代替默認配置,實現個性化的功能。
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration( HttpResponse response, HttpContext context) { long keepAlive = super.getKeepAliveDuration(response, context); if (keepAlive == -1) { //若是服務器沒有設置keep-alive這個參數,咱們就把它設置成5秒 keepAlive = 5000; } return keepAlive; } }; //定製咱們本身的httpclient CloseableHttpClient httpclient = HttpClients.custom() .setKeepAliveStrategy(keepAliveStrat) .build();
HttpClient
已經實現了線程安全。因此但願用戶在實例化HttpClient時,也要支持爲多個請求使用。
當一個CloseableHttpClient
的實例再也不被使用,而且它的做用範圍即將失效,和它相關的鏈接必須被關閉,關閉方法能夠調用CloseableHttpClient
的close()
方法。
CloseableHttpClient httpclient = HttpClients.createDefault(); try { <...> } finally { //關閉鏈接 httpclient.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
這個適配器來簡化和上下文交互的過程。
HttpContext context = <...> HttpClientContext clientContext = HttpClientContext.adapt(context); HttpHost target = clientContext.getTargetHost(); HttpRequest request = clientContext.getRequest(); HttpResponse response = clientContext.getResponse(); RequestConfig config = clientContext.getRequestConfig();
同一個邏輯會話中的多個Http請求,應該使用相同的Http上下文來執行,這樣就能夠自動地在http請求中傳遞會話上下文和狀態信息。
在下面的例子中,咱們在開頭設置的參數,會被保存在上下文中,而且會應用到後續的http請求中(源英文中有個拼寫錯誤)。
CloseableHttpClient httpclient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(1000) .setConnectTimeout(1000) .build(); HttpGet httpget1 = new HttpGet("http://www.yeetrack.com/1"); httpget1.setConfig(requestConfig); CloseableHttpResponse response1 = httpclient.execute(httpget1, context); try { HttpEntity entity1 = response1.getEntity(); } finally { response1.close(); } //httpget2被執行時,也會使用httpget1的上下文 HttpGet httpget2 = new HttpGet("http://www.yeetrack.com/2"); CloseableHttpResponse response2 = httpclient.execute(httpget2, context); try { HttpEntity entity2 = response2.getEntity(); } finally { response2.close(); }
HttpClient會被拋出兩種類型的異常,一種是java.io.IOException
,當遇到I/O異常時拋出(socket超時,或者socket被重置);另外一種是HttpException
,表示Http失敗,如Http協議使用不正確。一般認爲,I/O錯誤時不致命、可修復的,而Http協議錯誤是致命了,不能自動修復的錯誤。
Http協議不能知足全部類型的應用場景,咱們須要知道這點。Http是個簡單的面向協議的請求/響應的協議,當初它被設計用來支持靜態或者動態生成的內容檢索,以前歷來沒有人想過讓它支持事務性操做。例如,Http服務器成功接收、處理請求後,生成響應消息,而且把狀態碼發送給客戶端,這個過程是Http協議應該保證的。可是,若是客戶端因爲讀取超時、取消請求或者系統崩潰致使接收響應失敗,服務器不會回滾這一事務。若是客戶端從新發送這個請求,服務器就會重複的解析、執行這個事務。在一些狀況下,這會致使應用程序的數據損壞和應用程序的狀態不一致。
即便Http當初設計是不支持事務操做,可是它仍舊能夠做爲傳輸協議爲某些關鍵程序提供服務。爲了保證Http傳輸層的安全性,系統必須保證應用層上的http方法的冪等性(To ensure HTTP transport layer safety the system must ensure the idempotency of HTTP methods on the application layer)。
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
接口。
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest( IOException exception, int executionCount, HttpContext context) { if (executionCount >= 5) { // 若是已經重試了5次,就放棄 return false; } if (exception instanceof InterruptedIOException) { // 超時 return false; } if (exception instanceof UnknownHostException) { // 目標服務器不可達 return false; } if (exception instanceof ConnectTimeoutException) { // 鏈接被拒絕 return false; } if (exception instanceof SSLException) { // ssl握手異常 return false; } HttpClientContext clientContext = HttpClientContext.adapt(context); HttpRequest request = clientContext.getRequest(); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if (idempotent) { // 若是請求是冪等的,就再次嘗試 return true; } return false; } }; CloseableHttpClient httpclient = HttpClients.custom() .setRetryHandler(myRetryHandler) .build();
有時候因爲目標服務器負載太高或者客戶端目前有太多請求積壓,http請求不能在指定時間內執行完畢。這時候終止這個請求,釋放阻塞I/O的進程,就顯得很必要。經過HttpClient執行的Http請求,在任何狀態下都能經過調用HttpUriRequest
的abort()
方法來終止。這個方法是線程安全的,而且能在任何線程中調用。當Http請求被終止了,本線程(即便如今正在阻塞I/O)也會經過拋出一個InterruptedIOException
異常,來釋放資源。
HTTP協議攔截器是一種實現一個特定的方面的HTTP協議的代碼程序。一般狀況下,協議攔截器會將一個或多個頭消息加入到接受或者發送的消息中。協議攔截器也能夠操做消息的內容實體—消息內容的壓縮/解壓縮就是個很好的例子。一般,這是經過使用「裝飾」開發模式,一個包裝實體類用於裝飾原來的實體來實現。一個攔截器能夠合併,造成一個邏輯單元。
協議攔截器能夠經過共享信息協做——好比處理狀態——經過HTTP執行上下文。協議攔截器可使用Http上下文存儲一個或者多個連續請求的處理狀態。
一般,只要攔截器不依賴於一個特定狀態的http上下文,那麼攔截執行的順序就無所謂。若是協議攔截器有相互依賴關係,必須以特定的順序執行,那麼它們應該按照特定的順序加入到協議處理器中。
協議處理器必須是線程安全的。相似於servlets,協議攔截器不該該使用變量實體,除非訪問這些變量是同步的(線程安全的)。
下面是個例子,講述了本地的上下文時如何在連續請求中記錄處理狀態的:
CloseableHttpClient httpclient = HttpClients.custom() .addInterceptorLast(new HttpRequestInterceptor() { public void process( final HttpRequest request, final HttpContext context) throws HttpException, IOException { //AtomicInteger是個線程安全的整型類 AtomicInteger count = (AtomicInteger) context.getAttribute("count"); request.addHeader("Count", Integer.toString(count.getAndIncrement())); } }) .build(); AtomicInteger count = new AtomicInteger(1); HttpClientContext localContext = HttpClientContext.create(); localContext.setAttribute("count", count); HttpGet httpget = new HttpGet("http://www.yeetrack.com/"); for (int i = 0; i < 10; i++) { CloseableHttpResponse response = httpclient.execute(httpget, localContext); try { HttpEntity entity = response.getEntity(); } finally { response.close(); } }
上面代碼在發送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方法重定向的限制。
//LaxRedirectStrategy能夠自動重定向全部的HEAD,GET,POST請求,解除了http規範對post請求重定向的限制。 LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); CloseableHttpClient httpclient = HttpClients.custom() .setRedirectStrategy(redirectStrategy) .build();
HttpClient在請求執行過程當中,常常須要重寫請求的消息。 HTTP/1.0和HTTP/1.1都默認使用相對的uri路徑。一樣,原始的請求可能會被一次或者屢次的重定向。最終結對路徑的解釋可使用最初的請求和上下文。URIUtils
類的resolve
方法能夠用於將攔截的絕對路徑構建成最終的請求。這個方法包含了最後一個分片標識符或者原始請求。
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpClientContext context = HttpClientContext.create(); HttpGet httpget = new HttpGet("http://www.yeetrack.com:8080/"); CloseableHttpResponse response = httpclient.execute(httpget, context); try { HttpHost target = context.getTargetHost(); List<URI> redirectLocations = context.getRedirectLocations(); URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations); System.out.println("Final HTTP location: " + location.toASCIIString()); // 通常會取得一個絕對路徑的uri } finally { response.close(); }