HttpClient-4.0.1 官方教程

HttpClient-4.0.1 官方教程

寫在前頭(HttpClient項目現已停產,並且不再開發)

HttpClient項目現已停產,並且不再開發。它已被其HttpClient和HttpCore模塊中的Apache HttpComponents項目取代,它們提供了更好的性能和更大的靈活性。
穩定版:
最新版:(不穩定,生產不建議使用)

項目介紹

HttpClient不是一個瀏覽器,它是一個客戶端HTTP傳輸類庫。HttpClient作用是傳輸和接收HTTP消息。HttpClient不嘗試緩存內容,執行內嵌HTML頁面中的javascript腳本,嘗試猜測內容類型,重新定義請求/重定向URI位置,其他功能與HTTP傳輸無關。

HttpClient4.0 架構方法發生的變化:

  • 重新設計了HttpClient4.0 API架構,徹底從內部解決了所有HttpClient 3.x 已知的架構缺陷代碼。
  • HttpClient 4.0 提供了更簡潔,更靈活,更明確的API。
  • HttpClient 4.0 引入了很多模塊化的結構。
  • HttpClient 4.0性能方面得到了不小的提升,包括更少的內存使用,通過使用HttpCore模塊更高效完成HTTP傳輸。
  • 通過使用協議攔截器(protocolinterceptors), HttpClient 4.0實現了交叉HTTP(cross-cuttingHTTP protocol)協議
  • HttpClient 4.0增強了對連接的管理,更好的處理持久化連接,同時HttpClient4.0還支持連接狀態
  • HttpClient 4.0增加了插件式(可插拔的)的重定向(redirect)和驗證(authentication)處理。
  • HttpClient 4.0支持通過代理髮送請求,或者通過一組代理髮送請求。
  • 更靈活的SSLcontext 自定義功能在HttpClient4.0中得以實現。
  • HttpClient 4.0減少了在省城HTTP請求和解析HTTP響應過程中的垃圾信息。
  • HttpClient團隊鼓勵所有的項目升級成 HttpClient 4.0

第一章 Fundamentals(基礎)

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 HTTPRequest (HTTP請求)

所有的HTTP請求有一個請求行包含一個方法名,請求URI和HTTP協議版本。

HttpClient支持在HTTP/1.1規範中定義的所有HTTP方法:GET、HEAD、POST、PUT、 DELETE,、TRACEand OPTIONS,用一些特殊的類來表示每一個方法:HttpGet、HttpHead、 HttpPost、HttpPut、 HttpDelete、 HttpTrace和HttpOptions。

HTTP請求URI的包括協議、主機名、可選的端口、資源的路徑、可選的查詢和可選的片段。

HttpGet httpget = new HttpGet(
     "http://www.google.com/search?hl=en&q=httpclient&btnG=Google+Search&aq=f&oq=");

HttpClient提供了一個實用的方法來簡化創建和修改請求的URIs,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 HTTPResponse (HTTP響應)

HTTP Response消息是由服務器發送回客戶端,客戶端接收並解釋的請求消息。該消息的第一行由協議版本、數字狀態碼和相關的簡短文本構成。
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

1.1.3 Workingwith message headers (處理頭部消息)

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"
2

最有效的方法來獲得給定類型的所有頭部信息是利用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=localhost

1.1.4 HTTP entity(HTTP實體)

HTTP消息能傳輸內容實體的請求或響應,在一些請求和響應中可以發現實體,因爲它們是可選的。HTTP規範定義了兩個實體包裝的方法:POST和PUT,響應通常附上內容實體。

HttpClient根據內容來源區分了三種不同類型的實體:

  • l Streamed:內容是從流接收或產生的,特別是,這類實體是從HTTP響應中接收,流媒體通常是不可重複的。

  • l self-contained:內容是在內存中或者從獨立的一個連接或其它實體中獲得,self-contained實體通常是重複的。

  • l wrapping:內容從另一個實體獲得。

1.1.4.1 Repeatable entities (可重複的實體)

一個實體可以是可重複的,這意味着它的內容可以被讀取一次以上。這是唯一可能有自我包含的實體(像ByteArrayEntity 或 StringEntity)。

1.1.4.2 Using HTTP entities (使用HTTP實體)

由於一個實體即能表示二進制內容又能表示字符內容,它支持字符編碼(支持後者,即:字符內容)。

從實體中讀取內容,通過HttpEntity的getContent方法可以取回輸入流,並返回一個java.io.InputStream,或者給HttpEntity的writeTo(OutputStream) 方法提供一個輸出流,它將一次性返回所有的被寫在流裏的內容。

當實體已收到傳入消息,HttpEntity#getContentType()和HttpEntity#getContentLength()方法可用於讀取常見的元數據,例如內容類型和內容長度(如果可用)。由於內容類型標題可以包含文本MIME類型的字符編碼,像text/plain或者 text/html,HttpEntity#getContentEncoding()方法用來讀取此信息。如果標題不可用,返回的長度爲-1,並返回內容類型爲NULL,如果內容類型標題可用,返回標題對象。

當創建一個即將卸任的消息實體,此元數據必須由實體創建者提供。

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
17

1.1.5 Ensuring release of low level resources (確保底層資源的釋放)

當響應實體完成時,重要的是要確保所有的實體內容已被完全銷燬,使該連接可以安全地返回到連接池,由以後的請求重複利用連接管理器,最簡單的方法是調用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();
}

該連接將不被重用,底層的資源將正確地釋放。

1.1.6 Consumingentity content(銷燬實體內容)

推薦的方法消耗的實體內容是通過使用其HttpEntity#getContent()或HttpEntity#writeTo(OutputStream中)方法。HttpClient的還配備了EntityUtils類,它暴露了一些靜態方法能夠更容易地從一個實體讀取內容或信息。而不是直接讀取java.io.InputStream,使用這個類的方法可以取回string/byte array整個內容體,然而,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);
}

1.1.7 Producingentity content(產生實體內容)

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 Dynamic contententities (動態的內容實體)

通常的HTTP實體需要動態生成一個特定的執行上下文,HttpClient提供了ContentProducer接口和EntityTemplate實體類支持動態實體。內容的生產者通過寫出來到一個輸出流產生他們的需求內容。因此,與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 forms (HTML表單)

許多應用程序經常需要模擬的提交的HTM表單的過程,例如,按順序登錄到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&param2=value2

1.1.7.3 Content chunking(內容組塊)

一般來說,建議讓HttpClient基於HTTP消息的屬性傳輸選擇最合適的傳輸編碼,這是可能的,然而,真正的HttpClient通知該組塊編碼的,是首選的設置HttpEntity#setChunked()爲true。請注意的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 Responsehandlers(響應處理器)

最簡單和最方便的方式來處理的響應是使用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 execution context (HTTP執行上下文)

最初的HTTP被設計成一個無狀態,面向響應/請求的協議。然而,現實世界應用程序通常需要能夠堅持響應交換狀態信息通過幾個邏輯上相關的請求。爲了使應用程序能夠保持狀態的HttpClient處理HTTP請求允許將在一個特定的上下文中執行的執行,簡稱爲HTTP上下文。多個邏輯相關的請求可以參與邏輯會話,如果同樣的情況下連續請求之間重用。HTTP上下文功能類似於java.util.Map的<String,Object> 。它只是一個任意命名的值的集合。應用程序在執行請求之前填充的上下文屬性或請求完成之後檢查上下文。

在HttpClient的HTTP請求的執行過程中添加下列屬性的執行情況:

  • ‘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.3 Exception handling (異常處理)

HttpClient可以拋出兩種類型的異常:假如發生I/0故障拋出java.io.IOException,例如socket超時,socket重置,HttpException這種信號的HTTP故障,例如違反了HTTP協議。通常的I / O錯誤被視爲非致命性和可恢復的,而HTTP協議被認爲是致命的錯誤並不可恢復的。

1.3.1 HTTPtransport safety (HTTP傳輸安全性)

最重要的一點:HTTP協議並不適合所有類型的應用程序。HTTP是一個簡單的要求/響應導向的協議是用來支持靜態或開始動態生成的內容檢索。它從未打算支持交互操作。例如,將HTTP服務器及其部分合同履行如果它成功地接受和加工要求,產生反應和發送給客戶端狀態碼。該服務器將不作任何嘗試回滾事務,如果客戶端無法接收失事事故的全部原因是讀超時,要求取消或系統。如果客戶決定重試相同的請求,服務器將不可避免地最終執行相同的交易超過一次。在某些情況下,這可能會導致數據損壞或不一致的應用程序應用程序的狀態。

