电竞比分网-中国电竞赛事及体育赛事平台

分享

HttpClient 簡(jiǎn)介

 pengx 2009-09-16
一、HttpClient入門
      HttpClient 是 Apache Jakarta Common 下的子項(xiàng)目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。本文首先介紹 HTTPClient,然后根據(jù)作者實(shí)際工作經(jīng)驗(yàn)給出了一些常見問題的解決方法。
HttpClient 基本功能的使用
(一)、GET 方法
使用 HttpClient 需要以下 6 個(gè)步驟: 
      1. 創(chuàng)建 HttpClient 的實(shí)例 
      2. 創(chuàng)建某種連接方法的實(shí)例,在這里是 GetMethod。在 GetMethod 的構(gòu)造函數(shù)中傳入待連接的地址 
      3. 調(diào)用第一步中創(chuàng)建好的實(shí)例的 execute 方法來執(zhí)行第二步中創(chuàng)建好的 method 實(shí)例 
      4. 讀 response 
      5. 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接 
      6. 對(duì)得到后的內(nèi)容進(jìn)行處理
根據(jù)以上步驟,我們來編寫用GET方法來取得某網(wǎng)頁內(nèi)容的代碼。 
      • 大部分情況下 HttpClient 默認(rèn)的構(gòu)造函數(shù)已經(jīng)足夠使用。 
            HttpClient httpClient = new HttpClient(); 
      • 創(chuàng)建GET方法的實(shí)例。在GET方法的構(gòu)造函數(shù)中傳入待連接的地址即可。用GetMethod將會(huì)自動(dòng)處理轉(zhuǎn)發(fā)過程,如果想要把自動(dòng)處理轉(zhuǎn)發(fā)過程去掉的話,可以調(diào)用方法setFollowRedirects(false)。 
            GetMethod getMethod = new GetMethod("http://www.ibm.com/"); 
      • 調(diào)用實(shí)例httpClient的executeMethod方法來執(zhí)行g(shù)etMethod。由于是執(zhí)行在網(wǎng)絡(luò)上的程序,在運(yùn)行executeMethod方法的時(shí)候,需要處理兩個(gè)異常,分別是HttpException和IOException。引起第一種異常的原因主要可能是在構(gòu)造getMethod的時(shí)候傳入的協(xié)議不對(duì),比如不小心將"http"寫成"htp",或者服務(wù)器端返回的內(nèi)容不正常等,并且該異常發(fā)生是不可恢復(fù)的;第二種異常一般是由于網(wǎng)絡(luò)原因引起的異常,對(duì)于這種異常 (IOException),HttpClient會(huì)根據(jù)你指定的恢復(fù)策略自動(dòng)試著重新執(zhí)行executeMethod方法。HttpClient的恢復(fù)策略可以自定義(通過實(shí)現(xiàn)接口HttpMethodRetryHandler來實(shí)現(xiàn))。通過httpClient的方法setParameter設(shè)置你實(shí)現(xiàn)的恢復(fù)策略,本文中使用的是系統(tǒng)提供的默認(rèn)恢復(fù)策略,該策略在碰到第二類異常的時(shí)候?qū)⒆詣?dòng)重試3次。executeMethod返回值是一個(gè)整數(shù),表示了執(zhí)行該方法后服務(wù)器返回的狀態(tài)碼,該狀態(tài)碼能表示出該方法執(zhí)行是否成功、需要認(rèn)證或者頁面發(fā)生了跳轉(zhuǎn)(默認(rèn)狀態(tài)下GetMethod的實(shí)例是自動(dòng)處理跳轉(zhuǎn)的)等。
//設(shè)置成了默認(rèn)的恢復(fù)策略,在發(fā)生異常時(shí)候?qū)⒆詣?dòng)重試3次,在這里你也可以設(shè)置成自定義的恢復(fù)策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
    new DefaultHttpMethodRetryHandler());
