原文地址:http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/index.html
前言:
目前HTTP(超文本傳輸協議)已然成為了互聯網中重要的協議。在WEB服務、互聯網應用和網絡計算的增長繼續擴大了HTTP協議的作用,超越了用戶驅動的Web瀏覽器,同時增加了需要HTTP支持的應用程序的數量。
盡管java.net包提供了可以通過HTTP協議訪問互聯網資源的基礎功能,但是它並沒有許多應用所需的靈活功能全面的功能。HttpClient尋求通過提供一個高效的,最新的和功能豐富的軟件包來實現客戶端最新的HTTP標准和建議來填補這個空白。
在具有極大的擴展性的同時也基於HTTP協議提供了很多健壯性的支持,HttpClient可以構建HTTP感知客戶端應用程序(如Web瀏覽器,Web服務客戶端或利用或擴展用於分布式通信的HTTP協議的系統)。
HttpClient scope
-
基於HttpCore的客戶端HTTP傳輸庫
-
基於經典的I/O阻塞模型
-
Content agnostic(我個人認為內容不可知為,不是想瀏覽器一樣顯示的)
-
HttpClien並不是瀏覽器.它只是一個HTTP的客戶端 . HttpClient的作用只是傳輸和接收HTTP 信息. HttpClient不會去嘗試處理HTTP協議中的內容, 或者執行在 HTML頁面中的JavaScript腳本, 也不會嘗試猜測傳輸內容的類型(Content-Type),如果沒有顯示的設置 或者重新格式化request 和從定向 URIs,或者一些與HTTP傳輸無關的功能 。
1.1. Request 的執行
在HttpClient中最常用的功能就是執行HTTP的方法:GET/POST/PUT/DELETE/TRACE/OPTIONS/HEAD.HttpClient在執行一個HTTP方法時會涉及HTTP 請求/ HTTP 應答的數據交換. 使用者通常期望HttpClient可以提供一個傳送request對象到目標服務中,然后返回一個與之對應的response對象, 或者在沒有成功執行時拋出異常。
顯然,HttpClient API的主要實體重點就是通過HttpClient接口定義上面以上描述的需求。
下面就是一個request執行過程的簡單例子:
//1.將其想象成建一個瀏覽器的過程,HttpClients我個人感覺可以類比Collection和Collections的關系,提供HTTPClient的工具 CloseableHttpClient httpclient=HttpClients.createDefault(); //2.可以想象為用什么方法去訪問服務,就像表單提交時候選擇Get還是Post HttpGet httpget=new HttpGet("http://www.baidu.com"); //3.可以想象為點擊鼠標的過程,或者是提交表單的過程。有返回值。。。。。 CloseableHttpResponse response=httpclient.execute(httpget); try { //業務處理層的東西 }finally{ response.close(); }
1.1.1. HTTP request
在HTTP協議中定義的請求報文的第一行必須包含:請求方法, 請求URI ,HTTP協議版本。
HttpClient 支持所有定義在HTTP/1.1中的請求方法的拆箱工作: GET
, HEAD
, POST
, PUT
,DELETE
,TRACE
OPTIONS
. 分別對應的類是:HttpGet
,HttpHead
, HttpPost
,HttpPut
, HttpDelete
,HttpTrace
, HttpOptions。
URI是一個統一資源定位符用來標識想要從哪獲取資源. HTTP request URIs包含:protocol scheme(http://,https://),host name ,port, resource path, 可選的query,可選的fragment.(注:不翻譯成中文是因為在使用工具的時候,人家老外確實就是使用的這些英文來標識類或者字段、方法的),例如:
HttpGet httpget = new HttpGet("http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");
HttpClient 提供了 URIBuilder
工具類來簡化request URIs的創建和修改.
//uri就是JDK中默認的java.net.URI。可以想象為在瀏覽器中輸入網址的過程 java.net.URI uri=new URIBuilder().setScheme("http") .setHost("www.baidu.com") .setPath("/search") .setParameter("q", "httpclient") .setParameter("btnG", "Google Search") .setParameter("aq", "f") .setParameter("oq", "") .build(); //可以想象為用什么方法去訪問服務,就像表單提交時候選擇Get還是Post HttpGet httpget=new HttpGet(uri); //在get請求實體中同樣可以拆箱獲取uri httpget.getURI();
1.1.2. HTTP response
HTTP response 是在服務器在已經接收和處理請求消息之后返回給客戶端的信息. 消息中一定包含了:protocol version ,數值型的status code,狀態解釋短語textual phrase。
//在服務器中 HttpResponse response=new BasicHttpResponse(org.apache.http.HttpVersion.HTTP_1_1,org.apache.http.HttpStatus.SC_OK, "OK"); //同樣可以進行拆箱的工作獲取相關參數 System.out.println(response.getProtocolVersion()); //StatusLine 其實就是響應中的第一行,包含協議版本號,狀態,短語 System.out.println(response.getStatusLine().toString()); System.out.println(response.getStatusLine().getStatusCode()); System.out.println(response.getStatusLine().getReasonPhrase());
1.1.3. HTTP報文 headers
一個HTTP報文可能包含很多個header來表述報文的屬性例如: content length, content type等等. HttpClient提供了對http報文head進行查詢、添加、移除、枚舉的方法。
//在服務器中 HttpResponse response=new BasicHttpResponse(org.apache.http.HttpVersion.HTTP_1_1,org.apache.http.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 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);
最高效的獲取所有header的方法是使用 HeaderIterator接口。
//相當於只是取到了header HeaderIterator it = response.headerIterator("Set-Cookie"); while (it.hasNext()) { System.out.println(it.next()); }
當然也提供了非常方便的解析header elements部分報文的方法。
HeaderElementIterator hei=new BasicHeaderElementIterator(response.headerIterator("Set-Cookie")); while (it.hasNext()) { HeaderElement elem = hei.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]); }
}
1.1.4. HTTP entity
在HTTP request 和response報文中可以傳輸一個content entity. Entities是optional(比如:HttpHead方法就不會返回entity). Requests that use entities are referred to as entity enclosing requests(使用實體的請求被稱為實體封裝請求).在HTTP定義了兩種 entity enclosing 請求方法: POST
和 PUT
. 響應通常期望包含內容實體(content entity).此規則有例外情況,例如HEAD
方法和
204 No Content
,304 Not Modified
, 205 Reset Content
響應。
HttpClient 依據content的來源將entity分為三種:(官網中的分類確實讓人不知所雲。)
-
-
streamed: 內容是從流中接收到的,或者是即時生成的(generated on the fly)。特別地,該類別包括從HTTP響應接收到的實體. Streamed entities 通常不可重復
-
self-contained: 內容在內存中或通過獨立於連接或其他實體的方式獲取。Self-contained entities 通常是可重復的. 這是HTTP requests最常用的entities
-
wrapping: 通常 content 來自於另一個entity。
-
當從HTTP響應(streaming out content)流出內容時,此區別對於連接管理(connection management)很重要.使用HttpClient僅當sent的時候request entities才會由應用創建,streamed 和 self-contained 的差異不是很重要.在這種情況下,建議將不可重復的實體視為streamed,將可重復的實體視為self-contained。
1.1.4.1. Repeatable entities
一個entity可以重復也就意味着它的content可以被多次讀取. self contained entities (ByteArrayEntity
、StringEntity
)。
1.1.4.2. HTTP entities的使用
因為一個entity可以使用二進制和字符兩種形式表示, 所以entity支持字符encodings (用以支持latter, ie. character content)。
當執行帶有enclosed content的request請求(post、put)或者當請求成功返回響應體時entity會被創建。
從 entity讀取content, 既可以使用 HttpEntity#getContent()
方法, 將返回java.io.InputStream
, 也可以向HttpEntity#writeTo(OutputStream)方法提供輸出流,一旦所有內容都已寫入給定流,該方法將返回。
entity的 HttpEntity#getContentType()
和 HttpEntity#getContentLength()
方法對應的參數是header中:Content-Type
和Content-Length
headers (如果可訪問的話).因為在
header 含mime-types :text/plain 、 text/html. 如果header不可訪問則HttpEntity#getContentEncoding()
可以獲取Content-TypeContent-Length
返回-1 ,Content-Type
返回NULL .如果Content-Type
header 可以訪問則會返回Header對象
.
當為outgoing message外發消息創建實體時,該元數據必須由實體的創建者提供。
StringEntity entity=new StringEntity("important message",ContentType.create("text/plain","utf-8")); //Entity中可以包含的信息Content-Type entity.getContentType(); //Content-Length entity.getContentLength(); //支持的編碼:Content-Encoding;Content-Type: text/plain; charset=utf-8 entity.getContentEncoding(); //顯示entity的content :important message EntityUtils.toString(entity); //將entity轉化為Byte字節數組 EntityUtils.toByteArray(entity);
1.1.5. 確保釋放低級別資源
為了保證能正確釋放系統資源,必須關閉與實體或響應本身相關聯的內容流。
//將其想象成建一個瀏覽器的過程,HttpClients我個人感覺可以類比Collection和Collections的關系,提供HTTPClient的工具 CloseableHttpClient httpclient=HttpClients.createDefault(); //可以想象為用什么方法去訪問服務,就像表單提交時候選擇Get還是Post HttpGet httpget=new HttpGet("www.baidu.com"); //可以想象為點擊鼠標的過程,或者是提交表單的過程。有返回值。。。。。 CloseableHttpResponse response=httpclient.execute(httpget); try { //業務處理層的東西 HttpEntity entity=response.getEntity(); if(entity!=null) { InputStream is=entity.getContent(); try { //dosomething }finally { //關閉entity的輸入流 is.close(); } } }finally{ //關閉響應的流 response.close(); }
關閉entity的content stream和關閉response的區別在於前者將嘗試通過消費(取走)實體內容來保持底層連接,而后者立即關閉並丟棄連接。
請注意,一旦實體完全寫出,還需要使用HttpEntity#writeTo(OutputStream)方法來確保正確釋放系統資源。如果此方法通過調用HttpEntity#getContent()獲取java.io.InputStream的實例,那么也希望在finally子句中關閉流
當使用流實體時,可以使用EntityUtils#consume
(HttpEntity)方法來確保實體內容已被完全消費(其實就是我們所說的取走了),底層流已經被關閉 。
然而,可能會有情況,當只需要查詢整個響應內容的一小部分,並且取走剩余內容並使連接可重用的性能損失太高,在這種情況下,可以通過關閉終止內容流響應。
//業務處理層的東西 HttpEntity entity=response.getEntity(); if(entity!=null) { InputStream is=entity.getContent(); try { //一字節一字節的讀取 int byteOne = is.read(); int byteTwo = is.read(); //60,30 System.out.println(byteOne); System.out.println(byteTwo); // Do not need the rest }finally { //關閉entity的輸入流 is.close(); } }
連接不會重復使用,但由其持有的所有級別資源將被正確地分配。
1.1.6. Consuming entity content(我個人感覺為是使用entity的內容)
使用 HttpEntity#getContent()
和 HttpEntity#writeTo(OutputStream)
方法都能獲取到entity的內容. HttpClient 也提供了EntityUtils工具類
, 提供了許多static methods可以很容易讀取entity中的內容信息.可以直接使用java.io.InputStream方法,而不是直接讀取java.io.InputStream,可以通過使用該類的方法來查詢字符串/字節數組中的整個內容。但是強烈建議不要使用EntityUtils,除非響應實體來自受信任的HTTP服務器,並且已知其長度有限。
//業務處理層的東西 HttpEntity entity=response.getEntity(); if(entity!=null) { long len=entity.getContentLength(); //在entity內容非空且<2k的時候可以使用EntityUtils,否則使用流 if(len!=-1&&len<2048) { System.out.println(EntityUtils.toString(entity)); }else { // Stream content out } }
在某些情況下,可能需要多次讀取entity內容。在這種情況下,entity內容必須以某種方式緩存到內存或者磁盤上。最簡單的方法是通過使用BufferedHttpEntity類包裝原始實體。這將導致將原來的entity內容讀入內存緩沖區。其他的wrapper也是同樣可以獲取原始內容。
//業務處理層的東西 HttpEntity entity=response.getEntity(); if(entity!=null) { //會將entity的內容緩存到內存中 entity = new BufferedHttpEntity(entity); }
1.1.7. Producing entity content(生成 entity)
HttpClient 提供了許多實現類,通過HTTP connections可以高效輸出entity內容 . 這些類的實例可以與實體封裝請求相關聯,例如POST和PUT,以便將實體內容包含到傳出的HTTP請求中。HttpClient為大多數常見的數據容器提供了幾個類,例如字符串,字節數組,輸入流和文件:StringEntity
,ByteArrayEntity
,InputStreamEntity
, FileEntity。
//將其想象成建一個瀏覽器的過程,HttpClients我個人感覺可以類比Collection和Collections的關系,提供HTTPClient的工具 CloseableHttpClient httpclient=HttpClients.createDefault(); //FileEntity FileEntity entity=new FileEntity(new File(""),ContentType.create("text/plain","utf-8")); //post和put方法相關聯的實體 HttpPost post=new HttpPost("http://www.baidu.com"); post.setEntity(entity); httpclient.execute(post);
需要注意的是 InputStreamEntity
是不可repeatable,因為它只能從底層數據流讀取一次.通常建議實現自定義HttpEntity類,它是自包含的,而不是使用通用的InputStreamEntity。FileEntity可以是一個很好的起點。
1.1.7.1. HTML forms
許多應用需要模擬表單提交的過程, 例如, HttpClient 為了這種需求,記錄提交的數據提供了 UrlEncodedFormEntity。
//將其想象成建一個瀏覽器的過程,HttpClients我個人感覺可以類比Collection和Collections的關系,提供HTTPClient的工具 CloseableHttpClient httpclient=HttpClients.createDefault(); List<NameValuePair> formparams=new ArrayList<>(); formparams.add(new BasicNameValuePair("param1", "value1")); formparams.add(new BasicNameValuePair("param2", "value2")); UrlEncodedFormEntity entity=new UrlEncodedFormEntity(formparams); HttpPost httpPost=new HttpPost("http://www.baidu.com"); httpPost.setEntity(entity); httpclient.execute(httpPost);
UrlEncodedFormEntity實例將使用所謂的URL編碼來對參數進行編碼並產生以下內容:param1=value1¶m2=value2
1.1.7.2. Content chunking(內容分塊)
一般建議讓HttpClient根據正在傳輸的HTTP消息的屬性選擇最合適的傳輸編碼。但是,通過將HttpEntity#setChunked()設置為true,通知HttpClient可以優先使用塊編碼。請注意,HttpClient只會使用此標志作為提示。當使用不支持塊編碼的HTTP協議版本(如HTTP / 1.0)時,此值將被忽略。
StringEntity entity = new StringEntity("important message", ContentType.create("plain/text", Consts.UTF_8)); entity.setChunked(true);
1.1.8. Response handlers(響應處理器)
處理響應的最簡單和最方便的方法是使用ResponseHandler接口,其中包括handleResponse(HttpResponse response)方法。這種方法可以完全避免用戶不必擔心連接管理。當使用ResponseHandler時,HttpClient將自動處理確保將連接釋放回連接管理器,無論請求執行是成功還是導致異常。
//將其想象成建一個瀏覽器的過程,HttpClients我個人感覺可以類比Collection和Collections的關系,提供HTTPClient的工具 CloseableHttpClient httpclient=HttpClients.createDefault(); HttpPost httpPost=new HttpPost("http://www.baidu.com"); ResponseHandler<JsonObject> rh = new ResponseHandler<JsonObject>() { @Override public JsonObject handleResponse( final HttpResponse response) throws IOException { //對HttpResponse 進行處理 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();//創建Google的GSON對象 ContentType contentType = ContentType.getOrDefault(entity);//獲取Content-type中的編碼 Charset charset = contentType.getCharset(); Reader reader = new InputStreamReader(entity.getContent(), charset);//獲取字符流 return gson.fromJson(reader, JsonObject.class);//將其格式化為對象返回 } }; JsonObject json=httpclient.execute(httpPost, rh);
1.2. HttpClient interface(ConnectionKeepAliveStrategy)
HttpClient接口代表HTTP請求執行最基本的流程契約。它對請求執行過程只定義了過程方法,並將連接管理,狀態管理,身份驗證和重定向處理的具體細節留給各個實現類。這應該使得更容易使用附加功能(如響應內容緩存)來裝飾接口。
一般來說,HttpClient實現作為一個特殊用途處理程序或策略接口實現的外觀,負責處理HTTP協議的特定方面,例如重定向或身份驗證處理或決定連接持久性並保持活動持續時間。這使得用戶能夠選擇性地將這些方面的默認實現替換為具有特定應用程序的那些方面。ConnectionKeepAliveStrategy:
//設置連接KeepAlive策略 ConnectionKeepAliveStrategy keepAliveStrategy=new DefaultConnectionKeepAliveStrategy() { @Override ublic long getKeepAliveDuration(HttpResponse response, HttpContext context) { // TODO Auto-generated method stub long keeAlive=super.getKeepAliveDuration(response, context); if(keeAlive==-1) { // 如果服務器沒有顯示的設置 keep-alive的時間,就設置為保持連接5秒 keeAlive=5000; } return keeAlive; }; CloseableHttpClient httpclient=HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy).build();
1.2.1. HttpClient 是線程安全的
HttpClient實現是線程安全的。建議將此類的同一個實例重用於多個請求執行。
1.2.2. HttpClient resource deallocation
當CloseableHttpClient不再需要,與之關聯的連接管理器必須通過調用CloseableHttpClient#close()方法來關閉。
CloseableHttpClient httpclient = HttpClients.createDefault(); try { <...> } finally { httpclient.close(); }
1.3. HTTP 的執行上下文
最初HTTP被設計為無狀態的,響應請求的協議。然而,現實世界的應用程序通常需要通過幾個邏輯相關的請求 - 響應交換來保持狀態信息。為了使應用程序能夠保持處理狀態
HttpClient允許在特定執行上下文(稱為HTTP上下文)中執行HTTP請求。如果在連續請求之間重復使用相同的上下文,則多個邏輯相關請求可以使用邏輯session。HTTP上下
文功能類似於java.util.Map <String,Object>。 它只是一個任意命名值的集合。應用程序可以在請求執行之前填充上下文屬性,或在執行完成后檢查上下文。
HttpContext可以包含任意對象,因此可能不安全地在多個線程之間共享。建議每個執行線程都維護自己的上下文。
在HTTP請求執行過程中,HttpClient將以下屬性添加到執行上下文中: HttpConnection:
表示與目標服務器的實際連接的實例
-
-
HttpHost
:表示連接目標的實例. -
HttpRoute
:表示完整連接路由的實例 -
HttpRequest:
表示實際的HTTP請求實例。執行上下文中的最終HttpRequest對象總是表示消息的狀態與發送到目標服務器的狀態完全相同。 默認HTTP / 1.0和HTTP / 1.1使用相對請求URI。但是,如果請求是通過代理在非隧道模式(non-tunneling mode)下發送的,則URI將是絕對地址。 -
HttpResponse
:表示實際的HTTP響應. -
java.lang.Boolean
:表示表示實際請求是否被完全發送到連接目標的標志的對象 -
RequestConfig
:代表實際的請求配置. -
java.util.List<URI>
:表示在請求執行過程中接收的所有重定向位置的集合.
-
可以使用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實例來執行表示邏輯相關會話的多個請求序列,以確保在請求之間自動傳播會話上下文和狀態信息。
在以下示例中,由初始請求設置的請求配置將保留在執行上下文中,並被傳播到共享相同上下文的連續請求。
HttpContext context=<...>; //設置連接KeepAlive策略 ConnectionKeepAliveStrategy keepAliveStrategy=new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { // TODO Auto-generated method stub long keeAlive=super.getKeepAliveDuration(response, context); if(keeAlive==-1) { // 如果服務器沒有顯示的設置 keep-alive的時間,就設置為保持連接5秒 keeAlive=5000; } return keeAlive; } }; CloseableHttpClient httpclient=HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy).build(); //設置請求配置 RequestConfig requestConfig=RequestConfig.custom() .setSocketTimeout(1000) .setConnectTimeout(1000).build(); HttpPost httpPost=new HttpPost("http://www.baidu.com"); httpPost.setConfig(requestConfig); CloseableHttpResponse response1 = httpclient.execute(httpPost, context); try { HttpEntity entity1 = response1.getEntity(); } finally { response1.close(); } HttpGet httpget2 = new HttpGet("http://localhost/2"); CloseableHttpResponse response2 = httpclient.execute(httpget2, context); try { HttpEntity entity2 = response2.getEntity(); } finally { response2.close(); }
1.4. HTTP協議攔截器(interceptors)
HTTP協議攔截器是一個實現HTTP協議特定方面的例程。通常,協議攔截器預期作用於輸入消息的一個特定頭部或一組相關頭部,或者使用一個特定頭部或一組相關頭部填充輸出消息。協議攔截器還可以操縱包含消息的內容實體 - 透明內容壓縮/解壓縮就是一個很好的例子。 通常這是通過使用“裝飾器”模式來實現的,其中使用包裝器實體類來裝飾原始實體。 幾個協議攔截器可以組合形成一個邏輯單元.
協議攔截器可以通過HTTP執行上下文共享信息(如處理狀態)進行協作。 協議攔截器可以使用HTTP上下文來存儲一個請求或多個連續請求的處理狀態。
通常執行攔截器的順序不要緊,因為它們不依賴於執行上下文的特定狀態。 如果協議攔截器具有相互依賴關系,因此必須以特定順序執行,則應將它們按照與其預期執行順序相同的順序添加到協議處理器。
協議攔截器必須實現為線程安全。 與servlet類似,協議攔截器不應使用實例變量,除非對這些變量的訪問是同步的
這是一個例子,說明如何使用本地上下文來持續連續請求之間的處理狀態:
//設置連接KeepAlive策略 ConnectionKeepAliveStrategy keepAliveStrategy=new DefaultConnectionKeepAliveStrategy() { @Override public long getKeepAliveDuration(HttpResponse response, HttpContext context) { // TODO Auto-generated method stub long keeAlive=super.getKeepAliveDuration(response, context); if(keeAlive==-1) { // 如果服務器沒有顯示的設置 keep-alive的時間,就設置為保持連接5秒 keeAlive=5000; } return keeAlive; } }; //配置Request的interceptors攔截器,在請求中添加或者攔截特殊的頭部信息或者上下文信息 HttpRequestInterceptor httpRequestInterceptor=new HttpRequestInterceptor() { @Override public void process(HttpRequest request, HttpContext context) throws HttpException, IOException { AtomicInteger count = (AtomicInteger) context.getAttribute("count"); request.addHeader("Count", Integer.toString(count.getAndIncrement())); } }; CloseableHttpClient httpclient=HttpClients.custom().setKeepAliveStrategy(keepAliveStrategy) .addInterceptorLast(httpRequestInterceptor) .build(); AtomicInteger count=new AtomicInteger(1); HttpClientContext localContext=HttpClientContext.create(); localContext.setAttribute("count", count); RequestConfig requestConfig=RequestConfig.custom() .setSocketTimeout(1000) .setConnectTimeout(1000).build(); HttpPost httpPost=new HttpPost("http://www.baidu.com"); httpPost.setConfig(requestConfig); for(int i=0;i<10;i++) { CloseableHttpResponse response=httpclient.execute(httpPost,localContext); try { HttpEntity entity=response.getEntity(); }finally { response.close(); } }
1.5. 異常處理
HTTP protocol processors 可以拋出兩種類型的異常:java.io.IOException
(當 I/O出現異常時例如socket超時或者socket重置),HttpException表示HTTP故障,如違反HTTP協議。通常I / O錯誤被認為是非致命和可恢復的,而HTTP協議錯誤被認為是致命的,不能自動恢復。 請注意,HttpClient實現將HttpExceptions重新拋出為ClientProtocolException,它是java.io.IOException的子類。 這使HttpClient的用戶能夠從單個catch子句處理I / O錯誤和協議違規。
1.5.1. HTTP 事務安全
重要的是要了解HTTP協議不是很適合所有類型的應用程序。HTTP是一種簡單的面向請求/響應的協議,最初被設計為支持靜態或動態生成的內容檢索。從來沒有考慮到事務的處理方面。例如,如果HTTP服務器成功接收和處理請求,生成響應並將狀態代碼發送回客戶端,則HTTP服務器將考慮其部分的contract。如果客戶端由於讀取超時,請求取消或系統崩潰而無法全部收到響應,服務器將不會嘗試回滾事務。如果客戶端決定重試相同的請求,服務器將不可避免地最終不止一次地執行相同的事務。 在某些情況下,這可能導致應用程序數據損壞或應用程序狀態不一致。
即使HTTP從未被設計為支持事務處理,但如果滿足某些條件,它仍然可以用作任務關鍵應用程序的傳輸協議。為了確保HTTP傳輸層安全,系統必須確保應用層上的HTTP方法的等效性。
1.5.2. Idempotent methods(冪等方法,不是很明白這個)
在HTTP/1.1 idempotent method定義如下:
[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)不是出於兼容性原因。
1.5.3. 自動異常恢復
默認情況下,HttpClient嘗試自動從I / O異常恢復。 默認的自動恢復機制僅限於一些已知是安全的異常。
-
HttpClient不會嘗試從任何邏輯或HTTP協議錯誤(從HttpException類派生的錯誤)中恢復。
HttpClient會自動重試那些假定為冪等的方法。 -
當HTTP請求仍被傳送到目標服務器(即請求尚未完全發送到服務器)時,HttpClient將自動重試那些失敗的傳輸異常的方法。
1.5.4. 請求重試處理器
為了啟用自定義異常恢復機制,應該提供HttpRequestRetryHandler接口的實現。
//重新請求策略處理器 pRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() { public boolean retryRequest( IOException exception, int executionCount, HttpContext context) { if (executionCount >= 5) { // 超過設置的最大次數則不再重試 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而不是默認使用的HandlerRequestRetryHandler,以便通過RFC-2616定義為冪等的請求方法可以自動重試:GET,HEAD,PUT,DELETE,OPTIONS和TRACE。
1.6. 終止請求
在某些情況下,由於目標服務器的負載過高或客戶端發出的並發請求太多,HTTP請求執行在預期的時間內無法完成。在這種情況下,可能需要提前終止請求,並在I / O操作中解除阻塞執行線程。由HttpClient執行的HTTP請求可以通過調用HttpUriRequest#abort()方法在執行的任何階段中止。這種方法是線程安全的,可以從任何線程調用。 當一個HTTP請求被中止時,它的執行線程 - 即使當前在I / O操作中阻塞 - 也被保證通過拋出一個InterruptedIOException來解除阻塞;
HttpUriRequest的子類:HttpGet、HttpPost等等
HttpPost httpPost=new HttpPost("http://www.baidu.com"); httpPost.setConfig(requestConfig); for(int i=0;i<10;i++) { httpPost.abort(); CloseableHttpResponse response=httpclient.execute(httpPost,localContext); try { HttpEntity entity=response.getEntity(); }finally { response.close(); } }
1.7. 重定向處理
HttpClient會自動處理所有類型的重定向,除了要求用戶干預之外由HTTP規范明確禁止的重定向。請參閱POST上的其他(狀態碼303)重定向,並將PUT請求按照HTTP規范的要求轉換為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 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(); }