雖然從來沒有被設計的HTTP支持事務處理,它仍然可以用來作爲傳輸協議的關鍵任務應用程序提供某些條件得到滿足。爲了確保HTTP傳輸層安全系統必須確保層冪等的HTTP方法應用程序。

1.3.2 Idempotentmethods (冪等的方法)

HTTP/1.1規範定義了冪等方法,【該方法也有“冪等的”屬性(除了錯誤或過期的問題之外),N> 0相同請求】。換句話說,應用程序應該預先確保相同的方法多個執行的意義。這可以實現的,例如,通過提供一個唯一的事務ID和其他手段避免執行相同的邏輯運算。

請注意,此問題不是僅限於HttpClient。基於瀏覽器應用程序是完全符合HttpClient的同樣的問題與HTTP方法非冪等相關。

1.3.3 Automatic exception recovery (自動的異常恢復)

默認情況下的HttpClient嘗試自動恢復從I / O異常。默認的自動恢復機制是限於一個是安全的少數例外的是衆所周知的。

  • HttpClient不會試圖恢復任何邏輯錯誤或HTTP協議錯誤(那些來自HttpException類)。

  • HttpClient會自動重試那些被假定爲冪等方法。

  • HttpClient會自動重試那些傳輸異常失敗的方法,HTTP請求仍然被傳輸到目標服務器。

  • HttpClient會自動重試的完全傳輸到服務器方法,但服務器沒有響應HTTP狀態代碼(服務器僅僅放棄連接不發送任何東西),在這種情況下,假定要求服務器和應用程序沒有改變狀態。如果這個假設未必如此你的應用程序服務器強烈建議提供一個自定義的異常處理器。

1.3.4 Request retry handler (請求重試處理程序)

自定義異常恢復機制將提供HttpRequestRetryHandler接口的實現。

DefaultHttpClient httpclient = new DefaultHttpClient();
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 NoHttpResponseException) {
            // Retry if the server dropped connection on us
            return true;
        }
        if (exception instanceof SSLHandshakeException) {
            // Do not retry on SSL handshake exception
            return false;
        }
        HttpRequest request = (HttpRequest) context.getAttribute(
                ExecutionContext.HTTP_REQUEST);
        boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
        if (idempotent) {
            // Retry if the request is considered idempotent
            return true;
        }
        return false;
    }
};
httpclient.setHttpRequestRetryHandler(myRetryHandler);

1.4 Aborting requests(中止請求)

調用HttpUriRequest#abort()方法可以終止請求,這個方法是線程安全的,可以從任何線程調用。當HTTP請求中止時拋出InterruptedIOException。

1.5 HTTP protocol interceptors (HTTP協議攔截器)

HTTP協議攔截器協議是一個例行程序,實現了HTTP協議的具體內容。攔截器還可以操縱的消息封閉的實體內容,透明的內容壓縮/解壓是一個很好的例子。通常這是通過使用’裝飾’設計模式,其中一個包裝實體類是用來裝飾原始實體。幾個攔截器可以組合成一個邏輯單元。

攔截器可進行協作共享信息,如處理狀態。攔截器可以使用HTTP上下文來存儲一個或多個連續請求的處理狀態。

通常情況下攔截器順序的被執行,只要他們不依賴於執行上下文某一特定狀態。如果攔截器互相依賴,因此必須在一個特定的順序中執行。

協議攔截器必須線程安全的實現,同servlet一樣,協議攔截不應使用實例變量,除非獲得這些變量的同步。

這是一個例子,如何在局部的上下文中保存連續請求的處理狀態:

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
AtomicInteger count = new AtomicInteger(1);
localContext.setAttribute("count", count);
httpclient.addRequestInterceptor(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()));
    }
   
});
HttpGet httpget = new HttpGet("http://localhost/");
for (int i = 0; i < 10; i++) {
    HttpResponse response = httpclient.execute(httpget, localContext);
   
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        entity.consumeContent();
    }
}

1.6 HTTP攔截器必須實現爲線程安全的parameters (HTTP參數)

   HttpParams接口代表一個不可改變值的集合,定義一個組件運行時行爲。在許多方面HttpParams與HttpContext是相似的。兩個接口都代表一個對象集合,該集合是一個鍵到值的映射,但是,服務於不同的用途。
  • HttpParams適用於包含簡單的對象:integers, doubles, strings, collections和objects在運行時保持不變。

  • HttpParams預計用在“'寫一次,讀多次”方式,HttpContext適用於包含複雜對象,這些對象很可能在HTTP消息處理過程中發生變異。

  • HttpParams作用是定義其他組件的行爲,一般每個複雜的組件都有它自己的HttpParams對象。HttpContext作用是代表了一個HTTP進程的執行狀態。通常是相同的執行上下文之間共享許多合作對象。

1.6.1 Parameter hierarchies (參數的層次體系)

HttpRequest對象是HttpClient的實例用來執行連在一起的請求ParamsHttp。

允許參數設置成HTTP請求級別優先於設置成HTTP客戶端級別。

建議的做法是通過設置在HTTP客戶端級別的共同所有的HTTP請求參數和選擇性覆蓋在HTTP請求級別的具體參數。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
    HttpVersion.HTTP_1_0);
httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET,
    "UTF-8");
HttpGet httpget = new HttpGet("http://www.google.com/");
httpget.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,
    HttpVersion.HTTP_1_1);
httpget.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE,
    Boolean.FALSE);
httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
    public void process(
            final HttpRequest request,
            final HttpContext context) throws HttpException, IOException {
        System.out.println(request.getParams().getParameter(
                CoreProtocolPNames.PROTOCOL_VERSION));
        System.out.println(request.getParams().getParameter(
                CoreProtocolPNames.HTTP_CONTENT_CHARSET));
        System.out.println(request.getParams().getParameter(
                CoreProtocolPNames.USE_EXPECT_CONTINUE));
        System.out.println(request.getParams().getParameter(
                CoreProtocolPNames.STRICT_TRANSFER_ENCODING));
    }   
});

輸出:

HTTP/1.1
UTF-8
false
null

1.6.2. HTTP parameters beans

HttpParams接口在處理組件的配置方面考慮到很多靈活性。最重要的事,可以引進新的參數,而不會影響舊版本的二進制兼容性。然而,HttpParams與JAVABEAN規則相比也有一定的缺點:HttpParams不能使用DI框架裝配,爲了減輕限制,使用標準的JAVABEAN約定初始化HttpParams對象。

HttpParams params = new BasicHttpParams();
HttpProtocolParamBean paramsBean = new HttpProtocolParamBean(params);
paramsBean.setVersion(HttpVersion.HTTP_1_1);
paramsBean.setContentCharset("UTF-8");
paramsBean.setUseExpectContinue(true);
System.out.println(params.getParameter(CoreProtocolPNames.PROTOCOL_VERSION));
System.out.println(params.getParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET));
System.out.println(params.getParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE));
System.out.println(params.getParameter(CoreProtocolPNames.USER_AGENT));

輸出:

HTTP/1.1
UTF-8
false
null

1.7 HTTP request execution parameters (HTTP請求執行參數)

這些參數會影響請求執行的進程:

  • ‘http.protocol.version’:定義使用HTTP協議版本,如果沒有設定明確的請求對象。此參數要求是ProtocolVersion類型的值,如果不設置此參數將使用HTTP/1.1。

  • ‘http.protocol.element-charset’:定義了要使用的字符集編碼的HTTP協議的元素。此參數要求是java.lang.String類型的值,如果不設置此參數將使用US-ASCII。

  • ‘http.protocol.content-charset’:定義字符集用於每個內容體的默認編碼。此參數要求是java.lang.String類型的值,如果不設置此參數將使用ISO-8859-1。

  • ‘http.useragent’:定義User-Agent頭的內容。此參數要求是java.lang.String類型的值,如果此參數不設置,HttpClient會自動爲它生成值。

  • ‘http.protocol.strict-transfer-encoding’:定義是否以無效Transfer-Encoding頭的響應,應予以拒絕。此參數要求是java.lang. Boolean類型的值,如果該參數沒有設置無效Transfer-Encoding值將被忽略。

  • ‘http.protocol.expect-continue’:激活Expect: 100-Continue實體內附的方法握手,Expect: 100-Continue握手的作用是讓客戶端隨着請求主體發送一個請求消息,如果源服務器願意接受請求(基於請求頭),然後客戶端發送請求主體。使用Expect: 100-Continue握手,實體附入的請求結果性能得到明顯的改善(例如POST和PUT),謹慎使用Expect: 100-continue,因爲它可能引起問題,HTTP服務器和代理不支持HTTP 1.1協議。此參數要求是java.lang. Boolean類型的值,如果此參數不設置,HttpClient會試圖使用握手。

  • ‘http.protocol.wait-for-continue’:定義以毫秒爲單位,客戶端等待100-continue響應花費的最大一段時間。此參數要求是java.lang. Integer類型的值,如果此參數不設置,HttpClient的請求體將等待3秒爲一個確認,然後恢復傳輸。