//執(zhí)行g(shù)etMethod
int statusCode = client.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
  System.err.println("Method failed: " + getMethod.getStatusLine());

      • 在返回的狀態(tài)碼正確后,即可取得內(nèi)容。取得目標(biāo)地址的內(nèi)容有三種方法:第一種,getResponseBody,該方法返回的是目標(biāo)的二進(jìn)制的byte流;第二種,getResponseBodyAsString,這個(gè)方法返回的是String類型,值得注意的是該方法返回的String的編碼是根據(jù)系統(tǒng)默認(rèn)的編碼方式,所以返回的String值可能編碼類型有誤,在本文的"字符編碼"部分中將對(duì)此做詳細(xì)介紹;第三種,getResponseBodyAsStream,這個(gè)方法對(duì)于目標(biāo)地址中有大量數(shù)據(jù)需要傳輸是最佳的。在這里我們使用了最簡(jiǎn)單的getResponseBody方法。
byte[] responseBody = method.getResponseBody(); 
      • 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接。
method.releaseConnection(); 
      • 處理內(nèi)容。在這一步中根據(jù)你的需要處理內(nèi)容,在例子中只是簡(jiǎn)單的將內(nèi)容打印到控制臺(tái)。
System.out.println(new String(responseBody));
下面是程序的完整代碼,這些代碼也可在附件中的test.GetSample中找到。
package test;
import java.io.IOException;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
public class GetSample{
  public static void main(String[] args) {
  //構(gòu)造HttpClient的實(shí)例
  HttpClient httpClient = new HttpClient();
  //創(chuàng)建GET方法的實(shí)例
  GetMethod getMethod = new GetMethod("http://www.ibm.com");
  //使用系統(tǒng)提供的默認(rèn)的恢復(fù)策略
  getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
    new DefaultHttpMethodRetryHandler());
  try {
   //執(zhí)行g(shù)etMethod
   int statusCode = httpClient.executeMethod(getMethod);
   if (statusCode != HttpStatus.SC_OK) {
    System.err.println("Method failed: "
      + getMethod.getStatusLine());
   }
   //讀取內(nèi)容
   byte[] responseBody = getMethod.getResponseBody();
   //處理內(nèi)容
   System.out.println(new String(responseBody));
  } catch (HttpException e) {
   //發(fā)生致命的異常,可能是協(xié)議不對(duì)或者返回的內(nèi)容有問題
   System.out.println("Please check your provided http address!");
   e.printStackTrace();
  } catch (IOException e) {
   //發(fā)生網(wǎng)絡(luò)異常
   e.printStackTrace();
  } finally {
   //釋放連接
   getMethod.releaseConnection();
  }
}
}

(二)、POST方法 
      根據(jù)RFC2616,對(duì)POST的解釋如下:POST方法用來向目的服務(wù)器發(fā)出請(qǐng)求,要求它接受被附在請(qǐng)求后的實(shí)體,并把它當(dāng)作請(qǐng)求隊(duì)列(Request-Line)中請(qǐng)求URI所指定資源的附加新子項(xiàng)。POST被設(shè)計(jì)成用統(tǒng)一的方法實(shí)現(xiàn)下列功能: 
      • 對(duì)現(xiàn)有資源的注釋(Annotation of existing resources) 
      • 向電子公告欄、新聞組,郵件列表或類似討論組發(fā)送消息 
      • 提交數(shù)據(jù)塊,如將表單的結(jié)果提交給數(shù)據(jù)處理過程 
      • 通過附加操作來擴(kuò)展數(shù)據(jù)庫(kù)
調(diào)用HttpClient中的PostMethod與GetMethod類似,除了設(shè)置PostMethod的實(shí)例與GetMethod有些不同之外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,并以登錄清華大學(xué)BBS為例子進(jìn)行說明。 
      • 構(gòu)造PostMethod之前的步驟都相同,與GetMethod一樣,構(gòu)造PostMethod也需要一個(gè)URI參數(shù),在本例中,登錄的地址是http://www./bbslogin2.php。在創(chuàng)建了PostMethod的實(shí)例之后,需要給method實(shí)例填充表單的值,在BBS的登錄表單中需要有兩個(gè)域,第一個(gè)是用戶名(域名叫id),第二個(gè)是密碼(域名叫passwd)。表單中的域用類NameValuePair來表示,該類的構(gòu)造函數(shù)第一個(gè)參數(shù)是域名,第二參數(shù)是該域的值;將表單所有的值設(shè)置到PostMethod中用方法setRequestBody。另外由于BBS登錄成功后會(huì)轉(zhuǎn)向另外一個(gè)頁面,但是HttpClient對(duì)于要求接受后繼服務(wù)的請(qǐng)求,比如POST和PUT,不支持自動(dòng)轉(zhuǎn)發(fā),因此需要自己對(duì)頁面轉(zhuǎn)向做處理。具體的頁面轉(zhuǎn)向處理請(qǐng)參見下面的"自動(dòng)轉(zhuǎn)向"部分。代碼如下:
