HttpClient 教程 (一)

前言

超文本傳輸協議(HTTP)也許是當今互聯網上使用的最重要的協議了。Web服務,有網絡功能的設備和網絡計算的發展,都持續擴展了HTTP協議的角色,超越了用戶使用的Web瀏覽器范疇,同時,也增加了需要HTTP協議支持的應用程序的數量。

盡管java.net包提供了基本通過HTTP訪問資源的功能,但它沒有提供全面的靈活性和其它很多應用程序需要的功能。HttpClient就是尋求彌補這項空白的組件,通過提供一個有效的,保持更新的,功能豐富的軟件包來實現客戶端最新的HTTP標準和建議。

為擴展而設計,同時為基本的HTTP協議提供強大的支持,HttpClient組件也許就是構建HTTP客戶端應用程序,比如web瀏覽器,web服務端,利用或擴展HTTP協議進行分布式通信的系統的開發人員的關注點。

1. HttpClient的范圍

基于HttpCore[http://hc.apache.org/httpcomponents-core/index.html]的客戶端HTTP運輸實現庫

基于經典(阻塞)I/O

內容無關

2. 什么是HttpClient不能做的

HttpClient不是一個瀏覽器。它是一個客戶端的HTTP通信實現庫。HttpClient的目標是發送和接收HTTP報文。HttpClient不會去緩存內容,執行嵌入在HTML頁面中的javascript代碼,猜測內容類型,重新格式化請求/重定向URI,或者其它和HTTP運輸無關的功能。

第一章 基礎

1.1 執行請求

HttpClient最重要的功能是執行HTTP方法。一個HTTP方法的執行包含一個或多個HTTP請求/HTTP響應交換,通常由HttpClient的內部來處理。而期望用戶提供一個要執行的請求對象,而HttpClient期望傳輸請求到目標服務器并返回對應的響應對象,或者當執行不成功時拋出異常。

很自然地,HttpClient API的主要切入點就是定義描述上述規約的HttpClient接口。

這里有一個很簡單的請求執行過程的示例:

HttpClient httpclient = new DefaultHttpClient();

HttpGet httpget = new HttpGet("http://localhost/");

HttpResponse response = httpclient.execute(httpget);

HttpEntity entity = response.getEntity();

if (entity != null) {

InputStream instream = entity.getContent();

int l;

byte[] tmp = new byte[2048];

while ((l = instream.read(tmp)) != -1) {

}

}

1.1.1 HTTP請求

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

HttpClient支持所有定義在HTTP/1.1版本中的HTTP方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。對于每個方法類型都有一個特殊的類:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和HttpOptions。

請求的URI是統一資源定位符,它標識了應用于哪個請求之上的資源。HTTP請求URI包含一個協議模式,主機名稱,可選的端口,資源路徑,可選的查詢和可選的片段。

HttpGet httpget = new HttpGet(

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

HttpClient提供很多工具方法來簡化創建和修改執行URI。

URI也可以編程來拼裝:

URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",

"q=httpclient&btnG=Google+Search&aq=f&oq=", null);

HttpGet httpget = new HttpGet(uri);

System.out.println(httpget.getURI());

輸出內容為:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

查詢字符串也可以從獨立的參數中來生成:

List<NameValuePair> qparams = new ArrayList<NameValuePair>();

qparams.add(new BasicNameValuePair("q", "httpclient"));

qparams.add(new BasicNameValuePair("btnG", "Google Search"));

qparams.add(new BasicNameValuePair("aq", "f"));

qparams.add(new BasicNameValuePair("oq", null));

URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",

URLEncodedUtils.format(qparams, "UTF-8"), null);

HttpGet httpget = new HttpGet(uri);

System.out.println(httpget.getURI());

輸出內容為:

http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

1.1.2 HTTP響應

HTTP響應是由服務器在接收和解釋請求報文之后返回發送給客戶端的報文。響應報文的第一行包含了協議版本,之后是數字狀態碼和相關聯的文本段。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,

HttpStatus.SC_OK, "OK");

System.out.println(response.getProtocolVersion());

System.out.println(response.getStatusLine().getStatusCode());

System.out.println(response.getStatusLine().getReasonPhrase());

System.out.println(response.getStatusLine().toString());

輸出內容為:

HTTP/1.1

200

OK

HTTP/1.1 200 OK

1.1.3 處理報文頭部

一個HTTP報文可以包含很多描述如內容長度,內容類型等信息屬性的頭部信息。

