HTTPClient入門

HTTPClient入門

HttpClient Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。本文首先介紹 HTTPClient,然後根據作者實際工作經驗給出了一些常見問題的解決方法。

HttpClient簡介

HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPoweredHttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發佈的 3.0 RC4

 


 

HttpClient 功能介紹

以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。

  • 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)

  • 支持自動轉向

  • 支持 HTTPS 協議

  • 支持代理服務器等

下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 HttpClient

 


 

HttpClient 基本功能的使用

GET 方法

使用 HttpClient 需要以下 6 個步驟:

1. 創建 HttpClient 的實例

2. 創建某種連接方法的實例,在這裏是 GetMethod。在 GetMethod 的構造函數中傳入待連接的地址

3. 調用第一步中創建好的實例的 execute 方法來執行第二步中創建好的 method 實例

4. response

5. 釋放連接。無論執行方法是否成功,都必須釋放連接

6. 對得到後的內容進行處理

根據以上步驟,我們來編寫用GET方法來取得某網頁內容的代碼。

  • 大部分情況下 HttpClient 默認的構造函數已經足夠使用。

HttpClient httpClient = new HttpClient();

 

  • 創建GET方法的實例。在GET方法的構造函數中傳入待連接的地址即可。用GetMethod將會自動處理轉發過程,如果想要把自動處理轉發過程去掉的話,可以調用方法setFollowRedirects(false)

GetMethod getMethod = new GetMethod("http://www.ibm.com/");

 

  • 調用實例httpClient executeMethod方法來執行getMethod。由於是執行在網絡上的程序,在運行executeMethod方法的時候,需要處理兩個異常, 分別是HttpExceptionIOException。引起第一種異常的原因主要可能是在構造getMethod的時候傳入的協議不對,比如不小心 將"http"寫成"htp",或者服務器端返回的內容不正常等,並且該異常發生是不可恢復的;第二種異常一般是由於網絡原因引起的異常,對於這種異常 (IOException),HttpClient會根據你指定的恢復策略自動試着重新執行executeMethod方法。HttpClient的恢復 策略可以自定義(通過實現接口HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設置你實 現的恢復策略,本文中使用的是系統提供的默認恢復策略,該策略在碰到第二類異常的時候將自動重試3次。executeMethod返回值是一個整數,表示 了執行該方法後服務器返回的狀態碼,該狀態碼能表示出該方法執行是否成功、需要認證或者頁面發生了跳轉(默認狀態下GetMethod的實例是自動處理跳 轉的)等。

//設置成了默認的恢復策略,在發生異常時候將自動重試3次,在這裏你也可以設置成自定義的恢復策略

getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

               new DefaultHttpMethodRetryHandler());

//執行getMethod

int statusCode = client.executeMethod(getMethod);

if (statusCode != HttpStatus.SC_OK) {

  System.err.println("Method failed: " + getMethod.getStatusLine());

}

 

  • 在返回的狀態碼正確後,即可取得內容。取得目標地址的內容有三種方法:第一種,getResponseBody,該方法返回的是目標的二進制的byte 流;第二種,getResponseBodyAsString,這個方法返回的是String類型,值得注意的是該方法返回的String的編碼是根據系 統默認的編碼方式,所以返回的String值可能編碼類型有誤,在本文的"字符編碼"部分中將對此做詳細介紹;第三種, getResponseBodyAsStream,這個方法對於目標地址中有大量數據需要傳輸是最佳的。在這裏我們使用了最簡單的 getResponseBody方法。

byte[] responseBody = method.getResponseBody();

 

  • 釋放連接。無論執行方法是否成功,都必須釋放連接。