String url = "http://www./bbslogin2.php";
PostMethod postMethod = new PostMethod(url);
// 填入各個(gè)表單域的值
NameValuePair[] data = { new NameValuePair("id", "youUserName"),
new NameValuePair("passwd", "yourPwd") };
// 將表單的值放入postMethod中
postMethod.setRequestBody(data);
// 執(zhí)行postMethod
int statusCode = httpClient.executeMethod(postMethod);
// HttpClient對(duì)于要求接受后繼服務(wù)的請(qǐng)求,象POST和PUT等不能自動(dòng)處理轉(zhuǎn)發(fā)
// 301或者302
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {
    // 從頭中取出轉(zhuǎn)向的地址
    Header locationHeader = postMethod.getResponseHeader("location");
    String location = null;
    if (locationHeader != null) {
     location = locationHeader.getValue();
     System.out.println("The page was redirected to:" + location);
    } else {
     System.err.println("Location field value is null.");
    }
    return;
}
 
二、HttpClient一些問題
下面介紹在使用HttpClient過程中常見的一些問題。
(一)、字符編碼 
      某目標(biāo)頁的編碼可能出現(xiàn)在兩個(gè)地方,第一個(gè)地方是服務(wù)器返回的http頭中,另外一個(gè)地方是得到的html/xml頁面中。 
      • 在http頭的Content-Type字段可能會(huì)包含字符編碼信息。例如可能返回的頭會(huì)包含這樣子的信息:Content-Type: text/html; charset=UTF-8。這個(gè)頭信息表明該頁的編碼是UTF-8,但是服務(wù)器返回的頭信息未必與內(nèi)容能匹配上。比如對(duì)于一些雙字節(jié)語言國(guó)家,可能服務(wù)器返回的編碼類型是UTF-8,但真正的內(nèi)容卻不是UTF-8編碼的,因此需要在另外的地方去得到頁面的編碼信息;但是如果服務(wù)器返回的編碼不是UTF-8,而是具體的一些編碼,比如gb2312等,那服務(wù)器返回的可能是正確的編碼信息。通過method對(duì)象的getResponseCharSet()方法就可以得到http頭中的編碼信息。 
      • 對(duì)于象xml或者h(yuǎn)tml這樣的文件,允許作者在頁面中直接指定編碼類型。比如在html中會(huì)有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>這樣的標(biāo)簽;或者在xml中會(huì)有<?xml version="1.0" encoding="gb2312"?>這樣的標(biāo)簽,在這些情況下,可能與http頭中返回的編碼信息沖突,需要用戶自己判斷到底那種編碼類型應(yīng)該是真正的編碼。
編碼問題解決
private static final String CONTENT_CHARSET = "GBK";// httpclient讀取內(nèi)容時(shí)使用的字符集  
HttpClient client = new HttpClient();  
client.getParams().setParameter(  
HttpMethodParams.HTTP_CONTENT_CHARSET, CONTENT_CHARSET);
字符串編碼改變的方法:String target = new String(orig.getBytes("ISO-8859-1"),"GBK");