HttpClient提供獲取,添加,移除和枚舉頭部信息的方法。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,

HttpStatus.SC_OK, "OK");

response.addHeader("Set-Cookie",

"c1=a; path=/; domain=localhost");

response.addHeader("Set-Cookie",

"c2=b; path=\"/\", c3=c; domain=\"localhost\"");

Header h1 = response.getFirstHeader("Set-Cookie");

System.out.println(h1);

Header h2 = response.getLastHeader("Set-Cookie");

System.out.println(h2);

Header[] hs = response.getHeaders("Set-Cookie");

System.out.println(hs.length);

輸出內容為:

Set-Cookie: c1=a; path=/; domain=localhost

Set-Cookie: c2=b; path="/", c3=c; domain="localhost"

獲得給定類型的所有頭部信息最有效的方式是使用HeaderIterator接口。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,

HttpStatus.SC_OK, "OK");

response.addHeader("Set-Cookie",

"c1=a; path=/; domain=localhost");

response.addHeader("Set-Cookie",

"c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderIterator it = response.headerIterator("Set-Cookie");

while (it.hasNext()) {

System.out.println(it.next());

}

輸出內容為:

Set-Cookie: c1=a; path=/; domain=localhost

Set-Cookie: c2=b; path="/", c3=c; domain="localhost"

它也提供解析HTTP報文到獨立頭部信息元素的方法方法。

HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,

HttpStatus.SC_OK, "OK");

response.addHeader("Set-Cookie",

"c1=a; path=/; domain=localhost");

response.addHeader("Set-Cookie",

"c2=b; path=\"/\", c3=c; domain=\"localhost\"");

HeaderElementIterator it = new BasicHeaderElementIterator(

response.headerIterator("Set-Cookie"));

while (it.hasNext()) {

HeaderElement elem = it.nextElement();

System.out.println(elem.getName() + " = " + elem.getValue());

NameValuePair[] params = elem.getParameters();

for (int i = 0; i < params.length; i++) {

System.out.println(" " + params[i]);

}

}

輸出內容為:

c1 = a

path=/

domain=localhost

c2 = b

path=/

c3 = c

domain=localhost

1.1.4 HTTP實體

HTTP報文可以攜帶和請求或響應相關的內容實體。實體可以在一些請求和響應中找到,因為它們也是可選的。使用了實體的請求被稱為封閉實體請求。HTTP規范定義了兩種封閉實體的方法:POST和PUT。響應通常期望包含一個內容實體。這個規則也有特例,比如HEAD方法的響應和204 No Content,304 Not Modified和205 Reset Content響應。

HttpClient根據其內容出自何處區分三種類型的實體:

streamed流式:內容從流中獲得,或者在運行中產生。特別是這種分類包含從HTTP響應中獲取的實體。流式實體是不可重復生成的。

self-contained自我包含式:內容在內存中或通過獨立的連接或其它實體中獲得。自我包含式的實體是可以重復生成的。這種類型的實體會經常用于封閉HTTP請求的實體。

wrapping包裝式:內容從另外一個實體中獲得。

當從一個HTTP響應中獲取流式內容時,這個區別對于連接管理很重要。對于由應用程序創建而且只使用HttpClient發送的請求實體,流式和自我包含式的不同就不那么重要了。這種情況下,建議考慮如流式這種不能重復的實體,和可以重復的自我包含式實體。

1.1.4.1 重復實體

實體可以重復,意味著它的內容可以被多次讀取。這就僅僅是自我包含式的實體了(像ByteArrayEntity或StringEntity)。

1.1.4.2 使用HTTP實體

因為一個實體既可以代表二進制內容又可以代表字符內容,它也支持字符編碼(支持后者也就是字符內容)。

實體是當使用封閉內容執行請求,或當請求已經成功執行,或當響應體結果發功到客戶端時創建的。

要從實體中讀取內容,可以通過HttpEntity#getContent()方法從輸入流中獲取,這會返回一個java.io.InputStream對象,或者提供一個輸出流到HttpEntity#writeTo(OutputStream)方法中,這會一次返回所有寫入到給定流中的內容。

當實體通過一個收到的報文獲取時,HttpEntity#getContentType()方法和HttpEntity#getContentLength()方法可以用來讀取通用的元數據,如Content-Type和Content-Length頭部信息(如果它們是可用的)。因為頭部信息Content-Type可以包含對文本MIME類型的字符編碼,比如text/plain或text/html,HttpEntity#getContentEncoding()方法用來讀取這個信息。如果頭部信息不可用,那么就返回長度-1,而對于內容類型返回NULL。如果頭部信息Content-Type是可用的,那么就會返回一個Header對象。

