HttpComponents-Client學習

HttpComponents-Client 學習

點擊進入 個人爲知筆記連接(帶格式)
html

前言

HTTP大概是今天網絡上最引人注目的協議。
雖然java.net package中提供了基本的功能來經過HTTP獲取資源,但它沒有提供不少應用須要的徹底的彈性或者功能性。HttpClient尋求填補這個空白,提供一種有效的、最新的、富功能的package 來實現客戶端側的大多數最新的HTTP標準和推薦。

1 HttpClient 範圍

  • 基於HttpCore的客戶端側HTTP傳輸庫
  • 基於阻塞式I/O
  • 內容不可知

2 HttpClient不是什麼

不是一個瀏覽器。只是一個客戶端側的HTTP傳輸庫。目的是發送/接收HTTP message。
不會試圖處理content,不會試圖執行js,但會嘗試猜想content type -- 若是沒有明確指定的話,或者從新格式化請求/重寫location URI,或者其餘無關於HTTP傳輸的功能。
 

第一章 基礎

1.1 request execution   請求的執行  

HttpClient最基礎的功能就是執行HTTP methods。一個HTTP method的執行 涉及到一個或多個HTTP request/response,一般由HttpClient內部處理。
用戶只要提供一個request object來執行,HttpClient會發送request到目標服務器,正常的話服務器會返回response object,或者,若是沒有成功執行 就拋出一個異常。
 
至關天然的,HttpClient API的入口就是HttpClient接口,其定義了上面描述的約定。
這裏是一個例子,request execution的過程,最簡單的形式:
1 CloseableHttpClient httpclient = HttpClients.createDefault();
2 HttpGet httpget = new HttpGet("http://localhost/");
3 CloseableHttpResponse response = httpclient.execute(httpget);
4 try {
5     <...>
6 } finally {
7     response.close();
8 }
 

1.1.1 HTTP request  

HttpClient 自然支持HTTP/1.1中全部的HTTP methods。每種method都有一個相應的類:HttpGet、HttpHead、HttpPost、HttpPut、HttpDelete、HttpTrace、HttpOptions。-- 見HttpCore的學習。
 
HttpClient提供了URIBuilder 工具類來簡化request URI的建立和修改。
 1 URI uri = new URIBuilder()
 2     .setScheme("http")
 3     .setHost("www.google.com")
 4     .setPath("/search")
 5     .setParameter("q", "httpclient")
 6     .setParameter("btnG", "Google Search")
 7     .setParameter("aq", "f")
 8     .setParameter("oq", "")
 9     .build();
10 HttpGet httpget = new HttpGet(uri);
11 System.out.println(httpget.getURI());
 

1.1.2 HTTP response

請查看HttpComponents-Core中相關的部分。
1 HttpResponse response = new asicHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK");
2 System.out.println(response.getProtocolVersion());
3 System.out.println(response.getStatusLine().getStatusCode());
4 System.out.println(response.getStatusLine().getReasonPhrase());
5 System.out.println(response.getStatusLine().toString());
 

1.1.3 處理message headers

 
請參考HttpComponents-Core中部分。

1.1.4 HTTP entity

請參考HttpComponents-Core中部分。

1.1.5 確保釋放low level resources

 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         InputStream instream = entity.getContent();
 8         try {
 9             // do something useful
10         } finally {
11             instream.close();
12         }
13     }
14 } finally {
15     response.close();
16 }

 

關閉content stream 和 關閉response的區別在於:前者會試圖保持底層的鏈接,後者則會關掉並遺棄該鏈接。<TODO>
 
有一些狀況,當須要獲取整個response content的不多一部分,而且,消費剩餘的content具備性能懲罰,讓鏈接重用過高,這種狀況下,用戶能夠經過關閉response來結束content stream。(說啥了??)
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         InputStream instream = entity.getContent();
 8         int byteOne = instream.read();
 9         int byteTwo = instream.read();
10         // Do not need the rest 不須要剩下的部分
11     }
12 } finally {
13     response.close();
14 }

 

1.1.6 消費entity content