(二)、認(rèn)證
HttpClient三種不同的認(rèn)證方案: Basic, Digest and NTLM. 這些方案可用于服務(wù)器或代理對(duì)客戶端的認(rèn)證,簡(jiǎn)稱服務(wù)器認(rèn)證或代理認(rèn)證。
1) 服務(wù)器認(rèn)證(Server Authentication)
HttpClient處理服務(wù)器認(rèn)證幾乎是透明的,僅需要開發(fā)人員提供登錄信息(login credentials)。登錄信息保存在HttpState類的實(shí)例中,可以通過 setCredentials(String realm, Credentials cred)和getCredentials(String realm)來獲取或設(shè)置。注意,設(shè)定對(duì)非特定站點(diǎn)訪問所需要的登錄信息,將realm參數(shù)置為null. HttpClient內(nèi)建的自動(dòng)認(rèn)證,可以通過HttpMethod類的setDoAuthentication(boolean doAuthentication)方法關(guān)閉,而且這次關(guān)閉只影響HttpMethod當(dāng)前的實(shí)例。
搶先認(rèn)證(Preemptive Authentication)可以通過下述方法打開.
client.getState().setAuthenticationPreemptive(true);
在這種模式時(shí),HttpClient會(huì)主動(dòng)將basic認(rèn)證應(yīng)答信息傳給服務(wù)器,即使在某種情況下服務(wù)器可能返回認(rèn)證失敗的應(yīng)答,這樣做主要是為了減少連接的建立。為使每個(gè)新建的 HttpState實(shí)例都實(shí)行搶先認(rèn)證,可以如下設(shè)置系統(tǒng)屬性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");
Httpclient實(shí)現(xiàn)的搶先認(rèn)證遵循rfc2617.
2)代理認(rèn)證(proxy authentication)
除了登錄信息需單獨(dú)存放以外,代理認(rèn)證與服務(wù)器認(rèn)證幾乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)設(shè)、取登錄信息。
3) 認(rèn)證方案(authentication schemes)
Basic
是HTTP中規(guī)定最早的也是最兼容(?)的方案,遺憾的是也是最不安全的一個(gè)方案,因?yàn)樗悦鞔a傳送用戶名和密碼。它要求一個(gè)UsernamePasswordCredentials實(shí)例,可以指定服務(wù)器端的訪問空間或采用默認(rèn)的登錄信息。
Digest
是在HTTP1.1中增加的一個(gè)方案,雖然不如Basic得到的軟件支持多,但還是有廣泛的使用。Digest方案比Basic方案安全得多,因它根本就不通過網(wǎng)絡(luò)傳送實(shí)際的密碼,傳送的是利用這個(gè)密碼對(duì)從服務(wù)器傳來的一個(gè)隨機(jī)數(shù)(nonce)的加密串。它要求一個(gè)UsernamePasswordCredentials實(shí)例,可以指定服務(wù)器端的訪問空間或采用默認(rèn)的登錄信息。
NTLM
這是HttpClient支持的最復(fù)雜的認(rèn)證協(xié)議。它M$設(shè)計(jì)的一個(gè)私有協(xié)議,沒有公開的規(guī)范說明。一開始由于設(shè)計(jì)的缺陷,NTLM的安全性比Digest差,后來經(jīng)過一個(gè)ServicePack補(bǔ)丁后,安全性則比較Digest高。NTLM需要一個(gè)NTCredentials實(shí)例. 注意,由于NTLM不使用訪問空間(realms)的概念,HttpClient利用服務(wù)器的域名作訪問空間的名字。還需要注意,提供給NTCredentials的用戶名,不要用域名的前綴 - 如: "adrian" 是正確的,而 "DOMAIN\adrian" 則是錯(cuò)的. NTLM認(rèn)證的工作機(jī)制與basic和digest有很大的差別。這些差別一般由HttpClient處理,但理解這些差別有助避免在使用NTLM認(rèn)證時(shí)出現(xiàn)錯(cuò)誤。
從HttpClientAPI的角度來看,NTLM與其它認(rèn)證方式一樣的工作,差別是需要提供'NTCredentials'實(shí)例而不是'UsernamePasswordCredentials'(其實(shí),前者只是擴(kuò)展了后者)對(duì)NTLM認(rèn)證,訪問空間是連接到的機(jī)器的域名,這對(duì)多域名主機(jī)會(huì)有一些麻煩.只有HttpClient連接中指定的域名才是認(rèn)證用的域名。建議將realm設(shè)為null以使用默認(rèn)的設(shè)置。NTLM只是認(rèn)證了一個(gè)連接而不是一請(qǐng)求,所以每當(dāng)一個(gè)新的連接建立就要進(jìn)行一次認(rèn)證,且在認(rèn)證的過程中保持連接是非常重要的。 因此,NTLM不能同時(shí)用于代理認(rèn)證和服務(wù)器認(rèn)證,也不能用于http1.0連接或服務(wù)器不支持持久連接的情況。
(三)、重定向
由于技術(shù)限制,以及為保證2.0發(fā)布版API的穩(wěn)定,HttpClient還不能自動(dòng)處重定向,但對(duì)重定向到同一主機(jī)、同一端口且采用同一協(xié)議的情況HttpClient可以支持。不能自動(dòng)的處理的情況,包括需要人工交互的情況,或超出httpclient的能力。
當(dāng)服務(wù)器重定向指令指到不同的主機(jī)時(shí),HttpClient只是簡(jiǎn)單地將重定向狀態(tài)碼作為應(yīng)答狀態(tài)。所有的300到399(包含兩端)的返回碼,都表示是重定向應(yīng)答。常見的有:
301 永久移動(dòng). HttpStatus.SC_MOVED_PERMANENTLY
302 臨時(shí)移動(dòng). HttpStatus.SC_MOVED_TEMPORARILY
303 See Other. HttpStatus.SC_SEE_OTHER
307 臨時(shí)重定向. HttpStatus.SC_TEMPORARY_REDIRECT  
當(dāng)收到簡(jiǎn)單的重定向時(shí),程序應(yīng)從HttpMethod對(duì)象中抽取新的URL并將其下載。另外,限制一下重定向次數(shù)是個(gè)好的主意,這可以避免遞歸循環(huán)。新的URL可以從頭字段Location中抽取,如下:
String redirectLocation;
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null) {
redirectLocation = locationHeader.getValue();
} else {
// The response is invalid and did not provide the new location for
// the resource. Report an error or possibly handle the response
// like a 404 Not Found error.
}
特殊重定向:
300 多重選擇. HttpStatus.SC_MULTIPLE_CHOICES
304 沒有改動(dòng). HttpStatus.SC_NO T_MODIFIED
305 使用代理. HttpStatus.SC_USE_PROXY
 