當為一個傳出報文創建實體時,這個元數據不得不通過實體創建器來提供。

StringEntity myEntity = new StringEntity("important message",

"UTF-8");

System.out.println(myEntity.getContentType());

System.out.println(myEntity.getContentLength());

System.out.println(EntityUtils.getContentCharSet(myEntity));

System.out.println(EntityUtils.toString(myEntity));

System.out.println(EntityUtils.toByteArray(myEntity).length);

輸出內容為

Content-Type: text/plain; charset=UTF-8

17

UTF-8

important message

17

1.1.5 確保低級別資源釋放

當完成一個響應實體,那么保證所有實體內容已經被完全消耗是很重要的,所以連接可以安全的放回到連接池中,而且可以通過連接管理器對后續的請求重用連接。處理這個操作的最方便的方法是調用HttpEntity#consumeContent()方法來消耗流中的任意可用內容。HttpClient探測到內容流尾部已經到達后,會立即會自動釋放低層連接,并放回到連接管理器。HttpEntity#consumeContent()方法調用多次也是安全的。

也可能會有特殊情況,當整個響應內容的一小部分需要獲取,消耗剩余內容而損失性能,還有重用連接的代價太高,則可以僅僅通過調用HttpUriRequest#abort()方法來中止請求。

HttpGet httpget = new HttpGet("http://localhost/");

HttpResponse response = httpclient.execute(httpget);

HttpEntity entity = response.getEntity();

if (entity != null) {

InputStream instream = entity.getContent();

int byteOne = instream.read();

int byteTwo = instream.read();

// Do not need the rest

httpget.abort();

}

連接不會被重用,但是由它持有的所有級別的資源將會被正確釋放。

1.1.6 消耗實體內容

推薦消耗實體內容的方式是使用它的HttpEntity#getContent()或HttpEntity#writeTo(OutputStream)方法。HttpClient也自帶EntityUtils類,這會暴露出一些靜態方法,這些方法可以更加容易地從實體中讀取內容或信息。代替直接讀取java.io.InputStream,也可以使用這個類中的方法以字符串/字節數組的形式獲取整個內容體。然而,EntityUtils的使用是強烈不鼓勵的,除非響應實體源自可靠的HTTP服務器和已知的長度限制。

HttpGet httpget = new HttpGet("http://localhost/");

HttpResponse response = httpclient.execute(httpget);

HttpEntity entity = response.getEntity();

if (entity != null) {

long len = entity.getContentLength();

if (len != -1 && len < 2048) {

System.out.println(EntityUtils.toString(entity));

} else {

// Stream content out

}

}

在一些情況下可能會不止一次的讀取實體。此時實體內容必須以某種方式在內存或磁盤上被緩沖起來。最簡單的方法是通過使用BufferedHttpEntity類來包裝源實體完成。這會引起源實體內容被讀取到內存的緩沖區中。在其它所有方式中,實體包裝器將會得到源實體。

HttpGet httpget = new HttpGet("http://localhost/");

HttpResponse response = httpclient.execute(httpget);

HttpEntity entity = response.getEntity();

if (entity != null) {

entity = new BufferedHttpEntity(entity);

}

1.1.7 生成實體內容

HttpClient提供一些類,它們可以用于生成通過HTTP連接獲得內容的有效輸出流。為了封閉實體從HTTP請求中獲得的輸出內容,那些類的實例可以和封閉如POST和PUT請求的實體相關聯。HttpClient為很多公用的數據容器,比如字符串,字節數組,輸入流和文件提供了一些類:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。

File file = new File("somefile.txt");

FileEntity entity = new FileEntity(file, "text/plain; charset=\"UTF-8\"");

HttpPost httppost = new HttpPost("http://localhost/action.do");

httppost.setEntity(entity);

請注意InputStreamEntity是不可重復的,因為它僅僅能從低層數據流中讀取一次內容。通常來說,我們推薦實現一個定制的HttpEntity類,這是自我包含式的,用來代替使用通用的InputStreamEntity。FileEntity也是一個很好的起點。

1.1.7.1 動態內容實體

通常來說,HTTP實體需要基于特定的執行上下文來動態地生成。通過使用EntityTemplate實體類和ContentProducer接口,HttpClient提供了動態實體的支持。內容生成器是按照需求生成它們內容的對象,將它們寫入到一個輸出流中。它們是每次被請求時來生成內容。所以用EntityTemplate創建的實體通常是自我包含而且可以重復的。

