httpclient
HttpClient 是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。本文首先介紹 HTTPClient,然后根據(jù)作者實際工作經(jīng)驗給出了一些常見問題的解決方法。 HttpClient簡介 HTTP 協(xié)議可能是現(xiàn)在 Internet 上使用得最多、最重要的協(xié)議了,越來越多的 Java 應用程序需要直接通過 HTTP 協(xié)議來訪問網(wǎng)絡資源。雖然在 JDK 的 java.net 包中已經(jīng)提供了訪問 HTTP 協(xié)議的基本功能,但是對于大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。HttpClient 已經(jīng)應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發(fā)布的 3.0 RC4 。
以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。
- 實現(xiàn)了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
- 支持自動轉(zhuǎn)向
- 支持 HTTPS 協(xié)議
- 支持代理服務器等
下面將逐一介紹怎樣使用這些功能。首先,我們必須安裝好 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 中
GET 方法
使用 HttpClient 需要以下 6 個步驟:
3. 調(diào)用第一步中創(chuàng)建好的實例的 execute 方法來執(zhí)行第二步中創(chuàng)建好的 method 實例
4. 讀 response
5. 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接
根據(jù)以上步驟,我們來編寫用GET方法來取得某網(wǎng)頁內(nèi)容的代碼。
HttpClient httpClient = new HttpClient();
GetMethod getMethod = new GetMethod("http://www.ibm.com/");
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());
}
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的實例
HttpClient httpClient = new HttpClient();
//創(chuàng)建GET方法的實例
GetMethod getMethod = new GetMethod("http://www.ibm.com");
//使用系統(tǒng)提供的默認的恢復策略
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é)議不對或者返回的內(nèi)容有問題
System.out.println("Please check your provided http address!");
e.printStackTrace();
} catch (IOException e) {
//發(fā)生網(wǎng)絡異常
e.printStackTrace();
} finally {
//釋放連接
getMethod.releaseConnection();
}
}
}
POST方法
根據(jù)RFC2616,對POST的解釋如下:POST方法用來向目的服務器發(fā)出請求,要求它接受被附在請求后的實體,并把它當作請求隊列(Request-Line)中請求URI所指定資源的附加新子項。POST被設計成用統(tǒng)一的方法實現(xiàn)下列功能:
- 對現(xiàn)有資源的注釋(Annotation of existing resources)
- 向電子公告欄、新聞組,郵件列表或類似討論組發(fā)送消息
- 提交數(shù)據(jù)塊,如將表單的結(jié)果提交給數(shù)據(jù)處理過程
- 通過附加操作來擴展數(shù)據(jù)庫
PostMethod postMethod = new PostMethod(url);
// 填入各個表單域的值
NameValuePair[] data = { new NameValuePair("id", "youUserName"),
new NameValuePair("passwd", "yourPwd") };
// 將表單的值放入postMethod中
postMethod.setRequestBody(data);
// 執(zhí)行postMethod
int statusCode = httpClient.executeMethod(postMethod);
// HttpClient對于要求接受后繼服務的請求,象POST和PUT等不能自動處理轉(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;
}
字符編碼
某目標頁的編碼可能出現(xiàn)在兩個地方,第一個地方是服務器返回的http頭中,另外一個地方是得到的html/xml頁面中。
根據(jù)RFC2616中對自動轉(zhuǎn)向的定義,主要有兩種:301和302。301表示永久的移走(Moved Permanently),當返回的是301,則表示請求的資源已經(jīng)被移到一個固定的新地方,任何向該地址發(fā)起請求都會被轉(zhuǎn)到新的地址上。302表示暫時的轉(zhuǎn)向,比如在服務器端的servlet程序調(diào)用了sendRedirect方法,則在客戶端就會得到一個302的代碼,這時服務器返回的頭信息中l(wèi)ocation的值就是sendRedirect轉(zhuǎn)向的目標地址。
HttpClient支持自動轉(zhuǎn)向處理,但是象POST和PUT方式這種要求接受后繼服務的請求方式,暫時不支持自動轉(zhuǎn)向,因此如果碰到POST方式提交后返回的是301或者302的話需要自己處理。就像剛才在POSTMethod中舉的例子:如果想進入登錄BBS后的頁面,必須重新發(fā)起登錄的請求,請求的地址可以在頭字段location中得到。不過需要注意的是,有時候location返回的可能是相對路徑,因此需要對location返回的值做一些處理才可以發(fā)起向新地址的請求。
另外除了在頭中包含的信息可能使頁面發(fā)生重定向外,在頁面中也有可能會發(fā)生頁面的重定向。引起頁面自動轉(zhuǎn)發(fā)的標簽是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也處理這種情況的話得自己分析頁面來實現(xiàn)轉(zhuǎn)向。需要注意的是,在上面那個標簽中url的值也可以是一個相對地址,如果是這樣的話,需要對它做一些處理后才可以轉(zhuǎn)發(fā)
HttpClient提供了對SSL的支持,在使用SSL之前必須安裝JSSE。在Sun提供的1.4以后的版本中,JSSE已經(jīng)集成到JDK中,如果你使用的是JDK1.4以前的版本則必須安裝JSSE。JSSE不同的廠家有不同的實現(xiàn)。下面介紹怎么使用HttpClient來打開Https連接。這里有兩種方法可以打開https連接,第一種就是得到服務器頒發(fā)的證書,然后導入到本地的keystore中;另外一種辦法就是通過擴展HttpClient的類來實現(xiàn)自動接受證書。
方法1,取得證書,并導入本地的keystore:
- 安裝JSSE (如果你使用的JDK版本是1.4或者1.4以上就可以跳過這一步)。本文以IBM的JSSE為例子說明。先到IBM網(wǎng)站上下載JSSE的安裝包。然后解壓開之后將ibmjsse.jar包拷貝到<java-home>\lib\ext\目錄下。
- 取得并且導入證書。證書可以通過IE來獲得:
1. 用IE打開需要連接的https網(wǎng)址,會彈出如下對話框:
2. 單擊"View Certificate",在彈出的對話框中選擇"Details",然后再單擊"Copy to File",根據(jù)提供的向?qū)纱L問網(wǎng)頁的證書文件
3. 向?qū)У谝徊剑瑲g迎界面,直接單擊"Next",
4. 向?qū)У诙剑x擇導出的文件格式,默認,單擊"Next",
5. 向?qū)У谌剑斎雽С龅奈募斎牒螅瑔螕?Next",
6. 向?qū)У谒牟剑瑔螕?Finish",完成向?qū)?/font>
7. 最后彈出一個對話框,顯示導出成功
-
用keytool工具把剛才導出的證書倒入本地keystore。Keytool命令在<java-home>\bin\下,打開命令行窗口,并到<java-home>\lib\security\目錄下,運行下面的命令:
- keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer
其中參數(shù)alias后跟的值是當前證書在keystore中的唯一標識符,但是大小寫不區(qū)分;參數(shù)file后跟的是剛才通過IE導出的證書所在的路徑和文件名;如果你想刪除剛才導入到keystore的證書,可以用命令:
- keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1
- 寫程序訪問https地址。如果想測試是否能連上https,只需要稍改一下GetSample例子,把請求的目標變成一個https地址。
GetMethod getMethod = new GetMethod(https://www.yourdomain.com);
運行該程序可能出現(xiàn)的問題:
-
1. 拋出異常java.net.SocketException: Algorithm SSL not available。出現(xiàn)這個異常可能是因為沒有加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.Sun
security.provider.2=com.ibm.crypto.provider.IBMJCE
后面加入security.provider.3=com.ibm.jsse.IBMJSSEProvider
2. 拋出異常java.net.SocketException: SSL implementation not available。出現(xiàn)這個異常可能是你沒有把ibmjsse.jar拷貝到<java-home>\lib\ext\目錄下。
3. 拋出異常javax.net.ssl.SSLHandshakeException: unknown certificate。出現(xiàn)這個異常表明你的JSSE應該已經(jīng)安裝正確,但是可能因為你沒有把證書導入到當前運行JRE的keystore中,請按照前面介紹的步驟來導入你的證書。
方法2,擴展HttpClient類實現(xiàn)自動接受證書
因為這種方法自動接收所有證書,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統(tǒng)的安全需求。具體的步驟如下:
- 提供一個自定義的socket factory(test.MySecureProtocolSocketFactory)。這個自定義的類必須實現(xiàn)接口org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在實現(xiàn)接口的類中調(diào)用自定義的X509TrustManager(test.MyX509TrustManager),這兩個類可以在隨本文帶的附件中得到
- 創(chuàng)建一個org.apache.commons.httpclient.protocol.Protocol的實例,指定協(xié)議名稱和默認的端口號
Protocol myhttps = new Protocol("https", new MySecureProtocolSocketFactory (), 443);
- 注冊剛才創(chuàng)建的https協(xié)議對象
Protocol.registerProtocol("https ", myhttps); -
- 然后按照普通編程方式打開https的目標地址,代碼請參見test.NoCertificationHttpsGetSample
HttpClient中使用代理服務器非常簡單,調(diào)用HttpClient中setProxy方法就可以,方法的第一個參數(shù)是代理服務器地址,第二個參數(shù)是端口號。另外HttpClient也支持SOCKS代理。
httpClient.getHostConfiguration().setProxy(hostName,port);
結(jié)論
從上面的介紹中,可以知道HttpClient對http協(xié)議支持非常好,使用起來很簡單,版本更新快,功能也很強大,具有足夠的靈活性和擴展性。對于想在Java應用中直接訪問http資源的編程人員來說,HttpClient是一個不可多得的好工具。