(四)、自動(dòng)轉(zhuǎn)向
根據(jù)RFC2616中對(duì)自動(dòng)轉(zhuǎn)向的定義,主要有兩種:301和302。301表示永久的移走(Moved Permanently),當(dāng)返回的是301,則表示請(qǐng)求的資源已經(jīng)被移到一個(gè)固定的新地方,任何向該地址發(fā)起請(qǐng)求都會(huì)被轉(zhuǎn)到新的地址上。302表示暫時(shí)的轉(zhuǎn)向,比如在服務(wù)器端的servlet程序調(diào)用了sendRedirect方法,則在客戶端就會(huì)得到一個(gè)302的代碼,這時(shí)服務(wù)器返回的頭信息中l(wèi)ocation的值就是sendRedirect轉(zhuǎn)向的目標(biāo)地址。
HttpClient支持自動(dòng)轉(zhuǎn)向處理,但是象POST和PUT方式這種要求接受后繼服務(wù)的請(qǐng)求方式,暫時(shí)不支持自動(dòng)轉(zhuǎn)向,因此如果碰到POST方式提交后返回的是301或者302的話需要自己處理。就像剛才在POSTMethod中舉的例子:如果想進(jìn)入登錄BBS后的頁面,必須重新發(fā)起登錄的請(qǐng)求,請(qǐng)求的地址可以在頭字段location中得到。不過需要注意的是,有時(shí)候location返回的可能是相對(duì)路徑,因此需要對(duì)location返回的值做一些處理才可以發(fā)起向新地址的請(qǐng)求。
另外除了在頭中包含的信息可能使頁面發(fā)生重定向外,在頁面中也有可能會(huì)發(fā)生頁面的重定向。引起頁面自動(dòng)轉(zhuǎn)發(fā)的標(biāo)簽是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也處理這種情況的話得自己分析頁面來實(shí)現(xiàn)轉(zhuǎn)向。需要注意的是,在上面那個(gè)標(biāo)簽中url的值也可以是一個(gè)相對(duì)地址,如果是這樣的話,需要對(duì)它做一些處理后才可以轉(zhuǎn)發(fā)。
 
