前言
超文本傳輸協議(HTTP)也許是當今互聯網上使用的最重要的協議了。Web服務,有網絡功能的設備和網絡計算的發展,都持續擴展了HTTP協議的角色,超越了用戶使用的Web瀏覽器範疇,同時,也增長了須要HTTP協議支持的應用程序的數量。
儘管java.net包提供了基本經過HTTP訪問資源的功能,但它沒有提供全面的靈活性和其它不少應用程序須要的功能。HttpClient就是尋求彌補這項空白的組件,經過提供一個有效的,保持更新的,功能豐富的軟件包來實現客戶端最新的HTTP標準和建議。
爲擴展而設計,同時爲基本的HTTP協議提供強大的支持,HttpClient組件也許就是構建HTTP客戶端應用程序,好比web瀏覽器,web服務端,利用或擴展HTTP協議進行分佈式通訊的系統的開發人員的關注點。
1. HttpClient的範圍
基於HttpCore[http://hc.apache.org/httpcomponents-core/index.html]的客戶端HTTP運輸實現庫
基於經典(阻塞)I/O
內容無關
2. 什麼是HttpClient不能作的
HttpClient不是一個瀏覽器。它是一個客戶端的HTTP通訊實現庫。HttpClient的目標是發送和接收HTTP報文。HttpClient不會去緩存內容,執行嵌入在HTML頁面中的javascript代碼,猜想內容類型,從新格式化請求/重定向URI,或者其它和HTTP運輸無關的功能。
第一章 基礎
1.1 執行請求
HttpClient最重要的功能是執行HTTP方法。一個HTTP方法的執行包含一個或多個HTTP請求/HTTP響應交換,一般由HttpClient的內部來處理。而指望用戶提供一個要執行的請求對象,而HttpClient指望傳輸請求到目標服務器並返回對應的響應對象,或者當執行不成功時拋出異常。
很天然地,HttpClient API的主要切入點就是定義描述上述規約的HttpClient接口。
這裏有一個很簡單的請求執行過程的示例:
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int l;
byte[] tmp = new byte[2048];
while ((l = instream.read(tmp)) != -1) {
}
}
1.1.1 HTTP請求
全部HTTP請求有一個組合了方法名,請求URI和HTTP協議版本的請求行。
HttpClient支持全部定義在HTTP/1.1版本中的HTTP方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。對於每一個方法類型都有一個特殊的類:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。
請求的URI是統一資源定位符,它標識了應用於哪一個請求之上的資源。HTTP請求URI包含一個協議模式,主機名稱,可選的端口,資源路徑,可選的查詢和可選的片斷。
HttpGet httpget = new HttpGet(
"http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient提供不少工具方法來簡化建立和修改執行URI。
URI也能夠編程來拼裝:
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
"q=httpclient&btnG=Google+Search&aq=f&oq=", null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
輸出內容爲:
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
查詢字符串也能夠從獨立的參數中來生成:
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
qparams.add(new BasicNameValuePair("q", "httpclient"));
qparams.add(new BasicNameValuePair("btnG", "Google Search"));
qparams.add(new BasicNameValuePair("aq", "f"));
qparams.add(new BasicNameValuePair("oq", null));
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
URLEncodedUtils.format(qparams, "UTF-8"), null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());
輸出內容爲:
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=
1.1.2 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 OKjavascript
1.1.3 處理報文頭部
一個HTTP報文能夠包含不少描述如內容長度,內容類型等信息屬性的頭部信息。
HttpClient提供獲取,添加,移除和枚舉頭部信息的方法。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie",
"c1=a; path=/; domain=localhost");
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=localhost
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
得到給定類型的全部頭部信息最有效的方式是使用HeaderIterator接口。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie",
"c1=a; path=/; domain=localhost");
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=localhost
Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
它也提供解析HTTP報文到獨立頭部信息元素的方法方法。
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie",
"c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie",
"c2=b; path=\"/\", c3=c; domain=\"localhost\"");
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=localhost
c2 = b
path=/
c3 = c
domain=localhosthtml
1.1.4 HTTP實體
HTTP報文能夠攜帶和請求或響應相關的內容實體。實體能夠在一些請求和響應中找到,由於它們也是可選的。使用了實體的請求被稱爲封閉實體請求。HTTP規範定義了兩種封閉實體的方法:POST和PUT。響應一般指望包含一個內容實體。這個規則也有特例,好比HEAD方法的響應和204 No Content,304 Not Modified和205 Reset Content響應。
HttpClient根據其內容出自何處區分三種類型的實體:
streamed流式:內容從流中得到,或者在運行中產生。特別是這種分類包含從HTTP響應中獲取的實體。流式實體是不可重複生成的。
self-contained自我包含式:內容在內存中或經過獨立的鏈接或其它實體中得到。自我包含式的實體是能夠重複生成的。這種類型的實體會常常用於封閉HTTP請求的實體。
wrapping包裝式:內容從另一個實體中得到。
當從一個HTTP響應中獲取流式內容時,這個區別對於鏈接管理很重要。對於由應用程序建立並且只使用HttpClient發送的請求實體,流式和自我包含式的不一樣就不那麼重要了。這種狀況下,建議考慮如流式這種不能重複的實體,和能夠重複的自我包含式實體。
1.1.4.1 重複實體
實體能夠重複,意味着它的內容能夠被屢次讀取。這就僅僅是自我包含式的實體了(像ByteArrayEntity或StringEntity)。
1.1.4.2 使用HTTP實體
由於一個實體既能夠表明二進制內容又能夠表明字符內容,它也支持字符編碼(支持後者也就是字符內容)。
實體是當使用封閉內容執行請求,或當請求已經成功執行,或當響應體結果發功到客戶端時建立的。
要從實體中讀取內容,能夠經過HttpEntity#getContent()方法從輸入流中獲取,這會返回一個java.io.InputStream對象,或者提供一個輸出流到HttpEntity#writeTo(OutputStream)方法中,這會一次返回全部寫入到給定流中的內容。
當實體經過一個收到的報文獲取時,HttpEntity#getContentType()方法和HttpEntity#getContentLength()方法能夠用來讀取通用的元數據,如Content-Type和Content-Length頭部信息(若是它們是可用的)。由於頭部信息Content-Type能夠包含對文本MIME類型的字符編碼,好比text/plain或text/html,HttpEntity#getContentEncoding()方法用來讀取這個信息。若是頭部信息不可用,那麼就返回長度-1,而對於內容類型返回NULL。若是頭部信息Content-Type是可用的,那麼就會返回一個Header對象。
當爲一個傳出報文建立實體時,這個元數據不得不經過實體建立器來提供。
StringEntity myEntity = new StringEntity("important message",
"UTF-8");
System.out.println(myEntity.getContentType());
System.out.println(myEntity.getContentLength());
System.out.println(EntityUtils.getContentCharSet(myEntity));
System.out.println(EntityUtils.toString(myEntity));
System.out.println(EntityUtils.toByteArray(myEntity).length);
輸出內容爲
Content-Type: text/plain; charset=UTF-8
17
UTF-8
important message
17java
1.1.5 確保低級別資源釋放
當完成一個響應實體,那麼保證全部實體內容已經被徹底消耗是很重要的,因此鏈接能夠安全的放回到鏈接池中,並且能夠經過鏈接管理器對後續的請求重用鏈接。處理這個操做的最方便的方法是調用HttpEntity#consumeContent()方法來消耗流中的任意可用內容。HttpClient探測到內容流尾部已經到達後,會當即會自動釋放低層鏈接,並放回到鏈接管理器。HttpEntity#consumeContent()方法調用屢次也是安全的。
也可能會有特殊狀況,當整個響應內容的一小部分須要獲取,消耗剩餘內容而損失性能,還有重用鏈接的代價過高,則能夠僅僅經過調用HttpUriRequest#abort()方法來停止請求。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
int byteOne = instream.read();
int byteTwo = instream.read();
// Do not need the rest
httpget.abort();
}
鏈接不會被重用,可是由它持有的全部級別的資源將會被正確釋放。web
1.1.6 消耗實體內容
推薦消耗實體內容的方式是使用它的HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。HttpClient也自帶EntityUtils類,這會暴露出一些靜態方法,這些方法能夠更加容易地從實體中讀取內容或信息。代替直接讀取java.io.InputStream,也可使用這個類中的方法以字符串/字節數組的形式獲取整個內容體。然而,EntityUtils的使用是強烈不鼓勵的,除非響應實體源自可靠的HTTP服務器和已知的長度限制。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
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
}
}
在一些狀況下可能會不止一次的讀取實體。此時實體內容必須以某種方式在內存或磁盤上被緩衝起來。最簡單的方法是經過使用BufferedHttpEntity類來包裝源實體完成。這會引發源實體內容被讀取到內存的緩衝區中。在其它全部方式中,實體包裝器將會獲得源實體。
HttpGet httpget = new HttpGet("http://localhost/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity = new BufferedHttpEntity(entity);
}apache
1.1.7 生成實體內容
HttpClient提供一些類,它們能夠用於生成經過HTTP鏈接得到內容的有效輸出流。爲了封閉實體從HTTP請求中得到的輸出內容,那些類的實例能夠和封閉如POST和PUT請求的實體相關聯。HttpClient爲不少公用的數據容器,好比字符串,字節數組,輸入流和文件提供了一些類:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, "text/plain; charset=\"UTF-8\"");
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);
請注意InputStreamEntity是不可重複的,由於它僅僅能從低層數據流中讀取一次內容。一般來講,咱們推薦實現一個定製的HttpEntity類,這是自我包含式的,用來代替使用通用的InputStreamEntity。FileEntity也是一個很好的起點。編程
1.1.7.1 動態內容實體
一般來講,HTTP實體須要基於特定的執行上下文來動態地生成。經過使用EntityTemplate實體類和ContentProducer接口,HttpClient提供了動態實體的支持。內容生成器是按照需求生成它們內容的對象,將它們寫入到一個輸出流中。它們是每次被請求時來生成內容。因此用EntityTemplate建立的實體一般是自我包含並且能夠重複的。
ContentProducer cp = new ContentProducer() {
public void writeTo(OutputStream outstream) throws IOException {
Writer writer = new OutputStreamWriter(outstream, "UTF-8");
writer.write("<response>");
writer.write(" <content>");
writer.write(" important stuff");
writer.write(" </content>");
writer.write("</response>");
writer.flush();
}
};
HttpEntity entity = new EntityTemplate(cp);
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);數組
1.1.7.2 HTML表單
許多應用程序須要頻繁模擬提交一個HTML表單的過程,好比,爲了來記錄一個Web應用程序或提交輸出數據。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, "UTF-8");
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);
UrlEncodedFormEntity實例將會使用URL編碼來編碼參數,生成以下的內容:
param1=value1¶m2=value2瀏覽器
1.1.7.3 內容分塊
一般,咱們推薦讓HttpClient選擇基於被傳遞的HTTP報文屬性的最適合的編碼轉換。這是可能的,可是,設置HttpEntity#setChunked()方法爲true是通知HttpClient分塊編碼的首選。請注意HttpClient將會使用標識做爲提示。當使用的HTTP協議版本,如HTTP/1.0版本,不支持分塊編碼時,這個值會被忽略。
StringEntity entity = new StringEntity("important message",
"text/plain; charset=\"UTF-8\"");
entity.setChunked(true);
HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
httppost.setEntity(entity);緩存
1.1.8 響應控制器
控制響應的最簡便和最方便的方式是使用ResponseHandler接口。這個方法徹底減輕了用戶關於鏈接管理的擔憂。當使用ResponseHandler時,HttpClient將會自動關閉並保證釋放鏈接到鏈接管理器中去,而無論請求執行是否成功或引起了異常。
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
public byte[] handleResponse(
HttpResponse response) throws ClientProtocolException, IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
return EntityUtils.toByteArray(entity);
} else {
return null;
}
}
};
byte[] response = httpclient.execute(httpget, handler);安全
1.2
HTTP客戶端接口展現了最基本的HTTP請求執行規範。他沒有限定請求執行的具體的細節,把鏈接管理,狀態管理,鑑權和重定向處理的細節留給了我的實現。這樣就很容易去給接口加一些額外功能,例如響應緩存。
一般HTTP客戶端充當一個處理大量具體用途(負責處理HTTP協議的某個具體方面,好比重定向、鑑權處理、鏈接持久化、保活時間)的處理程序的外殼。這使用戶可以有選擇的個性化的去替換某些方面的實現,爲應用作定製。
HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions.
HttpClient實現是線程安全的。推薦這個類的實例被多線程的請求重複使用。
When an instance CloseableHttpClient is no longer needed and is about to go out of scope the connection manager associated with it must be shut down by calling the CloseableHttpClient#close() method.
當一個CloseableHttpClient實例沒用了,或者再也不用鏈接管理器管理了,必須調用close方式關閉他。
1.3 HTTP執行的環境
最初,HTTP是被設計成無狀態的,面向請求-響應的協議。然而,真實的應用程序常常須要經過一些邏輯相關的請求-響應交換來持久狀態信息。爲了開啓應用程序來維持一個過程狀態,HttpClient容許HTTP請求在一個特定的執行環境中來執行,簡稱爲HTTP上下文。若是相同的環境在連續請求之間重用,那麼多種邏輯相關的請求能夠參與到一個邏輯會話中。HTTP上下文功能和java.util.Map<String,Object>很類似。它僅僅是任意命名參數值的集合。應用程序能夠在請求以前或在檢查上下文執行完成以後來填充上下文屬性。
在HTTP請求執行的這一過程當中,HttpClient添加了下列屬性到執行上下文中:
'http.connection':HttpConnection實例表明了鏈接到目標服務器的真實鏈接。
'http.target_host':HttpHost實例表明了鏈接目標。
'http.proxy_host':若是使用了,HttpHost實例表明了代理鏈接。
'http.request':HttpRequest實例表明了真實的HTTP請求。
'http.response':HttpResponse實例表明了真實的HTTP響應。
'http.request_sent':java.lang.Boolean對象表明了暗示真實請求是否被徹底傳送到目標鏈接的標識。
好比,爲了決定最終的重定向目標,在請求執行以後,能夠檢查http.target_host屬性的值:
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpHost target = (HttpHost) localContext.getAttribute(
ExecutionContext.HTTP_TARGET_HOST);
System.out.println("Final target: " + target);
HttpEntity entity = response.getEntity();
if (entity != null) {
entity.consumeContent();
}
輸出內容爲:
Final target: http://www.google.ch
1.4 異常處理
HttpClient可以拋出兩種類型的異常:在I/O失敗時,如套接字鏈接超時或被重置的java.io.IOException異常,還有標誌HTTP請求失敗的信號,如違反HTTP協議的HttpException異常。一般I/O錯誤被認爲是非致命的和能夠恢復的,而HTTP協議錯誤則被認爲是致命的並且是不能自動恢復的。
1.4.1 HTTP運輸安全
要理解HTTP協議並非對全部類型的應用程序都適合的,這一點很重要。HTTP是一個簡單的面向請求/響應的協議,最初被設計用來支持取回靜態或動態生成的內容。它從未向支持事務性操做方向發展。好比,若是成功收到和處理請求,HTTP服務器將會考慮它的其中一部分是否完成,生成一個響應併發送一個狀態碼到客戶端。若是客戶端由於讀取超時,請求取消或系統崩潰致使接收響應實體失敗時,服務器不會試圖回滾事務。若是客戶端決定從新發起這個請求,那麼服務器將不可避免地不止一次執行這個相同的事務。在一些狀況下,這會致使應用數據損壞或者不一致的應用程序狀態。
儘管HTTP歷來都沒有被設計來支持事務性處理,但它也能被用做於一個傳輸協議對關鍵的任務應用提供被知足的肯定狀態。要保證HTTP傳輸層的安全,系統必須保證HTTP方法在應用層的冪等性。
1.4.2 冪等的方法
HTTP/1.1 明確地定義了冪等的方法,描述以下
[方法也能夠有「冪等」屬性在那些(除了錯誤或過時問題)N的反作用>0的相同請求和獨立的請求是相同的]
換句話說,應用程序應該保證準備着來處理多個相同方法執行的實現。這是能夠達到的,好比,經過提供一個獨立的事務ID和其它避免執行相同邏輯操做的方法。
請注意這個問題對於HttpClient是不具體的。基於應用的瀏覽器特別受和非冪等的HTTP方法相關的相同問題的限制。
HttpClient假設沒有實體包含方法,好比GET和HEAD是冪等的,而實體包含方法,好比POST和PUT則不是。
1.4.3 異常自動恢復
默認狀況下,HttpClient會試圖自動從I/O異常中恢復。默認的自動恢復機制是受不多一部分已知的異常是安全的這個限制。
HttpClient不會從任意邏輯或HTTP協議錯誤(那些是從HttpException類中派生出的)中恢復的。
HttpClient將會自動從新執行那麼假設是冪等的方法。
HttpClient將會自動從新執行那些因爲運輸異常失敗,而HTTP請求仍然被傳送到目標服務器(也就是請求沒有徹底被送到服務器)失敗的方法。
HttpClient將會自動從新執行那些已經徹底被送到服務器,可是服務器使用HTTP狀態碼(服務器僅僅丟掉鏈接而不會發回任何東西)響應時失敗的方法。在這種狀況下,假設請求沒有被服務器處理,而應用程序的狀態也沒有改變。若是這個假設可能對於你應用程序的目標Web服務器來講不正確,那麼就強烈建議提供一個自定義的異常處理器
1.4.4 請求重試處理
爲了開啓自定義異常恢復機制,應該提供一個HttpRequestRetryHandler接口的實現。
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception,
int executionCount,HttpContext context) {
if (executionCount >= 5) {
// 若是超過最大重試次數,那麼就不要繼續了
return false;
}
if (exception instanceof NoHttpResponseException) {
// 若是服務器丟掉了鏈接,那麼就重試
return true;
}
if (exception instanceof SSLHandshakeException) {
// 不要重試SSL握手異常
return false;
}
HttpRequest request = (HttpRequest) context.getAttribute(
ExecutionContext.HTTP_REQUEST);
boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
if (idempotent) {
// 若是請求被認爲是冪等的,那麼就重試
return true;
}
return false;
}
};
httpclient.setHttpRequestRetryHandler(myRetryHandler);
1.5 停止請求
在一些狀況下,因爲目標服務器的高負載或客戶端有不少活動的請求,那麼HTTP請求執行會在預期的時間框內而失敗。這時,就可能不得不過早地停止請求,解除封鎖在I/O執行中的線程封鎖。被HttpClient執行的HTTP請求能夠在執行的任意階段經過調用HttpUriRequest#abort()方法而停止。這個方法是線程安全的,並且能夠從任意線程中調用。當一個HTTP請求被停止時,它的執行線程就封鎖在I/O操做中了,並且保證經過拋出InterruptedIOException異常來解鎖。
In some situations HTTP request execution fails to complete within the expected time frame due to high load on the target server or too many concurrent requests issued on the client side. In such cases it may be necessary to terminate the request prematurely and unblock the execution thread blocked in a I/O operation. HTTP requests being executed by HttpClient can be aborted at any stage of execution by invoking HttpUriRequest#abort()
method. This method is thread-safe and can be called from any thread. When an HTTP request is aborted its execution thread - even if currently blocked in an I/O operation - is guaranteed to unblock by throwing a InterruptedIOException
有時候HTTP請求會在指望的時間內沒法完成,由於目標服務的高負荷或者客戶端有太多的併發請求。這時,可能須要去過早的中斷請求和解鎖被IO操做阻塞的線程。HttpClient可以用HttpUriRequest#abort()方法在任何執行階段丟棄HTTP請求。這個方法是線程安全的,而且能夠被任何線程調用。當一個請求被丟棄,他的執行線程應該保證解鎖並拋出一個InterruptedIOException異常。
HttpClient handles all types of redirects automatically, except those explicitly prohibited by the HTTP specification as requiring user intervention. See Other
(status code 303) redirects on POST
and PUT
requests are converted to GET
requests as required by the HTTP specification. One can use a custom redirect strategy to relaxe restrictions on automatic redirection of POST methods imposed by the HTTP specification.
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); CloseableHttpClient httpclient = HttpClients.custom() .setRedirectStrategy(redirectStrategy) .build();
HttpClient often has to rewrite the request message in the process of its execution. Per default HTTP/1.0 and HTTP/1.1 generally use relative request URIs. Likewise, original request may get redirected from location to another multiple times. The final interpreted absolute HTTP location can be built using the original request and the context. The utility method URIUtils#resolve
can be used to build the interpreted absolute URI used to generate the final request. This method includes the last fragment identifier from the redirect requests or the original request.
CloseableHttpClient httpclient = HttpClients.createDefault(); HttpClientContext context = HttpClientContext.create(); HttpGet httpget = new HttpGet("http://localhost: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()); // Expected to be an absolute URI } finally { response.close(); }