消費entity content的推薦方式是使用HttpEntity#getContent() 或者 HttpEntity#writeTo(OutputStream)方法。
HttpClient 也提供了EntityUtils類,能夠直接獲取整個content body,以String、byte array形式。而後,除非你response entity來自一個受信任的HTTP server,而且已知限制長度,不然極其不推薦使用。
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/");
 3 CloseableHttpResponse response = httpclient.execute(httpget);
 4 try {
 5     HttpEntity entity = response.getEntity();
 6     if (entity != null) {
 7         long len = entity.getContentLength();
 8         if (len != -1 && len < 2048) {
 9         System.out.println(EntityUtils.toString(entity));
10         } else {
11         // Stream content out
12         }
13     }
14 } finally {
15 response.close();
16 }

 

 
某些狀況下,可能有必要 可以屢次讀取entity content -- 可重複讀取 <TODO>。這種狀況下, entity content必須以某種形式buffer一下,或者在內存中,或者在磁盤中。最簡單的方式就是使用BufferedHttpEntity來包裝一下。
1 CloseableHttpResponse response = <...>
2 HttpEntity entity = response.getEntity();
3 if (entity != null) {
4     entity = new BufferedHttpEntity(entity);
5 }
 

1.1.7 生成entity content

HttpClient提供了幾個類,能夠有效地經過HTTP鏈接stream out content。這些類的實例 能夠關聯到包含entity的request,例如POST、PUT,以在發出的HTTP request中包含entity content。
HttpClient提供了幾個類用於最多見的數據容器,例如字符串、byte array、input stream、file:StringEntity、ByteArrayEntity、InputStreamEntity、FileEntity。
1 File file = new File("somefile.txt");
2 FileEntity entity = new FileEntity(file,
3 ContentType.create("text/plain", "UTF-8"));
4 HttpPost httppost = new HttpPost("http://localhost/action.do");
5 httppost.setEntity(entity);
 

1.1.7.1 HTML forms  表單 <TODO>

不少應用須要模擬提交HTML表單的過程,例如 爲了登陸某個web應用,或者提交input數據。HttpClient提供了UrlEncodedFormEntity來促進該過程:
1 List<NameValuePair> formparams = new ArrayList<NameValuePair>();
2 formparams.add(new BasicNameValuePair("param1", "value1"));
3 formparams.add(new BasicNameValuePair("param2", "value2"));
4 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, Consts.UTF_8);
5 HttpPost httppost = new HttpPost("http://localhost/handler.do");
6 httppost.setEntity(entity);
 

1.1.7.2 Content chunking

通常,推薦讓HttpClient自行選擇最合適的傳輸編碼 -- 基於要傳輸的HTTP message的properties。
然而,也能夠告知HttpClient 推薦chunk編碼 -- 經過設置HttpEntity#setChunked()。
請注意, HttpClient 僅會將這個flag視爲一個暗示 <TODO>。當使用不支持chunk coding的協議版本時,該值會被忽略,如HTTP/1.0。
1     StringEntity entity = new StringEntity("important message",
2     ContentType.create("plain/text", Consts.UTF_8));
3     entity.setChunked(true);
4     HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
5     httppost.setEntity(entity);
 

1.1.8 Response handlers   響應處理器 <TODO>

最簡單的和最有效的處理響應的方式,是使用ResponseHandler接口,它有一個handleResponse(response)放那功夫。該方法徹底解放了用戶,沒必要再擔憂鏈接管理問題。
當使用一個ResponseHandler時,HttpClient會自動注意確保鏈接釋放到鏈接管理器,不管請求執行成功 仍是致使了異常。
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpGet httpget = new HttpGet("http://localhost/json");
 3 ResponseHandler<MyJsonObject> rh = new ResponseHandler<MyJsonObject>() {
 4     @Override
 5     public JsonObject handleResponse(final HttpResponse response) throws IOException {
 6         StatusLine statusLine = response.getStatusLine();
 7         HttpEntity entity = response.getEntity();
 8         if (statusLine.getStatusCode() >= 300) {
 9             throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
10         }
11         if (entity == null) {
12             throw new ClientProtocolException("Response contains no content");
13         }
14         Gson gson = new GsonBuilder().create();
15         ContentType contentType = ContentType.getOrDefault(entity);
16         Charset charset = contentType.getCharset();
17         Reader reader = new InputStreamReader(entity.getContent(), charset);
18         return gson.fromJson(reader, MyJsonObject.class);
19     }
20 };
21 MyJsonObject myjson = client.execute(httpget, rh);
 