(五)、Cookies
   HttpClient能自動(dòng)管理cookie,包括允許服務(wù)器設(shè)置cookie并在需要的時(shí)候自動(dòng)將cookie返回服務(wù)器,它也支持手工設(shè)置cookie后發(fā)送到服務(wù)器端。不幸的是,對(duì)如何處理cookie,有幾個(gè)規(guī)范互相沖突:Netscape Cookie 草案, RFC2109, RFC2965,而且還有很大數(shù)量的軟件商的cookie實(shí)現(xiàn)不遵循任何規(guī)范. 為了處理這種狀況,HttpClient提供了策略驅(qū)動(dòng)的cookie管理方式。HttpClient支持的cookie規(guī)范有: Netscape cookie草案,是最早的cookie規(guī)范,基于rfc2109。盡管這個(gè)規(guī)范與rc2109有較大的差別,這樣做可以與一些服務(wù)器兼容。rfc2109,是w3c發(fā)布的第一個(gè)官方cookie規(guī)范。理論上講,所有的服務(wù)器在處理cookie(版本1)時(shí),都要遵循此規(guī)范,正因如此,HttpClient將其設(shè)為默認(rèn)的規(guī)范。遺憾的是,這個(gè)規(guī)范太嚴(yán)格了,以致很多服務(wù)器不正確的實(shí)施了該規(guī)范或仍在作用Netscape規(guī)范。在這種情況下,應(yīng)使用兼容規(guī)范。兼容性規(guī)范,設(shè)計(jì)用來兼容盡可能多的服務(wù)器,即使它們并沒有遵循標(biāo)準(zhǔn)規(guī)范。當(dāng)解析cookie出現(xiàn)問題時(shí),應(yīng)考慮采用兼容性規(guī)范。
   RFC2965規(guī)范暫時(shí)沒有被HttpClient支持(在以后的版本為會(huì)加上),它定義了cookie版本2,并說明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
在HttpClient中,有兩種方法來指定cookie規(guī)范的使用,
HttpClient client = new HttpClient();
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
這種方法設(shè)置的規(guī)范只對(duì)當(dāng)前的HttpState有效,參數(shù)可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。
System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
此法指的規(guī)范,對(duì)以后每個(gè)新建立的HttpState對(duì)象都有效,參數(shù)可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
常有不能解析cookie的問題,但更換到兼容規(guī)范大都能解決。

(六)、使用HttpClient遇到問題怎么辦?
用一個(gè)瀏覽器訪問服務(wù)器,以確認(rèn)服務(wù)器應(yīng)答正常如果在使代理,關(guān)掉代理試試另找一個(gè)服務(wù)器來試試(如果運(yùn)行著不同的服務(wù)器軟件更好)檢查代碼是否按教程中講的思路編寫設(shè)置log級(jí)別為debug,找出問題出現(xiàn)的原因打開wiretrace,來追蹤客戶端與服務(wù)器的通信,以確實(shí)問題出現(xiàn)在什么地方用telnet或netcat手工將信息發(fā)送到服務(wù)器,適合于猜測(cè)已經(jīng)找到了原因而進(jìn)行試驗(yàn)時(shí)將netcat以監(jiān)聽方式運(yùn)行,用作服務(wù)器以檢查httpclient如何處理應(yīng)答的。利用最新的httpclient試試,bug可能在最新的版本中修復(fù)了向郵件列表求幫助向bugzilla報(bào)告bug.