method.releaseConnection();

 

  • 處理內容。在這一步中根據你的需要處理內容,在例子中只是簡單的將內容打印到控制檯。

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) {

  //構造HttpClient的實例

  HttpClient httpClient = new HttpClient();

  //創建GET方法的實例

  GetMethod getMethod = new GetMethod("http://www.ibm.com");

  //使用系統提供的默認的恢復策略

  getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,

    new DefaultHttpMethodRetryHandler());

  try {

   //執行getMethod

   int statusCode = httpClient.executeMethod(getMethod);

   if (statusCode != HttpStatus.SC_OK) {

    System.err.println("Method failed: "

      + getMethod.getStatusLine());

   }

   //讀取內容

   byte[] responseBody = getMethod.getResponseBody();

   //處理內容

   System.out.println(new String(responseBody));

  } catch (HttpException e) {

   //發生致命的異常,可能是協議不對或者返回的內容有問題

   System.out.println("Please check your provided http address!");

   e.printStackTrace();

  } catch (IOException e) {

   //發生網絡異常

   e.printStackTrace();

  } finally {

   //釋放連接

   getMethod.releaseConnection();

  }

 }

}

 

POST方法

根據RFC2616,對POST的解釋如下:POST方法用來向目的服務器發出請求,要求它接受被附在請求後的實體,並把它當作請求隊列(Request-Line)中請求URI所指定資源的附加新子項。POST被設計成用統一的方法實現下列功能:

  • 對現有資源的註釋(Annotation of existing resources

  • 向電子公告欄、新聞組,郵件列表或類似討論組發送消息

  • 提交數據塊,如將表單的結果提交給數據處理過程

  • 通過附加操作來擴展數據庫

調 用HttpClient中的PostMethodGetMethod類似,除了設置PostMethod的實例與GetMethod有些不同之外,剩下 的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,並以登錄清華大學BBS爲例子進行說明。

  • 構 造PostMethod之前的步驟都相同,與GetMethod一樣,構造PostMethod也需要一個URI參數,在本例中,登錄的地址是http: //www.newsmth.net/bbslogin2.php。在創建了PostMethod的實例之後,需要給method實例填充表單的值,在 BBS的登錄表單中需要有兩個域,第一個是用戶名(域名叫id),第二個是密碼(域名叫passwd)。表單中的域用類NameValuePair來表 示,該類的構造函數第一個參數是域名,第二參數是該域的值;將表單所有的值設置到PostMethod中用方法setRequestBody。另外由於 BBS登錄成功後會轉向另外一個頁面,但是HttpClient對於要求接受後繼服務的請求,比如POSTPUT,不支持自動轉發,因此需要自己對頁面 轉向做處理。具體的頁面轉向處理請參見下面的"自動轉向"部分。代碼如下:

String url = "http://www.newsmth.net/bbslogin2.php";

PostMethod postMethod = new PostMethod(url);

// 填入各個表單域的值

NameValuePair[] data = { new NameValuePair("id", "youUserName"),                           

new NameValuePair("passwd", "yourPwd") };

// 將表單的值放入postMethod

postMethod.setRequestBody(data);

// 執行postMethod

int statusCode = httpClient.executeMethod(postMethod);

// HttpClient對於要求接受後繼服務的請求,象POSTPUT等不能自動處理轉發

// 301或者302

if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||

statusCode == HttpStatus.SC_MOVED_TEMPORARILY) {

    // 從頭中取出轉向的地址

    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;

}

 

完整的程序代碼請參見附件中的test.PostSample

 


 

使用HttpClient過程中常見的一些問題

下面介紹在使用HttpClient過程中常見的一些問題。

字符編碼

某目標頁的編碼可能出現在兩個地方,第一個地方是服務器返回的http頭中,另外一個地方是得到的html/xml頁面中。

  • http 頭的Content-Type字段可能會包含字符編碼信息。例如可能返回的頭會包含這樣子的信息:Content-Type: text/html; charset=UTF-8。這個頭信息表明該頁的編碼是UTF-8,但是服務器返回的頭信息未必與內容能匹配上。比如對於一些雙字節語言國家,可能服務 器返回的編碼類型是UTF-8,但真正的內容卻不是UTF-8編碼的,因此需要在另外的地方去得到頁面的編碼信息;但是如果服務器返回的編碼不是UTF- 8,而是具體的一些編碼,比如gb2312等,那服務器返回的可能是正確的編碼信息。通過method對象的getResponseCharSet()方 法就可以得到http頭中的編碼信息。

  • 對於象xml或者html這樣的文件,允許作者在頁面中直接指定編碼類型。比如在 html中會有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>這樣的標籤;或者在xml中會有<?xml version="1.0" encoding="gb2312"?>這樣的標籤,在這些情況下,可能與http頭中返回的編碼信息衝突,需要用戶自己判斷到底那種編碼類型應該 是真正的編碼。