1.2 HttpClient 接口

該接口表明了HTTP request execution的大多數的基本約定。
它沒有給處理request execution強加任何限制 或者專門的細節,諸如鏈接管理、狀態管理、認證和重定向管理 都留給特定的實現去處理。這樣,更易於使用更多的功能來裝飾該接口,如content caching。
 
通常,HttpClient的實現們扮演了一個門面(facade),能夠導向不少個特定目的的handler或者strategy接口實現 -- 用於處理HTTP protocol的某個特定方面,如重定向或者認證處理 或者決定鏈接是否保存。這使得用戶能夠有選擇的替換掉默認的實現 -- 使用自定義的。
 1 ConnectionKeepAliveStrategy keepAliveStrat = new DefaultConnectionKeepAliveStrategy() {
 2     @Override
 3     public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
 4         long keepAlive = super.getKeepAliveDuration(response, context);
 5         if (keepAlive == -1) {
 6             // Keep connections alive 5 seconds if a keep-alive value
 7             // has not be explicitly set by the server
 8             keepAlive = 5000;
 9         }
10         return keepAlive;
11     }
12 };
13 CloseableHttpClient httpclient = HttpClients.custom().setKeepAliveStrategy(keepAliveStrat).build();
 

1.2.1 HttpClient 線程安全性 <TODO>

HttpClient的實現們應該是線程安全的。推薦 該類的同一個實例被複用到多個request execution。
 

1.2.2 HttpClient 資源的釋放

當再也不須要CloseableHttpClient的某個實例,且該實例將要退出其關聯的鏈接管理器的範圍以外時,必須經過調用CloseableHttpClient#close()方法來關閉。
1 CloseableHttpClient httpclient = HttpClients.createDefault();
2 try {
3     <...>
4 } finally {
5     httpclient.close();
6 }
 

1.3 HTTP execution context

見HttpComponents-Core學習。
HttpContext。
在HTTP request execution 過程當中,HttpClient 爲execution context添加了以下attributes:
  • HttpConnection實例,表明到目標服務器的實際鏈接。
  • HttpHost實例,表明鏈接目標。
  • HttpRoute實例,表明完整的鏈接route。<TODO>
  • HttpRequest實例,表明實際的HTTP request。在execution context中最終的HttpRequest對象,老是表明了發送到目標服務器的message state。默認狀況下,HTTP/1.0 和 HTTP/1.1 使用相對的request URI。然而,若是request是經過proxy發送,在non-tuneling模式下,那URI就是絕對的。
  • HttpResponse實例,表明了實際的HTTP response。
  • Boolean對象,一個flag,用於指示實際請求是否被徹底地發送到了鏈接目標。
  • RequestConfig對象,表明了實際的請求配置。
  • List<URI>對象,表明了在request execution過程當中接收到的全部重定向地址的集合。
用戶可使用HttpClientContext adapter類來簡化與context state的交互。
1 HttpContext context = <...>
2 HttpClientContext clientContext = HttpClientContext.adapt(context);
3 HttpHost target = clientContext.getTargetHost();
4 HttpRequest request = clientContext.getRequest();
5 HttpResponse response = clientContext.getResponse();
6 RequestConfig config = clientContext.getRequestConfig();
個人備註:我猜,其實就是加了一些方法,能夠獲取HttpClient添加的attribute。若是用HttpContext的話,就只能本身輸入key了?
 
表明一個邏輯相關的session的多個請求序列,應該放到相同的HttpContext實例中執行,以確保在requests直接自動傳播對話上下文和狀態信息。
在下面的例子中, 由初始的request設置的request configuration,會被保存在execution context中,並被傳播到同一個context的後續的requests。<TODO>
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1000).setConnectTimeout(1000).build();
 3 HttpGet httpget1 = new HttpGet("http://localhost/1");
 4 httpget1.setConfig(requestConfig);
 5 CloseableHttpResponse response1 = httpclient.execute(httpget1, context);
 6 try {
 7     HttpEntity entity1 = response1.getEntity();
 8 } finally {
 9     response1.close();
10 }
11 HttpGet httpget2 = new HttpGet("http://localhost/2");
12 CloseableHttpResponse response2 = httpclient.execute(httpget2, context);
13 try {
14     HttpEntity entity2 = response2.getEntity();
15 } finally {
16     response2.close();
17 }
 