ContentProducer cp = new ContentProducer() {

public void writeTo(OutputStream outstream) throws IOException {

Writer writer = new OutputStreamWriter(outstream, "UTF-8");

writer.write("<response>");

writer.write(" <content>");

writer.write(" important stuff");

writer.write(" </content>");

writer.write("</response>");

writer.flush();

}

};

HttpEntity entity = new EntityTemplate(cp);

HttpPost httppost = new HttpPost("http://localhost/handler.do");

httppost.setEntity(entity);

1.1.7.2 HTML表單

許多應用程序需要頻繁模擬提交一個HTML表單的過程,比如,為了來記錄一個Web應用程序或提交輸出數據。HttpClient提供了特殊的實體類UrlEncodedFormEntity來這個滿足過程。

List<NameValuePair> formparams = new ArrayList<NameValuePair>();

formparams.add(new BasicNameValuePair("param1", "value1"));

formparams.add(new BasicNameValuePair("param2", "value2"));

UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");

HttpPost httppost = new HttpPost("http://localhost/handler.do");

httppost.setEntity(entity);

UrlEncodedFormEntity實例將會使用URL編碼來編碼參數,生成如下的內容:

param1=value1&param2=value2

1.1.7.3 內容分塊

通常,我們推薦讓HttpClient選擇基于被傳遞的HTTP報文屬性的最適合的編碼轉換。這是可能的,但是,設置HttpEntity#setChunked()方法為true是通知HttpClient分塊編碼的首選。請注意HttpClient將會使用標識作為提示。當使用的HTTP協議版本,如HTTP/1.0版本,不支持分塊編碼時,這個值會被忽略。

StringEntity entity = new StringEntity("important message",

"text/plain; charset=\"UTF-8\"");

entity.setChunked(true);

HttpPost httppost = new HttpPost("http://localhost/acrtion.do");

httppost.setEntity(entity);

1.1.8 響應控制器

控制響應的最簡便和最方便的方式是使用ResponseHandler接口。這個放完完全減輕了用戶關于連接管理的擔心。當使用ResponseHandler時,HttpClient將會自動關注并保證釋放連接到連接管理器中去,而不管請求執行是否成功或引發了異常。

HttpClient httpclient = new DefaultHttpClient();

HttpGet httpget = new HttpGet("http://localhost/");

ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {

public byte[] handleResponse(

HttpResponse response) throws ClientProtocolException, IOException {

HttpEntity entity = response.getEntity();

if (entity != null) {

return EntityUtils.toByteArray(entity);

} else {

return null;

}

}

};

byte[] response = httpclient.execute(httpget, handler);

1.2 HTTP執行的環境

最初,HTTP是被設計成無狀態的,面向請求-響應的協議。然而,真實的應用程序經常需要通過一些邏輯相關的請求-響應交換來持久狀態信息。為了開啟應用程序來維持一個過程狀態,HttpClient允許HTTP請求在一個特定的執行環境中來執行,簡稱為HTTP上下文。如果相同的環境在連續請求之間重用,那么多種邏輯相關的請求可以參與到一個邏輯會話中。HTTP上下文功能和java.util.Map<String,Object>很相似。它僅僅是任意命名參數值的集合。應用程序可以在請求之前或在檢查上下文執行完成之后來填充上下文屬性。

在HTTP請求執行的這一過程中,HttpClient添加了下列屬性到執行上下文中:

'http.connection':HttpConnection實例代表了連接到目標服務器的真實連接。

'http.target_host':HttpHost實例代表了連接目標。

'http.proxy_host':如果使用了,HttpHost實例代表了代理連接。

'http.request':HttpRequest實例代表了真實的HTTP請求。

'http.response':HttpResponse實例代表了真實的HTTP響應。

'http.request_sent':java.lang.Boolean對象代表了暗示真實請求是否被完全傳送到目標連接的標識。

比如,為了決定最終的重定向目標,在請求執行之后,可以檢查http.target_host屬性的值:

DefaultHttpClient httpclient = new DefaultHttpClient();

HttpContext localContext = new BasicHttpContext();

HttpGet httpget = new HttpGet("http://www.google.com/");

HttpResponse response = httpclient.execute(httpget, localContext);

HttpHost target = (HttpHost) localContext.getAttribute(

ExecutionContext.HTTP_TARGET_HOST);