第二章 Connectionmanagement(連接管理)

HttpClient的具有完全控制初始化連接和終止連接進程以及I/ O操作的活動連接,然而可以使用一個參數來控制連接方面的操作。當值爲0被解釋成一個無限暫停,此參數要求是java.lang. Integer類型的值,如果此參數不設置,讀操作不會超時(無限暫停)。

2.1 Connection parameters (連接參數)

這些參數可以影響連接操作:

  • ‘http.socket.timeout’:以毫秒爲單位定義套接字超時(SO_TIMEOUT)。當值爲0被解釋成一個無限的暫停,此參數要求是java.lang. Integer類型的值,如果此參數不設置,讀操作不會超時(無限暫停)。

  • ‘http.tcp.nodelay’:確定是否使用Nagle算法。Nagle 算法嘗試通過將被髮送的段數量減到最少節省帶寬。當應用程序希望降低網絡延遲提高性能,他們可以禁用Nagle算法(啓用TCP_NODELAY,以增加消耗帶寬爲代價發送數據,此參數要求是java.lang. Boolean類型的值,如果此參數不設置,啓用TCP_NODELAY)。

  • ‘http.socket.buffer-size’:接收/傳輸HTTP消息時,確定socket內部緩衝區緩存數據的大小。此參數要求是java.lang. Integer類型的值,如果此參數不設置,HttpClient將分配8192字節scoket緩衝區。

  • ‘http.socket.linger’:

  • ‘http.connection.timeout’:建立連接的超時時間(以毫秒爲單位)。當值爲0被解釋成一個無限超時,此參數要求是java.lang. Integer類型的值,如果此參數不設置,連接操作不會超時(無限超時)。

  • ‘http.connection.stalecheck’:

  • ‘http.connection.max-line-length’:確定行數最大長度的限制。如果設置爲正值,任何HTTP行超過此限制將導致IOException,負或零值將有效地禁用檢查。此參數要求是java.lang. Integer類型的值,如果此參數不設置,沒有限制。

  • ‘http.connection.max-header-count’:確定最大允許HTTP頭數。如果設置一個正值,從數據流收到的HTTP頭數超過此限制將導致一個IOException,負或零值將有效地禁用檢查,此參數要求是java.lang. Integer類型的值,如果此參數不設置,沒有限制。

  • ‘http.connection.max-status-line-garbage’:定義忽略的HTTP響應的狀態行最大行數。HTTP/1.1持續連接,該問題產生中斷腳本返回錯誤的內容長度(),不幸的是,在某些情況下,檢測不到這個錯誤的響應,所以的HttpClient必須能夠跳過這些多餘的行,此參數要求是java.lang. Integer類型的值,值爲0拒絕所有的垃圾行/空行之前的狀態行。

2.2 Connection persistence (持續連接)

從主機到另一個主機建立連接的過程是相當複雜的,涉及到兩個端點之間多個包交換是相當耗時的。人們可以實現更高的數據吞吐量,如果打開的連接可以被重新用於執行多個請求。

HTTP/1.1中指出HTTP連接默認可以重用多個請求。HTTP/1.0也符合該機制,HttpClient完全支持持續連接。

2.3 HTTP connection routing(HTTP連接路由)

HttpClient直接或間接路由建立連接到目標主機,路由涉及到多箇中間連接(跳板)。HttpClient路由連接的區別是進入線路、隧道、分層。使用多箇中間代理的連接到目標主機被稱爲代理鏈。

2.3.1 Routecomputation

RouteInfo接口信息明確表示了路由到目標主機涉及一個或多箇中間步驟或跳板。HttpRoute是RouteInfo一個具體的實現,HttpRouteDirector是一個輔助類,可以用來計算下一步路線,這個類由HttpClient內部使用。

HttpRoutePlanner接口代表一個完整的路徑來計算給定目標的基礎上的運行環境,HttpClient有兩個HttpRoutePlanner默認實現。ProxySelectorRoutePlanner基於java.net.ProxySelector,默認情況下,它取出JVM代理設置,無論是從系統屬性或從瀏覽器中運行的應用程序。DefaultHttpRoutePlanner實現不使用JAVA系統屬性,也不使用JAVA代理或瀏覽器代理設置,它計算路線完全基於HTTP的描述參數。

2.3.2 SecureHTTP connections

HTTP連接可以被視爲安全的,如果信息在兩個連接端點之間傳輸不能被讀取或未經授權的第三方篡改。SSL / TLS協議是最廣泛使用的技術,以確保HTTP傳輸安全,然而,其它加密技術也可以使用。通常,HTTP傳輸層使用SSL / TLS加密連接。

2.4 HTTP route parameters

這些參數可以影響線路計算:

  • ‘http.route.default-proxy’:定義一個代理主機使用默認的路線規劃者不使用JRE設置,此參數要求是HttpHost類型的值,如果此參數不設置,嘗試直接連接到目標地址。

  • ‘http.route.local-address’:定義一個本地地址使用默認路由規劃者。在具有多個網絡接口的機器上,這個參數可以用來選擇從哪個網絡接口連接。此參數要求是java.net.InetAddress類型的值,如果這個參數未設置自動默認本地地址將被使用。

  • ‘http.route.forced-route’:定義一個強制路由使用默認路由規劃者。代替計算路由,返回強制路由,即使它指向一個完全不同的目標主機。此參數要求是HttpRoute類型的值。

2.5 Socket factories

HTTP連接使用一個java.net.Socket對象內部通過線路處理數據傳輸。不過,他們依靠SocketFactory接口來創建,初始化連接套接字。這使得HttpClient用戶在應用程序運行時提供特定的套接字初始化代碼。PlainSocketFactory是一個默認的工廠用來創建和初始化簡單的(未加密)套接字。

建立一個套接字進程和連接到一個主機是分離的,當連接操作阻塞時,套接字可以被關閉。

PlainSocketFactory sf = PlainSocketFactory.getSocketFactory();
Socket socket = sf.createSocket();
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 8080, null, -1, params);

2.5.1 Secure socket layering

LayeredSocketFactory擴展了SocketFactory接口,套接字工廠layer在現有的普通套接字上創建套接字,套接字layering通過代理創建安全的套接字。HttpClientSSLSocketFactory實現了SSL/TSL層,請注意的HttpClient不使用任何自定義加密功能,它完全依賴於標準的Java擴展加密(JCE)和安全套接字(JSEE)。

2.5.2 SSL/TLS customization

HttpClient的使用SSLSocketFactory創建SSL連接,SSLSocketFactory允許高度定製,它可以採取javax.net.ssl.SSLContext實例作爲一個參數,並使用它來創建自定義配置SSL連接。

TrustManager easyTrustManager = new X509TrustManager() {
    @Override
    public void checkClientTrusted(
            X509Certificate[] chain,
            String authType) throws CertificateException {
        // Oh, I am easy!
    }
    @Override
    public void checkServerTrusted(
            X509Certificate[] chain,
            String authType) throws CertificateException {
        // Oh, I am easy!
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
   
};
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
SSLSocket socket = (SSLSocket) sf.createSocket();
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 443, null, -1, params);

SSLSocketFactory定製意味着對SSL/TLS協議概念有一定的瞭解,詳細解釋超出對本的範圍,請參閱【http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html】的詳細描述javax.net.ssl.SSLContext和擴展Java安全套接字相關的工具。

2.5.3. Hostname verification

除了信任驗證和客戶端身份驗證級別上執行的SSL / TLS協議,一旦連接已建立,HttpClient的可以選擇驗證目標主機名是否與存儲在服務器側的X.509證書名稱匹配。這種驗證可以對服務器的信任材料真實性提供額外的保證。X509HostnameVerifier接口表示主機名稱驗證策略,重要的是:主機名驗證不應與SSL的信任驗證混淆。

  • StrictHostnameVerifier:

  • BrowserCompatHostnameVerifier:

  • AllowAllHostnameVerifier:

2.6 Protocol schemes

Scheme類表示一個協議方案,例如"http"或者"https"和包含許多的協議屬性,例如缺省的端口和socket工廠常用於爲指定的協議創建java.net.Socket實例,SchemeRegistry類被用來維護一個Schemes的集合,HttpClient可以選擇通過一個請求URI設法建立一個連接。

Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", sf, 443);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(https);

2.7 HttpClient proxy configuration