1.4 HTTP 協議攔截器

見HttpComponents-Core學習。
下面的例子,示意瞭如何在多個相關聯的requests之間共享數據:
 1 CloseableHttpClient httpclient = HttpClients.custom().addInterceptorLast(new HttpRequestInterceptor() { // 添加攔截器
 2     public void process(final HttpRequest request, final HttpContext context)
 3             throws HttpException, IOException {
 4         AtomicInteger count = (AtomicInteger) context.getAttribute("count");
 5         request.addHeader("Count", Integer.toString(count.getAndIncrement()));
 6     }
 7 }).build();
 8 AtomicInteger count = new AtomicInteger(1);
 9 HttpClientContext localContext = HttpClientContext.create();
10 localContext.setAttribute("count", count); // 初始化數據
11 HttpGet httpget = new HttpGet("http://localhost/");
12 for (int i = 0; i < 10; i++) {
13     CloseableHttpResponse response = httpclient.execute(httpget, localContext);
14     try {
15         HttpEntity entity = response.getEntity();
16     } finally {
17         response.close();
18     }
19 }
20 // 缺省了一步 localContext.getAttribute("count")
 

1.5 異常處理

HTTP 協議處理器們,可能拋出兩種類型的異常IOException 和 HttpException。
見HttpComponents-Core學習。
 

1.5.1 HTTP傳輸安全性

要理解 HTTP協議並不能很好地適合全部類型的應用,這很重要。
例如,不支持事務操做。
要確保HTTP傳輸層安全性,系統必須確保HTTP methods在應用層上的冪等性(idempotency)。

1.5.2 冪等methods

HTTP/1.1 spec定義了冪等method:除了error或者失效問題外,N次request的結果等同於一次request的結果。
 
默認,HttpClient 認定只有無entity的methods纔是冪等的,例如GET/HEAD。而帶有entity的則不是,如POST/PUT,這是出於兼容性考慮。
 

1.5.3 自動的異常恢復

默認,HttpClient會試圖自動恢復I/O 異常。默認的自動恢復機制 侷限於幾個異常:
  • HttpClient不會試圖恢復任何邏輯的或者HTTP協議錯誤(派生自HttpException)。
  • HttpClient會自動地重試那些冪等的方法。
  • HttpClient會自動的重試那些 HTTP request仍被髮送中的傳輸異常(例如 request沒有徹底發送)。
 

1.5.4 request retry handler    請求重試處理器

想要啓用自定義的異常恢復機制,用戶應該提供HttpRequestRetryHandler接口的實現。 <TODO>
 1 HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
 2     public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
 3         if (executionCount >= 5) {
 4             // Do not retry if over max retry count
 5             return false;
 6         }
 7         if (exception instanceof InterruptedIOException) {
 8             // Timeout
 9             return false;
10         }
11         if (exception instanceof UnknownHostException) {
12             // Unknown host
13             return false;
14         }
15         if (exception instanceof ConnectTimeoutException) {
16             // Connection refused
17             return false;
18         }
19         if (exception instanceof SSLException) {
20             // SSL handshake exception
21             return false;
22         }
23         HttpClientContext clientContext = HttpClientContext.adapt(context);
24         HttpRequest request = clientContext.getRequest();
25         boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
26         if (idempotent) {
27             // Retry if the request is considered idempotent
28             return true;
29         }
30         return false;
31     }
32 };
33 CloseableHttpClient httpclient = HttpClients.custom().setRetryHandler(myRetryHandler).build();
請注意,用戶可使用StandardHttpRequestRetryHandler 來代替默認的,以便安全地自動重試RFC-2616中定義的冪等methods:GET, HEAD, PUT, DELETE, OPTIONS, TRACE。 <TODO>
 

1.6 放棄request  <TODO>

某些狀況下,HTTP request execution不能在預期的時間內完成,這多是因爲目標服務器的高荷載,或者是因爲客戶端側的大量併發請求致使的。這種狀況下,永久的結束request,並解鎖阻塞的執行線程,是很重要的。
HttpClient執行的HTTP requests,能夠在任何執行階段 經過調用HttpUriRequest#abort() 方法 來放棄。
該方法是線程安全的,能夠由任何線程調用。
當一個HTTP request被放棄以後,它的執行線程--哪怕正在阻塞中--也會被釋放,會拋出一個InterruptedIOException。
 