System.out.println("Final target: " + target);

HttpEntity entity = response.getEntity();

if (entity != null) {

entity.consumeContent();

}

輸出內容為:

Final target: http://www.google.ch

1.3 異常處理

HttpClient能夠拋出兩種類型的異常:在I/O失敗時,如套接字連接超時或被重置的java.io.IOException異常,還有標志HTTP請求失敗的信號,如違反HTTP協議的HttpException異常。通常I/O錯誤被認為是非致命的和可以恢復的,而HTTP協議錯誤則被認為是致命的而且是不能自動恢復的。

1.3.1 HTTP運輸安全

要理解HTTP協議并不是對所有類型的應用程序都適合的,這一點很重要。HTTP是一個簡單的面向請求/響應的協議,最初被設計用來支持取回靜態或動態生成的內容。它從未向支持事務性操作方向發展。比如,如果成功收到和處理請求,HTTP服務器將會考慮它的其中一部分是否完成,生成一個響應并發送一個狀態碼到客戶端。如果客戶端因為讀取超時,請求取消或系統崩潰導致接收響應實體失敗時,服務器不會試圖回滾事務。如果客戶端決定重新這個請求,那么服務器將不可避免地不止一次執行這個相同的事務。在一些情況下,這會導致應用數據損壞或者不一致的應用程序狀態。

盡管HTTP從來都沒有被設計來支持事務性處理,但它也能被用作于一個傳輸協議對關鍵的任務應用提供被滿足的確定狀態。要保證HTTP傳輸層的安全,系統必須保證HTTP方法在應用層的冪等性。

1.3.2 冪等的方法

HTTP/1.1 明確地定義了冪等的方法,描述如下

[方法也可以有“冪等”屬性在那些(除了錯誤或過期問題)N的副作用>0的相同請求和獨立的請求是相同的]

換句話說,應用程序應該保證準備著來處理多個相同方法執行的實現。這是可以達到的,比如,通過提供一個獨立的事務ID和其它避免執行相同邏輯操作的方法。

請注意這個問題對于HttpClient是不具體的。基于應用的瀏覽器特別受和非冪等的HTTP方法相關的相同問題的限制。

HttpClient假設沒有實體包含方法,比如GET和HEAD是冪等的,而實體包含方法,比如POST和PUT則不是。

1.3.3 異常自動恢復

默認情況下,HttpClient會試圖自動從I/O異常中恢復。默認的自動恢復機制是受很少一部分已知的異常是安全的這個限制。

HttpClient不會從任意邏輯或HTTP協議錯誤(那些是從HttpException類中派生出的)中恢復的。

HttpClient將會自動重新執行那么假設是冪等的方法。

HttpClient將會自動重新執行那些由于運輸異常失敗,而HTTP請求仍然被傳送到目標服務器(也就是請求沒有完全被送到服務器)失敗的方法。

HttpClient將會自動重新執行那些已經完全被送到服務器,但是服務器使用HTTP狀態碼(服務器僅僅丟掉連接而不會發回任何東西)響應時失敗的方法。在這種情況下,假設請求沒有被服務器處理,而應用程序的狀態也沒有改變。如果這個假設可能對于你應用程序的目標Web服務器來說不正確,那么就強烈建議提供一個自定義的異常處理器。

1.3.4 請求重試處理

為了開啟自定義異常恢復機制,應該提供一個HttpRequestRetryHandler接口的實現。

DefaultHttpClient httpclient = new DefaultHttpClient();

HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {

public boolean retryRequest(IOException exception,

int executionCount,HttpContext context) {

if (executionCount >= 5) {

// 如果超過最大重試次數,那么就不要繼續了

return false;

}

if (exception instanceof NoHttpResponseException) {

// 如果服務器丟掉了連接,那么就重試

return true;

}

if (exception instanceof SSLHandshakeException) {

// 不要重試SSL握手異常

return false;

}

HttpRequest request = (HttpRequest) context.getAttribute(

ExecutionContext.HTTP_REQUEST);

boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);

if (idempotent) {

// 如果請求被認為是冪等的,那么就重試

return true;

}

return false;

}

};

httpclient.setHttpRequestRetryHandler(myRetryHandler);

1.4 中止請求