即使HttpClient 明白複雜的路由計劃和一連串的代理,它也僅僅支持從box開始的簡單直接或一級跳轉代理連接。最簡單的方式來通知HttpClient通過代理設置一個缺省的代理參數連接目標主機。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpHost proxy = new HttpHost("someproxy", 8080);
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);

也可以指示HttpClient使用標準的JRE代理選擇器去獲得代理信息。

DefaultHttpClient httpclient = new DefaultHttpClient();
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
        httpclient.getConnectionManager().getSchemeRegistry(),
        ProxySelector.getDefault()); 
httpclient.setRoutePlanner(routePlanner);

爲了完全的控制Http路由計算,提供一個自定義RouterPlanner實現。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setRoutePlanner(new HttpRoutePlanner() {
    public HttpRoute determineRoute(
            HttpHost target,
            HttpRequest request,
            HttpContext context) throws HttpException {
        return new HttpRoute(target, null,  new HttpHost("someproxy", 8080),
                "https".equalsIgnoreCase(target.getSchemeName()));
    }
});

2.8. HTTP connection managers

2.8.1 Connection operators

操作連接是指客戶端連接,它的基礎套接字或狀態能被外部實體操作,通常是指一個連接操作者。OperatedClientConnection接口繼承HttpClientConnection接口,和定義額外的方法管理連接套接字。ClientConnectionOperator接口表示創建ClientConnectionOperator實例和更新這些對象基礎的套接字一個策略。應用充分利用SocketFactorys來創建java.net.socket實例。ClientConnectionOperator接口使HttpClient的用戶爲連接操作者提供傳統的策略,和提供交替應用OperatedClientConnection接口的能力。

2.8.2 Managed connections and connection managers

HTTP連接是複雜、有狀態的,非線程安全的對象需要適當的管理正確的功能。HTTP連接每次僅被一個執行的線程使用,HttpClient利用一個特殊的實體管理訪問HTTP連接,稱爲HTTP連接管理器,由ClientConnectionManager接口表示。HTTP連接管理器的充當一個新的HTTP連接工廠,管理持續的連接和同步的訪問持續的連接,確保每次只有一個線程能訪問連接。

內部的HTTP連接管理器與OperatedClientConnection的實例協同工作。但是他們取出ManagedClientConnection實例來服務客戶。ManagedClientConnection充當一個OperatedClientConnection實例的包裝器,管理它的狀態和控制連接的所有的I/O操作。爲了建立路由,爲打開和更新socket提供抽象的socket操作和便利的方法。ManagedClientConnection實例注意到他們對連接管理的連接來創建他們,事實上當他們不再使用時必須返回管理器。ManagedClientConnection類也應用 ConnectionReleaseTrigger接口,這個接口被用來觸發連接釋放,返回到管理器。一旦連接釋放被觸發,封裝的連接從ManagedClientConnection封裝和OperatedClientConnection實例中分離返回到管理器。即使service consumer任然控制着ManagedClientConnection實例,不能執行任何i/o操作或者改變ManagedClientConnection有意圖或者無意圖的狀態。

這是一個從連接管理器獲得連接的例子。

HttpParams params = new BasicHttpParams();
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
ClientConnectionManager connMrg = new SingleClientConnManager(params, sr);
// Request new connection. This can be a long process
ClientConnectionRequest connRequest = connMrg.requestConnection(
        new HttpRoute(new HttpHost("localhost", 80)), null);
// Wait for connection up to 10 sec
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
    // Do useful things with the connection.
    // Release it when done.
    conn.releaseConnection();
} catch (IOException ex) {
    // Abort connection upon an I/O error.
    conn.abortConnection();
    throw ex;
}

如果必要的話,連接請求可以調用ClientConnectionRequest#abortRequest()終止. 解除ClientConnectionRequest#getConnection()方法中阻塞的線程。

一旦響應內容被接收,BasicManagedEntity封裝類能確保基礎連接自動釋放。HttpClient使用內部機制取得透明的連接,從HttpClient#execute()方法中得到所有的響應,。

ClientConnectionRequest connRequest = connMrg.requestConnection(
        new HttpRoute(new HttpHost("localhost", 80)), null);
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
    BasicHttpRequest request = new BasicHttpRequest("GET", "/");
    conn.sendRequestHeader(request);
    HttpResponse response = conn.receiveResponseHeader();
    conn.receiveResponseEntity(response);
    HttpEntity entity = response.getEntity();
    if (entity != null) {
        BasicManagedEntity managedEntity = new BasicManagedEntity(entity, conn, true);
        // Replace entity
        response.setEntity(managedEntity);
    }
    // Do something useful with the response
    // The connection will be released automatically
    // as soon as the response content has been consumed
} catch (IOException ex) {
    // Abort connection upon an I/O error.
    conn.abortConnection();
    throw ex;
}

2.8.3. Simple connection manager

SingleClientConnManager是簡單的連接管理器,它每次僅維護一個連接。即使這個類是線程安全的,它也只能被一個執行線程使用。SingleClientConnManager努力重用具有相同路由的連續請求的連接。然而,如果持久連接的路由不匹配連接請求,它將會關閉已有的連接和打開指定的路由連接。如果連接已經被分配,拋出java.lang.IllegalStateException。

HttpClient缺省使用SingleClientConnManager。

2.8.4. Pooling connection manager

ThreadSafeClientConnManager是一個很複雜的實現,它管理一個客戶連接池,服務多個執行線程的連接請求,連接被每個路由放入池中。連接池中可用的已經存在持久連接的路由請求由池中租借的連接進行服務,而不是創建一個新的連接分支。

ThreadSafeClientConnManager在每個路由中維持一個最大的連接限制。缺省的應用對每個路由創建僅僅2個concurrent連接,總數不超過20個 連接。對於很多現實的應用,這些限制可能出現太多的限制,特別是如果他們使用HTTP作爲一個 傳輸協議進行服務。連接限制.然而,可以通過HTTP參數進行調整。

這個例子演示如何調整連接池參數:

HttpParams params = new BasicHttpParams();
// Increase max total connection to 200
ConnManagerParams.setMaxTotalConnections(params, 200);
// Increase default max connection per route to 20
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
        new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
        new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);

2.8.5. Connection manager shutdown

當HttpClient不再需要和超過作用域範圍時,連接管理器關閉所有的活動連接,確保系統資源被釋放掉。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
if (entity != null) {
    entity.consumeContent();
}
httpclient.getConnectionManager().shutdown();

2.9. Connection management parameters

自定義標準的HTTP連接管理實現參數:

  • ‘http.conn-manager.timeout’:定義以毫秒爲單位,從ClientConnectionManager獲得一個ManagedClientConnection實例超時時間。該參數要求是一個java.lang.Long類型的值,如果該參數沒有被設置,連接請求將沒有時間限制。

  • ‘http.conn-manager.max-per-route’:定義路由最大的連接數。這個限制被客戶連接管理器解釋。應用到單個的管理器實例。該參數要求是一個ConnPerRoute類型的值。

  • ‘http.conn-manager.max-total’:定義總連接數最大值。這個限制被客戶端連接管理器解釋和應用到單個的管理器實例。該參數要求是一個java.lang.Integer類型的值。

2.10 Multithreaded request execution

當配置了連接池管理器,例如ThreadSafeClientConnManager,HttpClient使用多線程同時執行多個請求。ThreadSafeClientConnManager類基於它的配置分配連接。如果所有的鏈接線路被租用,連接請求將被阻塞直到一個連接被釋放回到池中。在連接請求操作中設置’http.conn-manager.timeout’爲正值,確保連接管理器不會無限期的阻塞。如果連接請求在規定的時間段內沒有響應,將拋出ConnectionPoolTimeoutException異常。

HttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);
// URIs to perform GETs on
String[] urisToGet = {
    "http://www.domain1.com/",
    "http://www.domain2.com/",
    "http://www.domain3.com/",
    "http://www.domain4.com/"
};
// create a thread for each URI
GetThread[] threads = new GetThread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
    HttpGet httpget = new HttpGet(urisToGet[i]);
    threads[i] = new GetThread(httpClient, httpget);
}
// start the threads
for (int j = 0; j < threads.length; j++) {
    threads[j].start();
}
// join the threads
for (int j = 0; j < threads.length; j++) {
    threads[j].join();
}
static class GetThread extends Thread {
   
    private final HttpClient httpClient;
    private final HttpContext context;
    private final HttpGet httpget;
   
    public GetThread(HttpClient httpClient, HttpGet httpget) {
        this.httpClient = httpClient;
        this.context = new BasicHttpContext();
        this.httpget = httpget;
    }
   