1.7 重定向處理

HttpClient 會自動處理全部類型的重定向,除了那些HTTP spec明確禁止的 -- 這須要用戶的介入。
對於POST、PUT requests 返回的  303 See Other 重定向,會被轉換成GET requests -- HTTP spec要求的。
用戶可使用一個自定義的重定向策略來放寬HTTP spec對POST請求的重定向的限制。
1 LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();
2 CloseableHttpClient httpclient = HttpClients.custom().setRedirectStrategy(redirectStrategy).build();
 
HttpClient常常須要重寫request message。默認,HTTP/1.0和HTTP/1.1,會使用相對的request URIs。相似的,原始的request 可能被屢次重定向。最終的HTTP地址,可使用原始的request和context來構建。可使用工具方法 URIUtils#resolve() 。 <TODO>
 1 CloseableHttpClient httpclient = HttpClients.createDefault();
 2 HttpClientContext context = HttpClientContext.create();
 3 HttpGet httpget = new HttpGet("http://localhost:8080/");
 4 CloseableHttpResponse response = httpclient.execute(httpget, context);
 5 try {
 6     HttpHost target = context.getTargetHost();
 7     List<URI> redirectLocations = context.getRedirectLocations(); // 爲嘛??
 8     URI location = URIUtils.resolve(httpget.getURI(), target, redirectLocations);
 9     System.out.println("Final HTTP location: " + location.toASCIIString());
10     // Expected to be an absolute URI
11 } finally {
12     response.close();
13 }

 

 

第二章 鏈接管理

2.1 鏈接持久化

建立從一個主機到另外一個主機的鏈接,是一個很複雜的過程,涉及到多組 介於兩個端點間的packet交換 -- 可能至關耗時。鏈接握手的overhead(管理費用?) 可能至關明顯,特別是對於小的HTTP messages來講。若是已經打開的鏈接能夠給多個requests重複使用的話,用戶能夠得到一個很高的數據吞吐量。
 
默認,HTTP/1.1 就能夠重用HTTP鏈接。HTTP/1.0端點也可使用一種機制來顯式地交流他們的首選項 來保持鏈接alive,並將其用於多個requests。HTTP agents也能夠在一段特定時間內保持空閒鏈接alive,以防後續的requests也須要訪問同一個目標主機。保持鏈接alive的能力 一般被叫作 鏈接持久性。
 
HttpClient徹底支持連接持久化。
 

2.2 HTTP connection routing   鏈接路由?

HttpClient 可以建立到目標主機的鏈接,能夠是直接的,或者經過一個路由 -- 可能涉及多箇中間級鏈接,這個中間級鏈接也被叫作hops。 Httpclient 將一個路由的鏈接 劃分紅plain、tuneled、以及layered。使用到tunnel鏈接的多箇中間級代理,也叫proxy chaining。
plain route  
tunneled route
layered route
原文:
    HttpClient is capable of establishing connections to the target host either directly or via a route that may involve multiple intermediate connections - also referred to as hops. HttpClient differentiates connections of a route into plain, tunneled and layered. The use of multiple intermediate proxies to tunnel connections to the target host is referred to as proxy chaining.

Plain routes are established by connecting to the target or the first and only proxy. Tunnelled routes are established by connecting to the first and tunnelling through a chain of proxies to the target. Routes without a proxy cannot be tunnelled. Layered routes are established by layering a protocol over an existing connection. Protocols can only be layered over a tunnel to the target, or over a direct connection without proxies.java

 

 

2.2.1 route computation   路由計算

RouteInfo接口 表明了
HttpRoute是RouteInfo的一個強有力的實現,不可修改。
HttpTracker是RouteInfo的一個可修改的實現。
HttpRouteDirector是一個幫助類,可被用於計算出route中的下一步。
 

2.2.2 secure HTTP connection

...
 

2.3 HTTP connection managers   鏈接管理者  <TODO>

2.3.1 被管理的鏈接 和 鏈接管理者

 
 
>>>>>>>>>>>>>>>>>>>>>未完待續<<<<<<<<<<<<<<<<<<<<<
相關文章
相關標籤/搜索