層級: 進階 發華 金, 軟體工程師,IBM CSDL, IBM 樟洪 陳, 軟體工程師,IBM CSDL, IBM 2005 年 11 月 10 日
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/HttpClientPowered。HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發布的 3.0 RC4 。
HttpClient 功能介紹 以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的首頁。
- 實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
- 支援自動轉向
- 支援 HTTPS 協議
- 支援Proxy 伺服器等
下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 HttpClient。
- HttpClient 可以在http://jakarta.apache.org/commons/httpclient/downloads.html下載
- HttpClient 用到了 Apache Jakarta common 下的子項目 logging,你可以從這個地址http://jakarta.apache.org/site/downloads/downloads_commons-logging.cgi下載到 common logging,從下載後的壓縮包中取出 commons-logging.jar 加到 CLASSPATH 中
- HttpClient 用到了 Apache Jakarta common 下的子項目 codec,你可以從這個地址http://jakarta.apache.org/site/downloads/downloads_commons-codec.cgi 下載到最新的 common codec,從下載後的壓縮包中取出 commons-codec-1.x.jar 加到 CLASSPATH 中
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方法的時候,需要處理兩個異常,分別是HttpException和IOException。引起第一種異常的原因主要可能是在構造getMethod的時候傳入的協議不對,比如不小心將"http"寫成"htp",或者伺服器端返回的內容不正常等,並且該異常發生是不可恢複的;第二種異常一般是由於網路原因引起的異常,對於這種異常 (IOException),HttpClient會根據你指定的恢複策略自動試著重新執行executeMethod方法。HttpClient的恢複策略可以自訂(通過實現介面HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設定你實現的恢複策略,本文中使用的是系統提供的預設恢複策略,該策略在碰到第二類異常的時候將自動重試3次。executeMethod傳回值是一個整數,表示了執行該方法後伺服器返回的狀態代碼,該狀態代碼能表示出該方法執行是否成功、需要認證或者頁面發生了跳轉(預設狀態下GetMethod的執行個體是自動處理跳轉的)等。
//設定成了預設的恢複策略,在發生異常時候將自動重試3次,在這裡你也可以設定成自訂的恢複策略getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler()); //執行getMethodint 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中的PostMethod與GetMethod類似,除了設定PostMethod的執行個體與GetMethod有些不同之外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,並以登入清華大學BBS為例子進行說明。
- 構造PostMethod之前的步驟都相同,與GetMethod一樣,構造PostMethod也需要一個URI參數,在本例中,登入的地址是http://www.newsmth.net/bbslogin2.php。在建立了PostMethod的執行個體之後,需要給method執行個體填充表單的值,在BBS的登入表單中需要有兩個域,第一個是使用者名稱(網域名稱叫id),第二個是密碼(網域名稱叫passwd)。表單中的域用類NameValuePair來表示,該類的建構函式第一個參數是網域名稱,第二參數是該域的值;將表單所有的值設定到PostMethod中用方法setRequestBody。另外由於BBS登入成功後會轉向另外一個頁面,但是HttpClient對於要求接受後繼服務的請求,比如POST和PUT,不支援自動轉寄,因此需要自己對頁面轉向做處理。具體的頁面轉向處理請參見下面的"自動轉向"部分。代碼如下:
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);// 執行postMethodint statusCode = httpClient.executeMethod(postMethod);// HttpClient對於要求接受後繼服務的請求,象POST和PUT等不能自動處理轉寄// 301或者302if (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中對自動轉向的定義,主要有兩種:301和302。301表示永久的移走(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以前的版本則必須安裝JSSE。JSSE不同的廠家有不同的實現。下面介紹怎麼使用HttpClient來開啟Https串連。這裡有兩種方法可以開啟https串連,第一種就是得到伺服器頒發的認證,然後匯入到本地的keystore中;另外一種辦法就是通過擴充HttpClient的類來實現自動接受認證。 方法1,取得認證,並匯入本地的keystore:
- 安裝JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳過這一步)。本文以IBM的JSSE為例子說明。先到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工具把剛才匯出的認證倒入本地keystore。Keytool命令在<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,如果用的是IBM的JSSE 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.Sunsecurity.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應該已經安裝正確,但是可能因為你沒有把認證匯入到當前運行JRE的keystore中,請按照前面介紹的步驟來匯入你的認證。
方法2,擴充HttpClient類實現自動接受認證 因為這種方法自動接收所有認證,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統的安全需求。具體的步驟如下:
- 提供一個自訂的socket factory(test.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
處理Proxy 伺服器 HttpClient中使用Proxy 伺服器非常簡單,調用HttpClient中setProxy方法就可以,方法的第一個參數是Proxy 伺服器地址,第二個參數是連接埠號碼。另外HttpClient也支援SOCKS代理。
httpClient.getHostConfiguration().setProxy(hostName,port); |
結論 從上面的介紹中,可以知道HttpClient對http協議支援非常好,使用起來很簡單,版本更新快,功能也很強大,具有足夠的靈活性和擴充性。對於想在Java應用中直接存取http資源的編程人員來說,HttpClient是一個不可多得的好工具。 參考資料
- Commons logging包含了各種各樣的日誌API的實現,讀者可以通過網站http://jakarta.apache.org/commons/logging/得到詳細的內容
- Commons codec包含了一些一般的解碼/編碼演算法。包含了語音編碼、十六進位、Base64和URL編碼等,通過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 Force(IETF)定義的標準,它基於 SSL V3.0,並且在使用的密碼編譯演算法上與其有些許的不同。例如,SSL 使用 Message Authentication Code(MAC)演算法來產生完整性校正值,而 TLS 應用密鑰的 Hashing for Message Authentication Code(HMAC)演算法。
- IBM JSSE提供了SSL(Secure Sockets Layer)和TLS(Transport 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更加詳細的資訊
作者簡介
|
|
|
金髮華是一名工作在 IBM CSDL 的軟體工程師。他喜歡鑽研各種新的技術,在 Java 網路開發和 Web 開發方面頗有經驗。 |
|
|
|
陳樟洪是一位 IBM CSDL 的軟體工程師,目前從事企業電子商務應用的開發。 |
|