    @Override
    public void run() {
        try {
            HttpResponse response = this.httpClient.execute(this.httpget, this.context);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                // do something useful with the entity
                // ...
                // ensure the connection gets released to the manager
                entity.consumeContent();
            }
        } catch (Exception ex) {
            this.httpget.abort();
        }
    }
  
}

2.11 Connection eviction policy

典型的阻塞I / O模型的主要缺點之一是當一個I/O操作被阻塞時,網絡套接字可以對I/O事件作出反應。當連接被釋放回管理器時,它可以保持活動,然而,它不能監控socket的狀態並對任何I/O事件作出反應。如果連接在服務端被關閉,那麼客戶端不會偵測到這個連接狀態的變化而及時的作出反應來關閉socket。

HttpClient嘗試以減輕該問題通過測試連接是否’陳舊’,這是不再有效,因爲它是在服務器端關閉,才能使用該連接執行一個HTTP請求。陳舊的連接檢查不是100%可靠,並增加了10到30毫秒的開銷每個請求執行。唯一可行的解決方案,不涉及一個每一個線程空閒連接插座模型是一個專門用來監視的線程以驅逐那些被認爲過期由於長期不活動的連接。在監視器線程可以定期調用ClientConnectionManager#closeExpiredConnections()方法關閉所有過期的連接和驅逐從池中關閉連接。它也可以隨意調用ClientConnectionManager#closeIdleConnections()方法關閉所有已閒置一段時間一段時間的連接。

public static class IdleConnectionMonitorThread extends Thread {
   
    private final ClientConnectionManager connMgr;
    private volatile boolean shutdown;
   
    public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }
    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // Close expired connections
                    connMgr.closeExpiredConnections();
                    // Optionally, close connections
                    // that have been idle longer than 30 sec
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // terminate
        }
}
public void shutdown() {
    shutdown = true;
    synchronized (this) {
        notifyAll();
    }
}   
}

2.12. Connection keep alive strategy

HTTP規範沒有指定一個持久連接可以而且應該保持活動多久。一些HTTP服務器使用非標準keep-alive頭傳達給客戶的時間在幾秒鐘的時間,他們打算保持連接在服務器端活動。HttpClient是這個信息變得可用。如果keep-alive頭中沒有反應,目前的HttpClient假定連接可以無限期地持續下去。然而,許多HTTP服務器內有配置取消掉在一定的時間內沒有活動的連接,以節省系統的持久連接資源,往往不通知客戶。如果默認的策略證明是過於樂觀,可能要提供一個自定義keep-alive的策略。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        // Honor 'keep-alive' header
        HeaderElementIterator it = new BasicHeaderElementIterator(
                response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(NumberFormatException ignore) {
                }
            }
        }
        HttpHost target = (HttpHost) context.getAttribute(
                ExecutionContext.HTTP_TARGET_HOST);
        if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
            // Keep alive for 5 seconds only
            return 5 * 1000;
        } else {
            // otherwise keep alive for 30 seconds
            return 30 * 1000;
        }
    }   
});

第三章 HTTPstate management(HTTP狀態管理)

最初的HTTP作爲一個無狀態的,面向協議請求/響應被設計,這個協議沒有提供有狀態的會話進行橫跨多個邏輯相關的請求/響應交換, 作爲HTTP協議變的很流行和被採用,越來越多的沒有打算應用它的系統開始使用它,例如在電子商務應用中作爲傳輸工具。因此對狀態管理的支持變成必要。

Netscape通信,在那個時期引領網絡客戶和服務軟件,在他們的產品中依據一個專有規格對HTTP狀態管理實施支持。後來,Netscape通過發佈一個規格草稿試着使機制標準化。這個努力有助於通過RFC標準軌道使正式規格被定義,然而,在許多應用中狀態管理大部分依靠Netscape草稿和官方規格不兼容。所有主要web瀏覽器的開發者被迫保留和那些應用的兼容性有助於破碎的標準。

3.1. HTTP cookies

Cookie是一個標誌或者是狀態信息的封裝, HTTP代理和目標服務進行交換來保持一個會話,Netscape工程師過去常常稱它爲一個魔術cookie。

HttpClient使用cookie接口來代表一個抽象的cookie標誌,用簡單的形式,一個HTTP cookie 僅僅是一個鍵值對。通常一個HTTP cookie也包括許多屬性,例如版本,有效的域名,cookie向源服務申請的明確的URL子集路徑和最大的有效期。

SetCookie接口表示爲了維護一個會話狀態源服務向HTTP代理髮送的一個set-cookie的響應頭。setCookie2接口繼承SetCookie,SetCookie2帶有的特定的方法。

ClientCookie接口繼承Cookie接口,增加了客戶端特有的功能,能準確的獲得原始cookie屬性,它們被原始服務指定,這對於生成cookie頭部很重要,因爲一些cookie規範需要cookie頭部包含某個屬性,只有他們set-Cookie和set-Cookie2頭部中被指定。

3.1.1. Cookie versions

Netscape草稿規範Cookies的兼容性沒有遵從官方的規範,被認爲是0版本,標準合格cookies被認爲是1版本,HttpClient可能依靠版本處理不同的cookies。

這有一個重新創建Netscape cookie的例子:

BasicClientCookie netscapeCookie = new BasicClientCookie("name", "value");
netscapeCookie.setVersion(0);
netscapeCookie.setDomain(".mycompany.com");
netscapeCookie.setPath("/");

這裏有一個重新創建標準cookie的例子,請注意標準合格的cookie必須保留原始服務發送的所有的屬性:

BasicClientCookie stdCookie = new BasicClientCookie("name", "value");
stdCookie.setVersion(1);
stdCookie.setDomain(".mycompany.com");
stdCookie.setPath("/");
stdCookie.setSecure(true);
// Set attributes EXACTLY as sent by the server
stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1");
stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");

這裏有一個重建Set-cookie2合格cookie的例子,請注意標準合格的cookie必須保留原始服務發送的所有屬性:

BasicClientCookie2 stdCookie = new BasicClientCookie2("name", "value")
stdCookie.setVersion(1);
stdCookie.setDomain(".mycompany.com");
stdCookie.setPorts(new int[] {80,8080});
stdCookie.setPath("/");
stdCookie.setSecure(true);
// Set attributes EXACTLY as sent by the server
stdCookie.setAttribute(ClientCookie.VERSION_ATTR, "1");
stdCookie.setAttribute(ClientCookie.DOMAIN_ATTR, ".mycompany.com");
stdCookie.setAttribute(ClientCookie.PORT_ATTR, "80,8080");

3.2. Cookie specifications

CookieSpec接口代表一個cookie管理規範,cookie管理規範強制要求:

  • 解析set-cooke和可選的set-cookie2頭部的規則

  • 解析cookie驗證規則

  • 對於主機,端口和原始路徑的cookie頭部規格。

HttpClient有幾個CookieSpec實現:

  • Netscape 草稿: 該規範遵從原Netscape通信發佈的原始草稿規範,它應該避免,除非對遺留代碼兼容性絕對需要。

  • RFC 2109:官方HTTP狀態管理規範的舊版本被RFC2965取代。

  • RFC 2965:官方HTTP狀態管理規範。

  • 瀏覽器兼容:這個實現努力去精密的模仿常見的WEB瀏覽器應用程序,比如IE和火狐。

  • 最好的匹配:‘Meta’ cookie明確說出獲得一個cookie規則依據Http響應發送的cookie格式,它基本上把所有的應用積聚到一個類中。

強烈推薦使用最好匹配策略,讓HttpClient在運行時依據執行上下文獲取適當的遵從級別。

3.3 HTTP cookie和狀態管理參數

這些參數被用於自定義HTTP狀態管理和cookie規範行爲:

  • ‘http.protocol.cookie-datepatterns’:定義有效的日期格式被用來解析非標準的expires屬性。只有在兼容非遵從的服務中需要,這個服務仍然用expires定義在Netscape草稿而不是標準的max-age屬性。該參數要求一個java.util.Collection類型的值。這個集合元素必須是java.lang.String類型,和語法java.text.SimpleDateFormat語法兼容。如果該參數沒有設置,缺省值的選擇由CookieSpec應用指定,請注意這個參數應用。

  • ‘http.protocol.single-cookie-header’:定義cookies是否被強迫進入一個單個的cookie請求頭部,否則,每個cookie被格式爲分開的cookie頭部。該參數要求一個java.lang.Boolean類型的值。如果該參數沒有設置,缺省的值的選擇由cookieSpec應用指定。請注意該參數應用在一個嚴格的cookie規範(RFC2109 和RFC2965),瀏覽器兼容和草稿規範將總是把所有cookies放在一個請求的頭部。

  • ‘http.protocol.cookie-policy’:定義一個cookie名字的規則被用在HTTP狀態管理。該參數要求一個java.lang.String的值。如果該參數沒有設置,有效的日期格式由CookieSpec實現指定。