(七)、SSL
借助Java Secure Socket Extension (JSSE),HttpClient全面支持Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)協(xié)議上的HTTP。JSSE已經(jīng)jre1.4及以后的版本中,以前的版本則需要手工安裝設(shè)置,具體過程參見Sun網(wǎng)站或本學(xué)習(xí)筆記。
HttpClient中使用SSL非常簡(jiǎn)單,參考下面兩個(gè)例子:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www./");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
,如果通過需要授權(quán)的代理,則如下:
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setProxy("myproxyhost", 8080);
httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost",
new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password"));
GetMethod httpget = new GetMethod("https://www./");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
在HttpClient中定制SSL的步驟如下:
提供了一個(gè)實(shí)現(xiàn)了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。這個(gè) socket factory負(fù)責(zé)打一個(gè)到服務(wù)器的端口,使用標(biāo)準(zhǔn)的或第三方的SSL函數(shù)庫(kù),并進(jìn)行象連接握手等初始化操作。通常情況下,這個(gè)初始化操作在端口被創(chuàng)建時(shí)自動(dòng)進(jìn)行的。實(shí)例化一個(gè)org.apache.commons.httpclient.protocol.Protocol對(duì)象。創(chuàng)建這個(gè)實(shí)例時(shí),需要一個(gè)合法的協(xié)議類型(如https),一個(gè)定制的socket factory,和一個(gè)默認(rèn)的端中號(hào)(如https的443端口). Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);然后,這個(gè)實(shí)例可被設(shè)置為協(xié)議的處理器。
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost("www.", 443, myhttps);
GetMethod httpget = new GetMethod("/");
httpclient.executeMethod(httpget);
通過調(diào)用Protocol.registerProtocol方法,將此定制的實(shí)例,注冊(cè)為某一特定協(xié)議的默認(rèn)的處理器。由此,可以很方便地定制自己的協(xié)議類型(如myhttps)。
Protocol.registerProtocol("myhttps",
new Protocol("https", new MySSLSocketFactory(), 9443));
...
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("myhttps://www./");
httpclient.executeMethod(httpget);
如果想用自己定制的處理器取代https默認(rèn)的處理器,只需要將其注冊(cè)為"https"即可。
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www./");
httpclient.executeMethod(httpget);
已知的限制和問題持續(xù)的SSL連接在Sun的低于1.4JVM上不能工作,這是由于JVM的bug造成。通過代理訪問服務(wù)器時(shí),非搶先認(rèn)證( Non-preemptive authentication)會(huì)失敗,這是由于HttpClient的設(shè)計(jì)缺陷造成的,以后的版本中會(huì)修改。
遇到問題的處理
很多問題,特別是在jvm低于1.4時(shí),是由jsse的安裝造成的。
下面的代碼,可作為最終的檢測(cè)手段。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import javax.net.ssl.SSLSocketFactory;
public class Test {
public static final String TARGET_HTTPS_SERVER = "www.";
public static final int TARGET_HTTPS_PORT = 443;
public static void main(String[] args) throws Exception {
Socket socket = SSLSocketFactory.getDefault().
createSocket(TARGET_HTTPS_SERVER, TARGET_HTTPS_PORT);
try {
Writer out = new OutputStreamWriter(
socket.getOutputStream(), "ISO-8859-1");
out.write("GET / HTTP/1.1\r\n");
out.write("Host: " + TARGET_HTTPS_SERVER + ":" +
TARGET_HTTPS_PORT + "\r\n");
out.write("Agent: SSL-TEST\r\n");
out.write("\r\n");
out.flush();
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "ISO-8859-1"));
String line = null;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} finally {
socket.close();
}
}
}

(八)、httpclient的多線程處理
使用多線程的主要目的,是為了實(shí)現(xiàn)并行的下載。在httpclient運(yùn)行的過程中,每個(gè)http協(xié)議的方法,使用一個(gè)HttpConnection實(shí)例。由于連接是一種有限的資源,每個(gè)連接在某一時(shí)刻只能供一個(gè)線程和方法使用,所以需要確保在需要時(shí)正確地分配連接。HttpClient采用了一種類似jdbc連接池的方法來管理連接,這個(gè)管理工作由 MultiThreadedHttpConnectionManager完成。
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
此是,client可以在多個(gè)線程中被用來執(zhí)行多個(gè)方法。每次調(diào)用HttpClient.executeMethod() 方法,都會(huì)去鏈接管理器申請(qǐng)一個(gè)連接實(shí)例,申請(qǐng)成功這個(gè)鏈接實(shí)例被簽出(checkout),隨之在鏈接使用完后必須歸還管理器。管理器支持兩個(gè)設(shè)置:maxConnectionsPerHost 每個(gè)主機(jī)的最大并行鏈接數(shù),默認(rèn)為2
maxTotalConnections 客戶端總并行鏈接最大數(shù),默認(rèn)為20
管理器重新利用鏈接時(shí),采取早歸還者先重用的方式(least recently used approach)?! ∮捎谑鞘褂肏ttpClient的程序而不是HttpClient本身來讀取應(yīng)答包的主體,所以HttpClient無法決定什么時(shí)間連接不再使用了,這也就要求在讀完應(yīng)答包的主體后必須手工顯式地調(diào)用releaseConnection()來釋放申請(qǐng)的鏈接。
MultiThreadedHttpConnectionManager connectionManager = new
MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
...
// 在某個(gè)線程中。
GetMethod get = new GetMethod("http://jakarta./");
try {
client.executeMethod(get);
// print response to stdout
System.out.println(get.getResponseBodyAsStream());
} finally {
// be sure the connection is released back to the connection
// manager
get.releaseConnection();
}
對(duì)每一個(gè)HttpClient.executeMethod須有一個(gè)method.releaseConnection()與之匹配.
 