在一些情況下,由于目標服務器的高負載或客戶端有很多活動的請求,那么HTTP請求執行會在預期的時間框內而失敗。這時,就可能不得不過早地中止請求,解除封鎖在I/O執行中的線程封鎖。被HttpClient執行的HTTP請求可以在執行的任意階段通過調用HttpUriRequest#abort()方法而中止。這個方法是線程安全的,而且可以從任意線程中調用。當一個HTTP請求被中止時,它的執行線程就封鎖在I/O操作中了,而且保證通過拋出InterruptedIOException異常來解鎖。

1.5 HTTP協議攔截器

HTTP協議攔截器是一個實現了特定HTPP協議方面的慣例。通常協議攔截器希望作用于一個特定頭部信息上,或者一族收到報文的相關頭部信息,或使用一個特定的頭部或一族相關的頭部信息填充發出的報文。協議攔截器也可以操縱包含在報文中的內容實體,透明的內容壓縮/解壓就是一個很好的示例。通常情況下這是由包裝器實體類使用了“裝飾者”模式來裝飾原始的實體完成的。一些協議攔截器可以從一個邏輯單元中來結合。

協議攔截器也可以通過共享信息來共同合作-比如處理狀態-通過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參數

HttpParams接口代表了定義組件運行時行為的一個不變的值的集合。很多情況下,HttpParams和HttpContext相似。二者之間的主要區別是它們在運行時使用的不同。這兩個接口表示了對象的集合,它們被視作為訪問對象值的鍵的Map,但是服務于不同的目的:

HttpParams旨在包含簡單對象:整型,浮點型,字符串,集合,還有運行時不變的對象。

HttpParams希望被用在“一次寫入-多處準備”模式下。HttpContext旨在包含很可能在HTTP報文處理這一過程中發生改變的復雜對象

HttpParams的目標是定義其它組件的行為。通常每一個復雜的組件都有它自己的HttpParams對象。HttpContext的目標是來表示一個HTTP處理的執行狀態。通常相同的執行上下文在很多合作的對象中共享。

1.6.1 參數層次

在HTTP請求執行過程中,HttpRequest對象的HttpParams是和用于執行請求的HttpClient實例的HttpParams聯系在一起的。這使得設置在HTTP請求級別的參數優先于設置在HTTP客戶端級別的HttpParams。推薦的做法是設置普通參數對所有的在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參數bean

HttpParams接口允許在處理組件的配置上很大的靈活性。很重要的是,新的參數可以被引入而不會影響老版本的二進制兼容性。然而,和常規的Java bean相比,HttpParams也有一個缺點:HttpParams不能使用DI框架來組合。為了緩解這個限制,HttpClient包含了一些bean類,它們可以用來按順序使用標準的Java eban慣例初始化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請求執行參數

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

'http.protocol.version':如果沒有在請求對象中設置明確的版本信息,它就定義了使用的HTTP協議版本。這個參數期望得到一個ProtocolVersion類型的值。如果這個參數沒有被設置,那么就使用HTTP/1.1。

'http.protocol.element-charset':定義了編碼HTTP協議元素的字符集。這個參數期望得到一個java.lang.String類型的值。如果這個參數沒有被設置,那么就使用US-ASCII。

'http.protocol.eontent-charset':定義了為每個內容主體編碼的默認字符集。這個參數期望得到一個java.lang.String類型的值。如果這個參數沒有被設置,那么就使用ISO-8859-1。

'http.useragent':定義了頭部信息User-Agent的內容。這個參數期望得到一個java.lang.String類型的值。如果這個參數沒有被設置,那么HttpClient將會為它自動生成一個值。

'http.protocol.strict-transfer-encoding':定義了響應頭部信息中是否含有一個非法的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秒。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容

  • 大盤向下流哇,手中的股票栽跟頭哇,說割咱就割呀,你割我割全是肉。路見到底抄一手哇,抄后發現想剁手哇,瘋瘋癲癲想跳樓...
    一些云端閱讀 172評論 0 0
  • 姓名:徐祖德 公司:廣東思沃精密機械有限公司 230期_利他1組 272期_樂觀2組志工 【日精進打卡第153天】...
    徐祖德閱讀 122評論 0 3
  • 蚊子甩開翅膀要去墳地 他要和蒼蠅在那里去相遇 因為老鼠秉蠟燭翻看一塊塊墓碑上墓碑上的冰冷的文字 而狂躁的暴雨如初春...
    李一十八閱讀 290評論 0 1
  • 大安鄉是單位的扶貧點。第一次去過大安鄉后,心里難以抑制的想寫一點文字,因為那蒼涼的大山,那曲折的道...
    辛苦快樂閱讀 1,022評論 1 4