3.4 Cookie specification registry(cookie規範註冊表)

HTTPClient用CookiespecRegistry類維護一個可用規範的註冊表,下列規範被每個缺省註冊:

  • compatibility:瀏覽器兼容。

  • netscape:Netscape草稿。

  • rfc2109:RFC2109(嚴格規範過時)。

  • rfc2965:RFC2965(標準遵從嚴格規則)。

  • best-match:最好的匹配meta-policy。

##3.5 Choosing cookie policy(選擇cookie策略)

Cookievv策略被設置在HTTP客戶端,如果需要的話可以重寫HTTP請求級別:

HttpClient httpclient = new DefaultHttpClient();
// force strict cookie policy per default
httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.RFC_2965);
HttpGet httpget = new HttpGet("http://www.broken-server.com/");
// Override the default policy for this request
httpget.getParams().setParameter(
        ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY); 

3.6 Custom cookie policy(自定義cookie策略)

   爲了實現一個自定義的cookie策略,必須創建一個CookieSpec接口自定義實現。創建一個CookieSpecFactory實現來創建和初始化自定義規則和註冊一個HTTPClient工廠實例。一旦自定義的規則被註冊,它可以用和標準cookie規範相同的方法被激活。
CookieSpecFactory csf = new CookieSpecFactory() {
    public CookieSpec newInstance(HttpParams params) {
        return new BrowserCompatSpec() {  
            @Override
            public void validate(Cookie cookie, CookieOrigin origin)
            throws MalformedCookieException {
                // Oh, I am easy
            } 
        };
    }
};
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.getCookieSpecs().register("easy", csf);
httpclient.getParams().setParameter(ClientPNames.COOKIE_POLICY, "easy");

3.7 Cookie persistence(cookie持久化)

HTTpClient能從事持久化cookie的物理代表工作,被儲存在CookieStore接口,缺省的CookieStore實現調用BasicClientCookie,是一個依靠java.util.ArrayList簡單的應用 ,當容器對象取得垃圾收集時,保存在一個BasiscClientCookie對象中的cookie被丟失,當必要時,用戶能提供更多複雜的應用。

DefaultHttpClient httpclient = new DefaultHttpClient();
// Create a local instance of cookie store
CookieStore cookieStore = new MyCookieStore();
// Populate cookies if needed
BasicClientCookie cookie = new BasicClientCookie("name", "value");
cookie.setVersion(0);
cookie.setDomain(".mycompany.com");
cookie.setPath("/");
cookieStore.addCookie(cookie);
// Set the store
httpclient.setCookieStore(cookieStore);

3.8 HTTP state management and execution context

在HTTP請求執行過程中,HTTPClient添加下列狀態管理相關對象來執行上下文:

  • ‘http.cookiespec-registry’:CookieSpecRegistry實例代表一個實際的cookie規範註冊表,這個屬性值被設置到本地上下文中優先於缺省。

  • ‘http.cookie-spec’:CookieSpec 實例代表實際的cookie規範

  • ‘http.cookie-origin’:CookieOrigin實例代表實際的源服務細節。

  • ‘http.cookie-store’:CookieStore實例代表實際的cookie存儲。屬性值被設置在本地上下文中優先於缺省。

本地HttpContext對象被用來自定義HTTP狀態管理上下文,在請求執行之前或者當請求被執行後,檢查他的狀態。

HttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
CookieOrigin cookieOrigin = (CookieOrigin) localContext.getAttribute(ClientContext.COOKIE_ORIGIN);
System.out.println("Cookie origin: " + cookieOrigin);
CookieSpec cookieSpec = (CookieSpec) localContext.getAttribute(ClientContext.COOKIE_SPEC);
System.out.println("Cookie spec used: " + cookieSpec);

3.9 Per user / thread state management

使用單一的本地執行上下文爲了實現每個用戶或每個線程狀態管理。cookie規範註冊表和cookie存儲被定義在本地上下文中,將優先於缺省設置在HTTP客戶級別。

HttpClient httpclient = new DefaultHttpClient();
// Create a local instance of cookie store
CookieStore cookieStore = new BasicCookieStore();
// Create local HTTP context
HttpContext localContext = new BasicHttpContext();
// Bind custom cookie store to the local context
localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
HttpGet httpget = new HttpGet("http://www.google.com/");
// Pass local context as a parameter
HttpResponse response = httpclient.execute(httpget, localContext);

第四章 HTTPauthentication(HTTP認證)

HttpClient完全支持HTTP標準規範中定義的認證模式。HttpClient的認證框架也可以進行擴展以支持NTLM和SPNEGO等非標準認證模式。

4.1. User credentials(用戶憑證)

任何用戶身份驗證過程需要一套可以用來確定用戶的身份憑據。在最簡單的形式用戶crednetials可以只是一個用戶名/密碼對。UsernamePasswordCredentials代表了一個安全主體組成的憑據和一個明文密碼。這是執行標準認證由HTTP規範中定義的標準計劃足夠了。

UsernamePasswordCredentials creds = new UsernamePasswordCredentials("user", "pwd");
System.out.println(creds.getUserPrincipal().getName());
System.out.println(creds.getPassword());

輸出:

user
pwd

NTCredentials是一個Microsoft Windows的具體實現,包括用戶名/密碼對,增加一套windows特有的屬性,例如用戶的域名,微軟Windows網絡相同的用戶可以屬於多個域使用不同的認證設置。

NTCredentials creds = new NTCredentials("user", "pwd", "workstation", "domain");
System.out.println(creds.getUserPrincipal().getName());
System.out.println(creds.getPassword());

輸出:

DOMAIN/user
pwd

4.2. Authentication schemes(認證模式)

該AuthScheme接口表示面向應答式的驗證模式。驗證模式要求支持以下功能:

  • 在分析和處理髮送目標服務器響應請求的受保護資源的挑戰。

  • xx

  • yy

注意驗證模式可能涉及的狀態應答式交換。HttpClient有幾個AuthScheme實現:

  • Basic:基本身份驗證模式在RFC2617中定義。這種身份驗證模式是不安全的,因爲憑據以明文形式傳輸。儘管它不安全,如果與TLS / SSL加密技術結合使用,基本驗證模式是十分滿足需求的。

  • Digest:摘要式身份驗證模式在RFC2617中定義。摘要式身份驗證方案是大大超過基本安全,對於那些不想應用程序通過TLS / SSL加密安全傳輸,Basic是一個不錯的選擇。

  • NTLM:NTLM是微軟Windows平臺開發和優化專有的認證模式。 NTLM被認爲比摘要更安全。這個模式是需要一個外部NTLM身份引擎能夠發揮作用。有關詳情請參閱NTLM_SUPPORT.txt文件包含在HttpClient發行版中。

4.3. HTTP authentication parameters(HTTP認證參數)

這些都是用來定製的HTTP身份驗證過程和個人身份驗證方案的行爲參數:

  • ‘http.protocol.handle-authentication’:定義是否應自動處理身份驗證。此參數要求java.lang.Boolean類型的值。如果該參數沒有設置,HttpClient會自動處理身份驗證。

  • ‘http.auth.credential-charset’:定義的字符集編碼時要使用的用戶憑據。此參數要求java.lang.String類型的值。如果此參數不設置,US-ASCII將被使用。

4.4 Authentication scheme registry

HttpClient使用AuthSchemeRegistry類來維護可用的身份驗證模式註冊。下面的模式被默認註冊:

  • Basic:基本身份驗證模式

  • Digest:摘要身份驗證模式

請注意NTLM模式不是默認註冊的。NTLM不能啓用,由於默認許可和法律原因,有關如何啓用NTLM支持,請參見本節詳細介紹。

4.5 Credentials provider

憑證提供者是值維護一套用戶憑證,並能夠產生特定的認證範圍的用戶憑證。認證範圍包括主機名稱,端口號,一個域名和身份驗證模式的名稱。在與供應商可以提供憑證外卡(任何主機,任何端口,任何領域,任何模式),而不是一個具體的屬性值登記證書。提供的憑證,然後將能夠找到一個特定的範圍最接近的匹配,如果匹配的直接無法找到。 HttpClient的可以與任何一個憑證提供程序接口實現CredentialsProvider物理表示。默認CredentialsProvider實現所謂BasicCredentialsProvider是一個簡單實現的java.util.HashMap。

CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(new AuthScope("somehost", AuthScope.ANY_PORT),
    new UsernamePasswordCredentials("u1", "p1"));