自動轉向

根 據RFC2616中對自動轉向的定義,主要有兩種:301302301表示永久的移走(Moved Permanently),當返回的是301,則表示請求的資源已經被移到一個固定的新地方,任何向該地址發起請求都會被轉到新的地址上。302表示暫時 的轉向,比如在服務器端的servlet程序調用了sendRedirect方法,則在客戶端就會得到一個302的代碼,這時服務器返回的頭信息中 location的值就是sendRedirect轉向的目標地址。

HttpClient支持自動轉向處理,但是象POST PUT方式這種要求接受後繼服務的請求方式,暫時不支持自動轉向,因此如果碰到POST方式提交後返回的是301或者302的話需要自己處理。就像剛纔在 POSTMethod中舉的例子:如果想進入登錄BBS後的頁面,必須重新發起登錄的請求,請求的地址可以在頭字段location中得到。不過需要注意 的是,有時候location返回的可能是相對路徑,因此需要對location返回的值做一些處理纔可以發起向新地址的請求。

另 外除了在頭中包含的信息可能使頁面發生重定向外,在頁面中也有可能會發生頁面的重定向。引起頁面自動轉發的標籤是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也處理這種情況的話得自己分析頁面來實現轉向。需要注意的是,在上面那個標籤中url的值也可以是一個相對地址,如果是這樣的話,需要對它做一些處理後纔可以轉發。

處理HTTPS協議

HttpClient 提供了對SSL的支持,在使用SSL之前必須安裝JSSE。在Sun提供的1.4以後的版本中,JSSE已經集成到JDK中,如果你使用的是JDK1.4 以前的版本則必須安裝JSSEJSSE不同的廠家有不同的實現。下面介紹怎麼使用HttpClient來打開Https連接。這裏有兩種方法可以打開 https連接,第一種就是得到服務器頒發的證書,然後導入到本地的keystore中;另外一種辦法就是通過擴展HttpClient的類來實現自動接受證書。

方法1,取得證書,並導入本地的keystore

  • 安裝JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳過這一步)。本文以IBMJSSE爲例子說明。先到IBM網站上下載JSSE的安裝包。然後解 壓開之後將ibmjsse.jar包拷貝到<java-home>/lib/ext/目錄下。

  • 取得並且導入證書。證書可以通過IE來獲得:

1. 用IE打開需要連接的https網址,會彈出如下對話框:


2. 單擊"View Certificate",在彈出的對話框中選擇"Details",然後再單擊"Copy to File",根據提供的嚮導生成待訪問網頁的證書文件


3. 嚮導第一步,歡迎界面,直接單擊"Next"


4. 嚮導第二步,選擇導出的文件格式,默認,單擊"Next"


5. 嚮導第三步,輸入導出的文件名,輸入後,單擊"Next"


6. 嚮導第四步,單擊"Finish",完成嚮導


7. 最後彈出一個對話框,顯示導出成功


  • keytool工具把剛纔導出的證書倒入本地keystoreKeytool命令在<java-home>/bin/下,打開命令行窗口,併到<java-home>/lib/security/目錄下,運行下面的命令:

keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer

 

其中參數alias後跟的值是當前證書在keystore中的唯一標識符,但是大小寫不區分;參數file後跟的是剛纔通過IE導出的證書所在的路徑和文件名;如果你想刪除剛纔導入到keystore的證書,可以用命令:

keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1

 

  • 寫程序訪問https地址。如果想測試是否能連上https,只需要稍改一下GetSample例子,把請求的目標變成一個https地址。

GetMethod getMethod = new GetMethod("https://www.yourdomain.com");

 