(九)、HttpClient的連接釋放問題
在method.releaseConnection()后并沒有把鏈接關(guān)閉,這個(gè)方法只是將鏈接返回給connection manager。
如果使用HttpClient client = new HttpClient()實(shí)例化一個(gè)HttpClient connection manager默認(rèn)實(shí)現(xiàn)是使用SimpleHttpConnectionManager。
SimpleHttpConnectionManager有個(gè)構(gòu)造函數(shù)如下
public SimpleHttpConnectionManager(boolean alwaysClose) {  
    super();  
    this.alwaysClose = alwaysClose;  

alwaysClose設(shè)為true在鏈接釋放之后connection manager 就會(huì)關(guān)閉鏈。在我們HttpClient client = new HttpClient()這樣實(shí)例化一個(gè)client時(shí)connection manager是這樣被實(shí)例化的
this.httpConnectionManager = new SimpleHttpConnectionManager(); 
因此alwaysClose默認(rèn)是false,connection是不會(huì)被主動(dòng)關(guān)閉的,因此我們就有了一個(gè)客戶端關(guān)閉鏈接的方法。
解決方法:
1、把事例代碼中的第一行實(shí)例化代碼改為如下即可,在method.releaseConnection();之后connection manager會(huì)關(guān)閉connection 。
HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) ); 
2、實(shí)例化代碼使用:HttpClient client = new HttpClient(); 在method.releaseConnection();之后加上 ((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();
其實(shí)shutdown源代碼是 
public void shutdown() {  
    httpConnection.close();  

3、實(shí)例化代碼使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
client.getHttpConnectionManager().closeIdleConnections(0);此方法源碼代碼如下:
public void closeIdleConnections(long idleTimeout) {  
    long maxIdleTime = System.currentTimeMillis() - idleTimeout;  
    if (idleStartTime <= maxIdleTime) {  
        httpConnection.close();  
    }  
}
4、代碼實(shí)現(xiàn)很簡(jiǎn)單,所有代碼就和最上面的事例代碼一樣。只需要在HttpMethod method = new GetMethod("http://www.");加上一行HTTP頭的設(shè)置即可method.setRequestHeader("Connection", "close");

(十)、HttpClient的一個(gè)應(yīng)用的例子(圖片下載)
1. import java.io.File;  
2. import java.io.FileOutputStream;  
3. import java.io.IOException;  
4. import org.apache.commons.httpclient.HttpClient;  
5. import org.apache.commons.httpclient.methods.GetMethod;  
6.   
7. /** 
8. * 用HttpClient下載圖片 
9. * @author wei 
10. */  
11. public class TestDownImage {  
12.       
13.     public static void main(String[] args) throws IOException{  
14.         HttpClient client = new HttpClient();  
15.         GetMethod get = new GetMethod("http://images.sohu.com/uiue/sohu_logo/beijing2008/2008sohu.gif");  
16.         client.executeMethod(get);  
17.         File storeFile = new File("c:/2008sohu.gif");  
18.         FileOutputStream output = new FileOutputStream(storeFile);  
19.         //得到網(wǎng)絡(luò)資源的字節(jié)數(shù)組,并寫入文件  
20.         output.write(get.getResponseBody());  
21.         output.close();  
22.     }  
23. }

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多