credsProvider.setCredentials(new AuthScope("somehost", 8080), new UsernamePasswordCredentials("u2", "p2"));
credsProvider.setCredentials(new AuthScope("otherhost", 8080, AuthScope.ANY_REALM, "ntlm"),
    new UsernamePasswordCredentials("u3", "p3"));
System.out.println(credsProvider.getCredentials(new AuthScope("somehost", 80, "realm", "basic")));
System.out.println(credsProvider.getCredentials(new AuthScope("somehost", 8080, "realm", "basic")));
System.out.println(credsProvider.getCredentials(new AuthScope("otherhost", 8080, "realm", "basic")));
System.out.println(credsProvider.getCredentials(new AuthScope("otherhost", 8080, null, "ntlm")));

輸出:

[principal: u1]
[principal: u2]
null
[principal: u3]

4.6 HTTP authentication and execution context

HttpClient依賴有關身份驗證過程的狀態詳細信息跟蹤的AuthState類。在HTTP請求執行期間創建兩個AuthState實例:一個用於目標主機的認證,另一個用於代理認證。如果目標服務器或代理服務器需要用戶身份驗證的實例將各自AuthScope與AuthScope,AuthScheme並在驗證過程中使用Crednetials填充該AuthState可以檢查,以找出什麼樣的認證要求,是否匹配AuthScheme執行結果和提供的憑據,是否能夠找到給定的認證範圍的用戶憑據。

在HTTP請求執行的HttpClient過程中添加下面的身份驗證來執行上下文相關的對象:

  • ‘http.authscheme -registry’:AuthSchemeRegistry實例代表實際的身份驗證方案的註冊表。由於在本地範圍內設置此屬性的默認值接管一個優先級。

  • ‘http.auth.credentials-provider’:CookieSpec實例代表提供實際憑據。由於在本地範圍內設置此屬性的默認值接管一個優先級。

  • ‘http.auth.target-scope’:HTTP認證AuthState實例代表實際的目標身份驗證狀態。由於在本地的情況設置此屬性的默認值接管一個優先級。

  • ‘http.auth.proxy-scope’:AuthState實例代表實際的代理身份驗證狀態。由於在本地範圍內設置此屬性的默認值接管一個優先級。

本地HttpContext對象被用來自定義HTTP認證上下文,在請求執行之前或者當請求被執行後,檢查他的狀態:

HttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
AuthState proxyAuthState = (AuthState) localContext.getAttribute(ClientContext.PROXY_AUTH_STATE);
System.out.println("Proxy auth scope: " + proxyAuthState.getAuthScope());
System.out.println("Proxy auth scheme: " + proxyAuthState.getAuthScheme());
System.out.println("Proxy auth credentials: " + proxyAuthState.getCredentials());
AuthState targetAuthState = (AuthState) localContext.getAttribute(ClientContext.TARGET_AUTH_STATE);
System.out.println("Target auth scope: " + targetAuthState.getAuthScope());
System.out.println("Target auth scheme: " + targetAuthState.getAuthScheme());
System.out.println("Target auth credentials: " + targetAuthState.getCredentials());

4.7 Preemptive authentication

HttpClient的不支持超前驗證開箱,因爲如果濫用或使用不當的超前身份驗證可以導致嚴重的安全問題,如明文發送用戶憑據的未經授權的第三方。因此,用戶將評估在他們的特定應用環境中先發制人的認證與安全風險的潛在利益,並需添加先發制人的身份驗證支持使用標準的HttpClient擴展機制,例如攔截協議。

這是一個簡單的協議攔截器,搶先介紹BasicScheme實例的執行上下文,舉例來說,如果認證沒有完成。請注意,此攔截器在標準的認證攔截器之前必須添加協議處理鏈。

HttpRequestInterceptor preemptiveAuth = new HttpRequestInterceptor() {   
    public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {       
        AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
        CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(
                ClientContext.CREDS_PROVIDER);
        HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);       
        // If not auth scheme has been initialized yet
        if (authState.getAuthScheme() == null) {
            AuthScope authScope = new AuthScope(targetHost.getHostName(),targetHost.getPort());
            // Obtain credentials matching the target host
Credentials creds = credsProvider.getCredentials(authScope);
            // If found, generate BasicScheme preemptively
            if (creds != null) {
                authState.setAuthScheme(new BasicScheme());
                authState.setCredentials(creds);
            }
        }
    }   
};
DefaultHttpClient httpclient = new DefaultHttpClient();
// Add as the very first interceptor in the protocol chain
httpclient.addRequestInterceptor(preemptiveAuth, 0);

4.8 NTLM Authentication

目前的HttpClient不提供對NTLM驗證模式的支持,可能永遠也不會。可能是法律原因而不是技術的原因。但是,啓用NTLM身份驗證可以通過使用外部的NTLM引擎,例如JCIFS [http:/jcifs.samba.org/]類庫由Samba [http:/www.samba.org/]作爲Windows的一部分的操作性成套的方案。有關詳情請參閱NTLM_SUPPORT.txt文件包含的HttpClient發佈版。

4.8.1. NTLM connection persistence

NTLM驗證模式是貴得多的計算開銷和性能比標準的基本和摘要模式的影響方面。這可能是爲什麼微軟選擇做驗證計劃狀態的主要原因之一。也就是說,一旦通過認證,用戶身份是,對於其整個生命跨度連接相關聯。連接狀態的NTLM持久性,使連接更復雜,因爲持續的理由很明顯,使用NTLM連接可能不會再由用戶使用不同的用戶身份。標準連接與HttpClient的發運經理完全有能力管理有狀態連接。然而,至關重要的是在同一會期內使用相同的執行上下文,以使他們認識到當前用戶的身份在邏輯上相關的要求。否則,最終的HttpClient將創建一個新的HTTP對NTLM身份爲每個HTTP請求連接保護的資源。有關HTTP連接狀態的詳細討論請參閱本節。

由於NTLM身份連接狀態,一般建議使用NTLM身份驗證觸發一個相對廉價的方法,例如GET或HEAD和重新使用相同的連接來執行更昂貴的方法,特別是那些要求附上一個實體,如POST或PUT 。

DefaultHttpClient httpclient = new DefaultHttpClient();
NTCredentials creds = new NTCredentials("user", "pwd", "myworkstation", "microsoft.com");
httpclient.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);
HttpHost target = new HttpHost("www.microsoft.com", 80, "http");
// Make sure the same context is used to execute logically related requests
HttpContext localContext = new BasicHttpContext();
// Execute a cheap method first. This will trigger NTLM authentication
HttpGet httpget = new HttpGet("/ntlm-protected/info");
HttpResponse response1 = httpclient.execute(target, httpget, localContext);
HttpEntity entity1 = response1.getEntity();
if (entity1 != null) {
entity1.consumeContent();
}
// Execute an expensive method next reusing the same context (and connection)
HttpPost httppost = new HttpPost("/ntlm-protected/form");
httppost.setEntity(new StringEntity("lots and lots of data"));
HttpResponse response2 = httpclient.execute(target, httppost, localContext);
HttpEntity entity2 = response2.getEntity();
if (entity2 != null) {
    entity2.consumeContent();
}

第五章 HTTP client service(HTTP客戶服務)

5.1 HTTP FACADE

HttpClient的接口代表了最重要的HTTP請求執行合同。它並沒有強加限制或執行請求的過程特殊的細節和交付給連接管理,狀態管理,認證和重新處理了個別實現的細節。這應該更容易裝飾,如響應內容緩存等附加功能的接口。

DefaultHttpClient是HttpClient的接口的默認實現。作爲一個特殊用途的處理程序或接口實現策略的一個具體方面的HTTP協議處理,如處理或重定向或驗證作出有關決定,並保持連接持續時間或着負責人數外觀這個類的行爲。這使用戶有選擇性地取代默認與自定義這些方面的執行情況,具體的應用。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
    @Override
    public long getKeepAliveDuration(
            HttpResponse response,
            HttpContext context) {
        long keepAlive = super.getKeepAliveDuration(response, context);
        if (keepAlive == -1) {
            // Keep connections alive 5 seconds if a keep-alive value
            // has not be explicitly set by the server
            keepAlive = 5000;
        }
        return keepAlive;
    }
   
});

efaultHttpClient還維護攔截器的協議列表處理傳入請求和傳出響應,並提供管理那些攔截器的方法。新的協議攔截器可以引進到協議處理器鏈或從中刪除如果需要的話。內部協議攔截器存儲在一個簡單的java.util.ArrayList。它們按照已添加到列表中順序被執行。

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.removeRequestInterceptorByClass(RequestUserAgent.class);
httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
    public void process(
            HttpRequest request, HttpContext context)
            throws HttpException, IOException {
        request.setHeader(HTTP.USER_AGENT, "My-own-client");
    }
   
});