運行該程序可能出現的問題:

1. 拋出異常java.net.SocketException: Algorithm SSL not available。出現這個異常可能是因爲沒有加JSSEProvider,如果用的是IBMJSSE Provider,在程序中加入這樣的一行:

 if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null)

 Security.addProvider(new IBMJSSEProvider());

 

 

或者也可以打開<java-home>/lib/security/java.security,在行

security.provider.1=sun.security.provider.Sun

security.provider.2=com.ibm.crypto.provider.IBMJCE

 

後面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider

2. 拋出異常java.net.SocketException: SSL implementation not available。出現這個異常可能是你沒有把ibmjsse.jar拷貝到<java-home>/lib/ext/目錄下。

3. 拋出異常javax.net.ssl.SSLHandshakeException: unknown certificate。出現這個異常表明你的JSSE應該已經安裝正確,但是可能因爲你沒有把證書導入到當前運行JREkeystore中,請按照前 面介紹的步驟來導入你的證書。

方法2,擴展HttpClient類實現自動接受證書

因爲這種方法自動接收所有證書,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統的安全需求。具體的步驟如下:

  • 提 供一個自定義的socket factorytest.MySecureProtocolSocketFactory)。這個自定義的類必須實現接口 org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在實現接口 的類中調用自定義的X509TrustManager(test.MyX509TrustManager),這兩個類可以在隨本文帶的附件中得到

  • 創建一個org.apache.commons.httpclient.protocol.Protocol的實例,指定協議名稱和默認的端口號

Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);

 

  • 註冊剛纔創建的https協議對象

Protocol.registerProtocol("https ", myhttps);

 

  • 然後按照普通編程方式打開https的目標地址,代碼請參見test.NoCertificationHttpsGetSample

處理代理服務器

HttpClient中使用代理服務器非常簡單,調用HttpClientsetProxy方法就可以,方法的第一個參數是代理服務器地址,第二個參數是端口號。另外HttpClient也支持SOCKS代理。

 

httpClient.getHostConfiguration().setProxy(hostName,port);

 


 

結論

從上面的介紹中,可以知道HttpClienthttp協議支持非常好,使用起來很簡單,版本更新快,功能也很強大,具有足夠的靈活性和擴展性。對於想在Java應用中直接訪問http資源的編程人員來說,HttpClient是一個不可多得的好工具。

 

參考資料

  • Commons logging包含了各種各樣的日誌API的實現,讀者可以通過站點http://jakarta.apache.org/commons/logging/得到詳細的內容

  • Commons codec包含了一些一般的解碼/編碼算法。包含了語音編碼、十六進制、Base64URL編碼等,通過http://jakarta.apache.org/commons/codec/可以得到詳細的內容

  • rfc2616 是關於HTTP/1.1的文檔,可以在http://www.faqs.org/rfcs/rfc2616.html上得到詳細的內容,另外 rfc1945是關於HTTP/1.0的文檔,通過http://www.faqs.org/rfcs/rfc1945.html可以得到詳細內容

  • SSL――SSL 是由 Netscape Communications Corporation 1994 年開發的,而 TLS V1.0 是由 Internet Engineering Task ForceIETF)定義的標準,它基於 SSL V3.0,並且在使用的加密算法上與其有些許的不同。例如,SSL 使用 Message Authentication CodeMAC)算法來生成完整性校驗值,而 TLS 應用密鑰的 Hashing for Message Authentication CodeHMAC)算法。

  • IBM JSSE提供了SSLSecure Sockets Layer)和TLSTransport Layer Security)的java實現,在http://www- 03.ibm.com/servers/eserver/zseries/software/java/jsse.html中可以得到詳細的信息

  • Keytool是一個管理密鑰和證書的工具。關於它詳細的使用信息可以在http://www.doc.ic.ac.uk/csg/java/1.3.1docs/tooldocs/solaris/keytool.html上得到

  • HTTPClient的主頁是http://jakarta.apache.org/commons/httpclient/,你可以在這裏得到關於HttpClient更加詳細的信息

 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章