HttpClient 教程
該教程原文來自官方提供的 httpcomponents-client-4.5.2 包下 tutorial 目錄的 pdf 教程。
如需要閱讀原文,在 apache 官網 下載 HttpClient 壓縮包解壓后可以找到相關教程。
前言
Hyper-Text Transfer Protocol(HTTP 超文本傳輸協議)應該是應用在互聯網上最重要的協議了。
網絡服務,網絡使能應用以及雲計算的增多,在增加需要 HTTP 支持的應用數量時,不斷的擴展 HTTP 協議在用戶主導的瀏覽器中
所扮演的角色。
盡管 java.net 包提供了基礎的功能,可以通過 HTTP 協議來訪問網絡資源,但它沒有提供許多應用需要的靈活性或功能性。
HttpClient 通過高效,即時,並且富有特色的包填補 java.net 這一空缺,實現了 HTTP 絕大部分的標准和建議的客戶端。
用於擴展,並為基礎 HTTP 協議提供魯棒性支持的 HttpClient 將受到那些構建 HTTP 客戶端應用的用戶青睞,
例如 web 瀏覽器,網絡服務客戶端,或者是運用或擴展 HTTP 協議用於分布式會議。
HttpClient 適用范圍
- 客戶端 HTTP 傳輸庫基於 HttpCore
- 基於傳統(阻塞式)I/O
- 內容無關
HttpClient 不是什么
- HttpClient 不是瀏覽器,它是客戶端的 HTTP 傳輸庫。它的目的在於發送、接受 HTTP 信息。如果沒有明確設置,或者
重新格式化請求 / 重寫定位 URI, 或其它不涉及 HTTP 傳輸的功能,HttpClient 不會去
解析內容,運行嵌入在 HTML 頁面的 javascript 代碼,獲取內容類型(content type)。
基礎
1.1) 執行請求
HttpClient 中最重要的功能是執行 HTTP 方法。執行 HTTP 方法包含一個或多個 HTTP 請求/響應交換,
通常是在 HttpClient 內部處理。使用者需要提供一個要執行的請求對象,HttpClient 會向目標服務器發送請求,
返回相應的響應對象,或者在執行失敗時拋出異常。
HttpClient API 的主要入口是 HttpClient 接口,它定義了上面所述的規則。
這里有一個關於請求執行過程的例子,格式很簡單:
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.excute(httpget); try { // TODO } finally { response.close(); }
- 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 提供了 URIBuilder 工具類來簡化請求 URI 的創建和修改。
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());
stdout >
http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=
- 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());
stdout >
HTTP/1.1
200
OK
HTTP/1.1 200 OK
- 使用消息頭
HTTP 消息可以包含一些用來描述像 content length, content type 這些的頭部信息。HttpClient 提供了檢索、
添加、移除和枚舉頭部的方法。
HttpResponse response = enw 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);
stdout >
Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path="/", c3=c; domain="localhost" 2
用 HeaderIterator 接口來獲取給定類型的所有頭部是最高效的方式。
HttpResponse response = enw 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()); }
stdout >
Set-Cookie: c1=a; path=/; domain=localhost Set-Cookie: c2=b; path="/", c3=c; domain="localhost"
迭代器同時提供了方便的方法來將 HTTP 消息解析成獨立的頭部元素。
HttpResponse response = enw 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]); } }
stdout >
c1 = a path=/ domain=localhost c2 = b path=/ c3 = c domain=localhost
- HTTP 實體
HTTP 消息能夠攜帶與請求或響應相關的內容實體。這些實體能在某些請求和某些響應中找到,因為它們是可選的。
使用實體的請求指的是封裝請求的實體。HTTP 協議規定了兩種實體封裝請求方法:POST 和 PUT。
響應通常被認為是封裝內容的實體。當然這個規定也有例外,比如 HEAD 方法的響應和
204 No Content,304 Not Modified, 205 Reset Content 響應
HttpClient 能按照它們的內容來源分辨出三種實體:
- streamed: 該內容是從一個流中獲取的,或者是突然生成的。特別的,這一類包含了正被 HTTP 響應接受的實體。
流式實體通常不能復現。 - self-contained: 內存中的,或者不是通過連接及其它實體獲得的內容。自包含(Self-Contained)實體一般可以復現。
這一類實體絕大多數是用於封裝 HTTP 請求的實體。 - wrapping: 從其它實體中獲取的內容
當從 HTTP 響應發出內容時,這個分類對管理連接就很重要了。對於應用生成的那些請求實體,它們只用 HttpClient 發送,
流式(Streamed)和自包含(self-contained)之間的區別就不那么重要了。這時,建議將不重復的實體划為流式(streamed),
可重復的實體划為自包含的(self-contained)。
===
使用可重復的實體
由於一個實體既可以表示二進制內容,又能表示字符內容,因此它支持字符編碼(為了支持后者,即字符內容)。
當發送封裝有內容的請求或者成功收到請求后響應內容要被返回給客戶端時,實體就會被創建。
要從實體中讀出內容,一種方法是通過 HttpEntity#getContent() 方法查詢輸入流,getContent() 方法返回
一個 java.io.InputStream 對象。另一種方法是提供一個輸出流給 HttpEntity#writeTo(OutputStream) 方法,
這會一次性返回所有被寫入給定流內的內容。
通過傳入的信息接收到實體后,HttpEntity#getContentType() 和 HttpEntity#getContentLength() 方法可以用來讀出
大多數元數據,比如 Content-Type 和 Content-Length 頭(如果能夠獲取的話)。因為 Content-Type 頭可以包含
文本多媒體類型的字符編碼,像 text/plain 或者是 text/html,HttpEntity#getContentEncoding() 方法可以讀出這些信息。
如果無法獲取頭部信息的話,返回的長度就會是 -1 ,類型是 NULL。如果 Content-Type 頭部可獲得,那么將返回
一個 Header 對象。
要發送消息時,創建一個實體,需要向實體創建者提供元數據。
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);
stdout >
Content-Type: text/plain; charset=utf-8
17
important message
17
- 確保基礎資源的釋放
為了確保系統資源的合理釋放,必須關閉與實體相關聯的內容流或者響應。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.excute(httpget); try { HttpEntity entity = response.getEntity(); if(entity != null) { InputStream instream = entity.getContent(); try { // do something useful } finally { instream.close(); } } } finally { response.close(); }
關閉內容流和關閉響應的區別在於前者會試着通過消耗實體內容來保持基礎連接,而后者會立即切斷並中止連接。
請注意,一旦實體被完全寫入到輸出流中后, HttpEntity#writeTo(OutputStream) 方法同樣需要保證系統資源的釋放。
如果這個方法要用到一個調用 HttpEntity#getContent() 方法得到的 java.io.InputStream 的實例,那么這個實例也應該在
finally 塊中關閉。
使用流式實體時,可以調用 EntityUtils#consume(HttpEntity) 方法來保證實體內容被完全消耗,也關閉了基礎連接。
但也可能某些情況,當整個響應內容的一小部分需要被檢索,而消耗剩余內容及連接可復用造成的性能代價實在是太高了,
這時就要關閉響應來中止內容流。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.excute(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(); }
連接不會被復用,但是其中包含的所有資源將會被正確釋放。
- 消耗實體內容
推薦一種消耗實體內容的方式,使用 HttpEntity#getContent() 或 HttpEntity#writeTo(OutputStream) 方法。HttpClient 配合 EntityUtils 類,這個類用幾個靜態方法,能讓讀取實體中的內容或信息更加簡單。
不是直接讀取 java.io.InputStream 對象,你可以用這個類的某些方法,用字符串或是字節數組來檢索整個內容主題。
但是強烈不推薦使用 EntityUtils 類,除非響應實體來源於可信的 HTTP 服務器,並且知道它的最大長度。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/"); CloseableHttpResponse response = httpclient.excute(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(); }
有時候多次讀取實體內容很必要。這是實體內容就需要用某種方式緩存着,要么在內存里,要么在硬盤里。
最簡單的方式是將原始實體用 BufferedHttpEntity 類封裝。這就會把原始實體的內容放在內存緩沖區里。
在其它任何方面,實體封裝器都有一個原始的實體。
CloseableHttpResponse response = httpclient.excute(httpget);
HttpEntity entity = response.getEntity();
if(entity != null) { entity = new BufferedHttpEntity(entity); }
- 產生實體內容
HttpClient 提供了幾個類,它們能夠通過 HTTP 連接來高效地輸出內容。這些類的實例可以與封裝請求的
實體(比如 POST 和 PUT)關聯,可以把實體內容封裝到輸出的 HTTP 請求中。 HttpClient 提供幾個類,
適用於大多數常見的數據,諸如字符串,字節數組,輸入流和文件: 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://localhost/action.do"); httppost.setEntity(entity);
注意 InputStreamEntity 不可復用,因為它僅能從基礎數據流讀取一次。一般來說推薦實現一個自定義的 HttpEntity 類
是一個良好的開頭,這個類是自包含的而不是使用普通的 InputStreamEntity, FileEntity。
===
HTML 表單
許多應用需要模擬提交 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://localhost/handler.do"); httppost.setEntity(entity);
UrlEncodedFormEntity 實例會用所謂的 URL 編碼來編碼參數,產生以下內容:
param1=value1¶m2=value2
===
內容塊
一般來講推薦讓 HttpClient 根據發送的 HTTP 消息屬性來選擇最合適的轉碼。但也有可能要告訴 HttpClient 某個塊優先編碼,
將 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://localhost/action.do"); httppost.setEntity(entity);
- Response handlers
最簡單、最方便的處理響應的方式是使用 ResponseHandler 接口,它包含了 handleResponse(HttpResponse response)方法。
這個方法徹底的解決了用戶關於連接管理的擔憂。使用 ResponseHandler 時,HttpClient 會自動處理連接,無論執行請求成功
或是發生了異常,都確保連接釋放到連接管理者。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpget = new HttpGet("http://localhost/json"); ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() { @Override public JsonObject handleResponse(final HttpResponse response) throws IOException { StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if(statusLine.getStatusCode() >= 300) { 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, MyJsonObject.class); } }; MyJsonObject myjson = client.execute(httpget, rh);
1.2) HttpClient 接口
HttpClient 接口代表了執行 HTTP 請求的最重要的規則。它利用請求執行過程中的無限制或特殊細節,
把特定的連接管理,狀態管理,授權和重定向處理留給了不同的實現者。這更容易用別的功能(比如緩存響應內容)來完善接口。
一般來說 HttpClient 的實現是充當外觀,暴露一些特殊意圖的處理者或者策略接口,來合理的處理實現 HTTP 協議中特殊的
部分,比如重定向、認證或確定連接持久化和持續活躍。這保證了用戶可以選擇使用自定義的,應用指定的實現方式來替換
默認的實現方式。
ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { long keepAlive = super.getKeepAliveDuration(respnse, context); if(keepAlive == -1) { // Keep connections alive 5 secondes if a keep-alive value // has not be explicitly set by the server keepAlive = 5000; } return keepAlive; } }; CloseableHttpClient httpclient = HttpClients.custom().setKeepAliveStrategy(keepAliveStrat).build();
- HttpClient 線程安全
HttpClient 的實現應該是線程安全的,建議在執行多次請求時復用同一個 HttpClient 對象。
- HttpClient resource deallocation
當不再需要 CloseableHttpClient 的實例時,並且要離開作用域時,與之相關聯的連接管理器必須
要通過 CloseableHttpClient#close() 方法關閉。
CloseableHttpClient httpclient = HttpClients.createDefault();
try{ // TODO // <some code> } finally { httpclient.close(); }
1.3) HTTP 運行的上下文
原聲的 HTTP 被設計為弱狀態,請求-響應式的協議。但實際應用常常需要能夠通過幾個邏輯相關的請求-響應交換
來持久化狀態信息。為了讓應用能保持在處理中的狀態,HttpClient 允許在一個特殊的運行上下文中執行 HTTP 請求,
這就是 HTTP 運行時上下文。如果在連續請求時復用同一個上下文,能把各種邏輯相關的請求加入到一個邏輯會話中。
HTTP 上下文的作用類似於 java.util.Map<String, Object>。它僅是一個鍵值對集合。應用可以設置上下文中的屬性,
優先執行請求或者在執行完畢后檢驗上下文。
HttpContext 能包含任意的對象,可能在多線程下共享工作時不太安全。建議每個執行線程都保持自己的上下文。
在執行 HTTP 請求 這一節里,HttpClient 向執行上下文中添加了以下屬性:
- HttpConnection 代表與目標服務器實際連接的實例
- HttpHost 代表目標主機的實例
- HttpRoute 代表完整的連接路由的實例
- HttpRequest 代表實際的 HTTP 請求的實例。執行上下文中的
HttpRequest對象總是表示明確的消息狀態,因為它
是發送到服務器中的。每個默認的 HTTP/1.0 和 HTTP/1.1 協議使用相對應的 URI。但如果請求是是通過非通道模式的代理發送的,
這個 URI 就是絕對的。 - HttpResponse 代表實際 HTTP 響應的實例
- java.lang.Boolean 代表實際請求是否被完全發送到目標主機的標志的對象
- RequestConfig 代表實際的請求配置的對象
- java.util.List 代表在執行請求過程中的所有重定向的地址的對象
可以用 HttpClientContext 適配器類簡化與上下文狀態的交互。
HttpContext context = <...> HttpClientContext clientContext = HttpClientContext.adapt(context); HttpHost target = clientContext.getTargetHost(); HttpRequest request = clientContext.getRequest(); HttpResponse response = clientContext.getResponse(); RequestConfig config = clientContext.getRequestConfig();
許多代表同一個邏輯關聯會話的請求隊列應該在同一個 HttpContext 實例下執行,確保在請求間自動傳播會話上下文和狀態信息。
下面的例子中,初始請求設置的請求配置將會被保存到運行時上下文中,並會被應用到該上下文中的其它請求。
CloseableHttpClient httpclient = HttpClients.createDefault();
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000) .setConnectTimeout(1000) .build(); HttpGet httpget1 = new HttpGet("http://localhost/1"); httpget1.setConfig(requestConfg); CloseableHttpResponse response1 = httpclient.excute(httpget1, context); try{ HttpEntity entity1 = response1.getEntity(); } finally { response1.close(); } HttpGet httpget2 = new HttpGet("http://localhost/2"); CloseableHttpResponse response2 = httpclient.excute(httpget2, context); try { HttpEntity entity2 = response2.getEntity(); } finally { response.close(); }
1.4) 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 count = (AtomicInteger) context.getAttribute("count"); request.addHeader("Count", Integer.toString(count.getAndIncrement())); } }) .build(); AtomicInteger count = new AtomicInteger(); HttpClientContext localContext = HttpClientContext.create(); localContext.setAttribute("count", count); HttpGet httpget = new HttpGet("http://localhost/"); for (int i = 0; i < 10; i++) { CloseableHttpResponse response = httpclient.execute(httpget, localContext); try { HttpEntity entity = response.getEntity(); } finally { response.close(); } }
1.5) 處理異常
HTTP 協議處理能夠拋出兩種類型的異常:如果發生了 I/O 錯誤,拋出 java.io.IOException 異常,比如連接超時或者
連接重置,如果發生了 HTTP 錯誤,拋出 HttpException ,比如違反了 HTTP 協議。通常認為 I/O 錯誤無關緊要並且
可以修復,而認為 HTTP 協議錯誤是致命的並且不能自動修復。請注意 HttpClient 的實現是像 ClientProtocolException
一樣重復拋出 HttpException, ClientProtocolException 是 java.io.IOException 的子類。這保證 HttpClient
的用戶在一個 catch 塊中既能處理 I/O 錯誤,也能處理違反協議的錯誤。
- HTTP 傳輸安全
了解 HTTP 協議並不適合所有類型的應用很重要。HTTP 是一個簡單的面向請求/響應式的協議,一開始就設計成支持檢索
靜態和動態生成的內容。它從未想過去支持事務型操作。例如,如果 HTTP 服務器成功接收和處理了請求,它將會按照服務端的
協議,生成響應,返回狀態碼給客戶。即便客戶端由於讀入超時、取消請求或者系統崩潰而沒能收到響應的全部內容,服務端也
不會嘗試回滾事務。如果客戶端要重試同樣的請求,服務器將不可避免地結束執行多次同樣的事務。有時候這將導致應用數據錯誤
或者應用狀態不一致。
即使 HTTP 從沒被設計成支持事務處理的協議,如果條件允許,它還是能用作重要任務的傳輸協議。確保 HTTP 傳輸層安全,
系統就要確保傳輸層的 HTTP 方法的冪等性。
- 冪等方法
HTTP/1.1 關於冪等方法的定義
方法同樣可以有冪等屬性(出了錯誤或過期的方面),N 的副作用 > 0 ,同樣的請求被視作一個請求。
換句話說,應用應該保證它能弄清楚一個方法的各種運行結果的含義。這可以做到,例如,提供一個獨特的事務 id ,用其它
方法避免運行同樣的裸機操作。
請注意這個問題不是 HttpClient 獨有的。基於瀏覽器的應用都會面臨同樣的與 HTTP 方法非冪等性相關的問題。
HttpClient 默認認為無實體封裝的方法比如 GET 和 HEAD 是冪等性的,而 POST 和 PUT 由於兼容性就不是。
- 自動解決異常
HttpClient 默認會嘗試自動解決 I/O 異常。默認的自動解決機制僅限於解決一些已知是安全的異常。
- HttpClient 不會嘗試解決邏輯錯誤或者 HTTP 協議錯誤(源自
HttpException類的異常) - HttpClient 會自動重試冪等性的方法。
- HttpClient 會自動重試那些由於傳輸異常導致的方法,盡管 HTTP 請求仍然被發送到目標服務器上(比如請求還沒有
完全發送到服務器)
- Request retry handler
為了保證自定義的異常處理機制,應該實現 HttpRequestRetryHandler 接口。
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest (IOException exception, int executionCount, HttpContext context) { if(executionCount >= 5) { // Do not retry if over max retry count return false; } if(exception instanceof InterruptedIOException) { // Timeout return false; } if(exception instanceof UnknownHostException) { // Unknown host return false; } if(exception instanceof ConnectTimeoutException) { //Connection refused return false; } if(exception instanceof SSLException) { // SSL handshake exception return false; } HttpClientContext clientContext = HttpClientContext.adapt(context); HttpRequest request = clientContext.getRequest(); boolean idempotent = !(request instanceof HttpEntityEnclosingRequest); if(!idempotent) { // Retry if the request is considered idempotent return true; } return false; } }; CloseableHttpClient httpclient = HttpClients.custom() .setRetryHandler(myRetryHandler) .build();
請注意可以使用 StandardHttpRequestRetryHandler 代替默認使用的處理器,自動重試那些根據 RFC-2616 定義為冪等性的
請求方法: GET,HEAD,PUT,DELETE,OPTIONS 和 TRACE。
1.6) 終止請求
有些情況,由於目標服務器的高負載或者很多並發請求發送給客戶端,導致 HTTP 請求沒能在預期的時間內完成。這時就有必要
提前終止請求並且解除執行線程對一個 I/O 操作的占用。HTTPClient 執行的 HTTP 請求可以在任何階段被打斷,調用HttpUriRequest#abort() 方法就可以了。這個方法是線程安全的,能被任何線程調用。當一個 HTTP 請求被終止了,它的
執行線程哪怕當時占用了一個 I/O 操作,也能確保解鎖並拋出 InterruptedIOException 異常。
1.7) 處理重定向
HttpClient 自動處理所有類型的重定向,除了某些 HTTP 規則因需要用戶干預而明令禁止的類型。參見其它重定向(狀態碼:303),
HTTP 規則要求將 POST 和 PUT 請求被轉化為 GET 請求。可以使用自定義的重定向策略,利用 HTTP 規則來解除
POST 方法自動重定向的限制。
LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy(); CloseableHttpClient httpclient = HttpClients.custom() .setRedirectStrategy(redirectStrategy) .build();
HttpClient 一般能在執行過程中重寫請求消息,每個默認的 HTTP/1.0 和 HTTP/1.1 一般用相關聯的請求 URI。同樣的,
原始請求可能會被多次重定向到其它地方。最終解譯出的 HTTP 絕對地址可以用原始請求和上下文來構建。協助方法 URIUtils#resolve
可以用來構建解譯的絕對 URI,從而生成最終的請求。這個方法包含了在重定向請求或者原始請求中的最后一個分片校驗器。
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
HttpGet httpget = new HttpGet("http://localhost:8080/"); CloseableHttpResponse respnse = httpclient.excute(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(); }
Connection management
Connection persistence
HTTP connection routing
-
Route computation
-
Secure HTTP connections
HTTP connection managers
-
Managed connections and connection managers
-
Simple connection manager
-
Pooling connection manager
-
Connection manager shutdown
Multithreaded request excution
Connection socket factories
-
Secure socket layering
-
Integration with connection manager
-
SSL/TLS customization
-
Hostname verification
HttpClient proxy configuration
HTTP state management
HTTP cookies
Cookie specifications
Choosing cookie policy
Custom cookie policy
Cookie persistence
HTTP state management and execution context
HTTP authentication
User credentials
Authentication schemes
Credentials provider
Preemptive authentication
NTLM Authentication
- NTLM connection persistence
SPNEGO/Kerberos Authentication
-
SPNEGO support in HttpClient
-
GSS/Java Kerberos Setup
-
login.conf file
-
krb5.conf / krb5.ini file
-
Windows Specific configuration
Fluent API
Easy to use facade API
- Response handling
HTTP Caching
General Concepts
RFC-2616 Compliance
Example Usage
configuration
Storage Backends
Advanced topics
Custom client connections
Stateful HTTP connections
-
User token handler
-
Persistent stateful connections
Using the FutureRequestExecutionService
-
Creating the FutureRequestExecutionService
-
Scheduling requests
-
Canceling tasks
-
Callbacks
-
Metrics
轉載地址:https://www.ctolib.com/topics-80581.html