DefaultHttpClient是線程安全的。建議類的同一個實例是複用多個請求。當一個DefaultHttpClient實例不再需要和超出作用域,與它相關的連接管理器必須關閉調用ClientConnectionManager#shutdown()方法。

HttpClient httpclient = new DefaultHttpClient();
// Do something useful
httpclient.getConnectionManager().shutdown();

5.2 HttpClient parameters

這些參數用於HttpClient默認執行自定義行爲:

  • ‘http.protocol.handle -redirects’:定義是否應自動處理重定向。此參數期望是java.lang.Boolean類型的值。如果此參數沒有定義HttpClient將不會自動處理重定向。

  • ‘http.protocol.reject -relative-redirect’:定義是否相對重定向應予以拒絕。 HTTP規範規定位置值是一個絕對的URI。此參數期望是java.lang.Boolean類型的值。如果該參數沒有設置相對重定向將被允許。

  • ‘http.protocol.max -redirects’:定義的最大數量應遵循的重定向。關於重定向的數量限制是爲了防止破壞服務器端腳本造成無限循環。預計此參數類型java.lang.Integer價值。如果不設置此參數不超過100重定向將被允許。

  • ‘http.protocol.allow-circular-redirects’:定義是否(循環重定向重定向到相同的位置)應該被允許。的HTTP規範不夠明確,是否允許重定向循環,因此他們可以選擇啓用。預計此參數類型java.lang.Boolean的價值。如果該參數沒有設置循環重定向將被禁止。

  • ‘http.connection - manager.factory-class-name’:定義了默認的ClientConnectionManager實現類的名稱。此參數預計java.lang.String類型的值。如果不設置此參數爲默認將使用SingleClientConnManager。

  • ‘http.virtual-host’:定義的虛擬主機名,而不是在物理主機使用主機頭名。預計此參數類型HttpHost價值。如果未設置此參數的名稱或IP地址將目標主機使用。

  • ‘http.default -headers’:定義了請求爲每發送的每個請求的默認標題。預計此參數的類型包含頭對象爲 java.util.Collection價值。

  • ‘http.default-host’:定義的默認主機。默認值將用於如果目標主機不明確的請求的URI(相對URI)中指定。預計此參數類型HttpHost價值。

5.3 Automcatic redirect handling

HttpClient處理所有類型的重定向,除了那些顯式地需要用戶干預的HTTP規範所禁止的。見其他(狀態代碼303)重定向的GET轉換按照HTTP規範所要求的POST和PUT請求。

5.4 HTTP client and execution context

該DefaultHttpClient視爲應該是從來沒有改變的請求執行過程中的不可變對象的HTTP請求。相反,它創造了一個原始請求對象,其可以更新取決於執行上下文屬性私有可變的副本。因此,最終的請求,例如目標主機和請求URI可以通過檢查本地HTTP上下文內容確定後,要求被執行。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpHost target = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
HttpUriRequest req = (HttpUriRequest) localContext.getAttribute(
        ExecutionContext.HTTP_REQUEST);
System.out.println("Target host: " + target);
System.out.println("Final request URI: " + req.getURI());
System.out.println("Final request method: " + req.getMethod());

第六章 Advanced topics(高級主題)

6.1. Custom client connections

在某些情況下,定製HTTP消息通過線路被傳輸的方式是必要的,而不是爲了非標準,非遵守的行爲

而使用Http參數。對於web爬蟲來說,爲了搶救消息的內容,強迫HttpClient接受畸形的頭反應是有必要的。

通常插件在習慣的消息解析或者習慣的連接應用的處理涉及到幾個步驟。

l 提供一個自定義的LineParser/LineFormatter接口實現,實現消息解析/格式化必需的邏輯。

class MyLineParser extends BasicLineParser {
    @Override
    public Header parseHeader(
            final CharArrayBuffer buffer) throws ParseException {
        try {
            return super.parseHeader(buffer);
        } catch (ParseException ex) {
            // Suppress ParseException exception
            return new BasicHeader("invalid", buffer.toString());
        }
    }
}

| 提供一個自定義的OperatedClientConnection實現,替換缺省的請求和響應解析,請求和響應格式化,如果有必要的話,實現不同的消息讀寫代碼。

class MyClientConnection extends DefaultClientConnection {
    @Override
    protected HttpMessageParser createResponseParser(
            final SessionInputBuffer buffer,
            final HttpResponseFactory responseFactory,
            final HttpParams params) {
        return new DefaultResponseParser(
                buffer,
                new MyLineParser(),
                responseFactory,
                params);
    }   
}

l 提供一個自定義ClientConnectionOperator接口實現爲了創建一個新連接,如果有必要的話,實現socket初始化代碼。

class MyClientConnectionOperator extends DefaultClientConnectionOperator {
public MyClientConnectionOperator(final SchemeRegistry sr) {
        super(sr);
    }
    @Override
    public OperatedClientConnection createConnection() {
        return new MyClientConnection();
    }   
}

l 提供一個自定義ClientConnectionManager接口實現爲了創建新類的連接操作。

class MyClientConnManager extends SingleClientConnManager {   
    public MyClientConnManager(
            final HttpParams params,
            final SchemeRegistry sr) {
        super(params, sr);
    }
    @Override
    protected ClientConnectionOperator createConnectionOperator(
            final SchemeRegistry sr) {
        return new MyClientConnectionOperator(sr);
    }   
}

6.2. Stateful HTTP connections

HTTP詳細說明如果會話狀態信息總是以HTTP cookie的形式嵌入HTTP 消息中,因此HTTP連接總是無狀態的,這個假象在真實的生活中不總是有效。這種情況下,HTTP連接被一個特別的用戶創建或者在一個特別的安全環境下被創建,因此不能被其他用戶分享,只能被同一個用戶重用。有狀態的HTTP連接的例子是NTLM認證連接和SSL連接帶有客戶憑證認證。

6.2.1. User token handler

如果特定的執行上下文具有用戶或者沒有,HTTPClient依靠UserTokenHandler接口來確定。處理返回的標記對象唯一標示當前用戶,是否上下文具有用戶或者爲空。是否上下文不包含任何資源或者當前用戶具體的細節。用戶令牌被用來確定用戶具體的資源不能被分享或者被其他用戶重用。

UserTokenHandler接口缺省實現使用一個Principal類的實例來代表HTTP連接的一個狀態對象,如果它從特定的執行的上下文中獲得,DefaultUserTokenHandler將在認證模式基礎上使用用戶連接規則。例如NTLM或者SSL會話打開客戶認證,如果兩者都不可用,空令牌將會返回。

如果默認是實現不能滿足需求,用戶提供一個自定義的實現:

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setUserTokenHandler(new UserTokenHandler() {
    public Object getUserToken(HttpContext context) {
        return context.getAttribute("my-token");
    }   
});

6.2.2. User token and execution context

在HTTP請求執行過程中,HTTPClient添加以下與用戶識別相關對象來執行上下文。

  • ‘http.user-token’:對象實例代表實際用戶標識。通常要求是Principle接口的一個實例。

在請求被執行以後,通過檢查本地HTTP上下文的內容。你可以查明是否被用來執行請求的連接是有狀態的。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://localhost:8080/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpEntity entity = response.getEntity();
if (entity != null) {
    entity.consumeContent();
}
Object userToken = localContext.getAttribute(ClientContext.USER_TOKEN);
System.out.println(userToken);

6.2.2.1. Persistent stateful connections

請注意,攜帶一個狀態對象的持續連接不能被用,只有當請求被執行,同一個狀態對象綁定到執行上下文,連接才能用。所以確保相同的上下文被相同的用戶用來執行連續的HTTP請求,或者用戶令牌被綁定到上下文優先於請求被執行很重要。

DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext1 = new BasicHttpContext();
HttpGet httpget1 = new HttpGet("http://localhost:8080/");
HttpResponse response1 = httpclient.execute(httpget1, localContext1);
HttpEntity entity1 = response1.getEntity();
if (entity1 != null) {
    entity1.consumeContent();
}
Principal principal = (Principal) localContext1.getAttribute(ClientContext.USER_TOKEN);
HttpContext localContext2 = new BasicHttpContext();
localContext2.setAttribute(ClientContext.USER_TOKEN, principal);
HttpGet httpget2 = new HttpGet("http://localhost:8080/");
HttpResponse response2 = httpclient.execute(httpget2, localContext2);
HttpEntity entity2 = response2.getEntity();
if (entity2 != null) {
    entity2.consumeContent();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章