HttpClient 簡(jiǎn)介
HttpClient 簡(jiǎn)介HttpClient 是 Apache Jakarta Common 下的子項(xiàng)目,可以用來(lái)提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 H
HttpClient 簡(jiǎn)介
HttpClient 是 Apache Jakarta Common 下的子項(xiàng)目,可以用來(lái)提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。本文首先介紹
HTTPClient ,然后根據(jù)作者實(shí)際工作經(jīng)驗(yàn)給出了一些常見(jiàn)問(wèn)題的解決方法。HTTP 協(xié)議可能是現(xiàn)在 Internet 上使用得最多、最重要的協(xié)議了,越來(lái)越多的 Java 應(yīng)用程序需要直接通過(guò) HTTP 協(xié)議來(lái)訪問(wèn)網(wǎng)絡(luò)資源。雖然在 JDK 的 java.net 包中已經(jīng)提供了訪問(wèn) HTTP 協(xié)議的基本功能,但是對(duì)于大部分應(yīng)用程序來(lái)說(shuō),JDK 庫(kù)本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項(xiàng)目,用來(lái)提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。HttpClient 已經(jīng)應(yīng)用在很多的項(xiàng)目中,比如 Apache Jakarta 上很著名的另外兩個(gè)開(kāi)源項(xiàng)目 Cactus 和 HTMLUnit 都使用了 HttpClient ?,F(xiàn)在HttpClient 最新版本為 HttpClient 4.0-beta2
2.HttpClient 功能介紹
以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細(xì)的功能可以參見(jiàn) HttpClient 的主頁(yè)。
(1)實(shí)現(xiàn)了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
(2)支持自動(dòng)轉(zhuǎn)向
(3)支持 HTTPS 協(xié)議
(4)支持代理服務(wù)器等
3.HttpClient 基本功能的使用
(1) 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 方法來(lái)執(zhí)行第二步中創(chuàng)建好的 method 實(shí)例
4. 讀 response
5. 釋放連接。無(wú)論執(zhí)行方法是否成功,都必須釋放連接
6. 對(duì)得到后的內(nèi)容進(jìn)行處理
根據(jù)以上步驟,我們來(lái)編寫(xiě)用GET 方法來(lái)取得某網(wǎng)頁(yè)內(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ā)過(guò)程,如果想要把自動(dòng)處理轉(zhuǎn)發(fā)過(guò)程去掉 的話,可以調(diào)用方法setFollowRedirects(false)。
GetMethod getMethod = new GetMethod(".....");
調(diào)用實(shí)例httpClient 的executeMethod 方法來(lái)執(zhí)行g(shù)etMethod 。由于是執(zhí)行在網(wǎng)絡(luò)上的程序,在運(yùn)行 executeMethod 方法的時(shí)候,需要處理兩個(gè)異常,分別是HttpException 和IOException 。引起第一種異常的原因主要可能是 在構(gòu)造getMethod 的時(shí)候傳入的協(xié)議不對(duì),比如不小心將"http" 寫(xiě)成"htp" ,或者服務(wù)器端返回的內(nèi)容不正常等,并且該異常發(fā)生是不可恢復(fù) 的;第二種異常一般是由于網(wǎng)絡(luò)原因引起的異常,對(duì)于這種異常 (IOException ),HttpClient 會(huì)根據(jù)你指定的恢復(fù)策略自動(dòng)試著重新執(zhí)行executeMethod 方法。HttpClient 的恢復(fù) 策略可以自定義(通過(guò)實(shí)現(xiàn)接口HttpMethodRetryHandler 來(lái)實(shí)現(xiàn))。通過(guò)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)證或者頁(yè)面發(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(); 釋放連接。無(wú)論執(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("...");
//使用系統(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)容有問(wèn)題
System.out.println("Please check your provided http address!"); e.printStackTrace();
} catch (IOException e) {
//發(fā)生網(wǎng)絡(luò)異常
e.printStackTrace();
} finally {
//釋放連接
getMethod.releaseConnection();
}
}
}
(2)POST 方法
根據(jù)RFC2616,對(duì)POST 的解釋如下:POST 方法用來(lái)向目的服務(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ù)處理過(guò)程
通過(guò)附加操作來(lái)擴(kuò)展數(shù)據(jù)庫(kù)
調(diào)用HttpClient 中的PostMethod 與GetMethod 類似,除了設(shè)置
,PostMethod 的實(shí)例與GetMethod 有些不同之 外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod 相同的步驟,只說(shuō)明與上面不同的地方,并以登錄清華大學(xué)BBS 為例子進(jìn)行說(shuō)明。
構(gòu)造PostMethod 之前的步驟都相同,與GetMethod 一樣,構(gòu)造PostMethod 也需要一個(gè)URI 參數(shù)。在創(chuàng)建了 PostMethod 的實(shí)例之后,需要給method 實(shí)例填充表單的值,在BBS 的登錄表單中需要有兩個(gè)域,第一個(gè)是用戶名(域名叫id ),第二個(gè)是密碼 (域名叫
passwd )。表單中的域用類NameValuePair 來(lái)表示,該類的構(gòu)造函數(shù)第一個(gè)參數(shù)是域名,第二參數(shù)是該域的值;將表單所有的值設(shè)置到
PostMethod 中用方法setRequestBody 。另外由于BBS 登錄成功后會(huì)轉(zhuǎn)向另外一個(gè)頁(yè)面,但是HttpClient 對(duì)于要求接受后繼服 務(wù)的請(qǐng)求,比如POST 和PUT ,不支持自動(dòng)轉(zhuǎn)發(fā),因此需要自己對(duì)頁(yè)面轉(zhuǎn)向做處理。具體的頁(yè)面轉(zhuǎn)向處理請(qǐng)參見(jiàn)下面的" 自動(dòng)轉(zhuǎn)向" 部分。代碼如下: String url = "....";
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;
}
[編輯本段]4 使用HttpClient 過(guò)程中常見(jiàn)的一些問(wèn)題 下面介紹在使用HttpClient 過(guò)程中常見(jiàn)的一些問(wèn)題。
字符編碼
某目標(biāo)頁(yè)的編碼可能出現(xiàn)在兩個(gè)地方,第一個(gè)地方是服務(wù)器返回的http 頭中,另外一個(gè)地方是得到的html/xml頁(yè)面中。
在http 頭的Content-Type 字段可能會(huì)包含字符編碼信息。例如可能返回的頭會(huì)包含這樣子的信息:Content-Type: text/html;
charset=UTF-8。這個(gè)頭信息表明該頁(yè)的編碼是UTF-8,但是服務(wù)器返回的頭信息未必與內(nèi)容能匹配上。比如對(duì)于一些雙字節(jié)語(yǔ)言國(guó)家,可能服務(wù) 器返回的編碼類型是UTF-8,但真正的內(nèi)容卻不是UTF-8編碼的,因此需要在另外的地方去得到頁(yè)面的編碼信息;但是如果服務(wù)器返回的編碼不是UTF- 8,而是具體的一些編碼,比如gb2312等,那服務(wù)器返回的可能是正確的編碼信息。通過(guò)method 對(duì)象的getResponseCharSet()方 法就可以得到http 頭中的編碼信息。
,對(duì)于象xml 或者h(yuǎn)tml 這樣的文件,允許作者在頁(yè)面中直接指定編碼類型。比如在html 中會(huì)有
content="text/html; charset=gb2312"/>這樣的標(biāo)簽;或者在xml 中會(huì)有這樣的標(biāo)簽,在這些情況下,可能與http 頭中返回的編碼信息沖突,需要用戶自己判斷到底那種編碼類型應(yīng)該 是真正的編碼。
自動(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ù)器返回的頭信息中 location 的值就是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 后的頁(yè)面,必須重新發(fā)起登錄的請(qǐng)求, 請(qǐng)求的地址可以在頭字段location 中得到。不過(guò)需要注意的是,有時(shí)候location 返回的可能是相對(duì)路徑,因此需要對(duì)location 返回的值做 一些處理才可以發(fā)起向新地址的請(qǐng)求。
另外除了在頭中包含的信息可能使頁(yè)面發(fā)生重定向外,在頁(yè)面中也有可能會(huì)發(fā)生頁(yè)面的重定向。引起頁(yè)面自動(dòng)轉(zhuǎn)發(fā)的標(biāo)簽是:。如果你想在程序中也處理這種情況的話得自己分析頁(yè)面來(lái)實(shí)現(xiàn)轉(zhuǎn)向。需要注意的是,在上面那個(gè)標(biāo)簽中url 的值也可以是一個(gè)相對(duì) 地址,如果是這樣的話,需要對(duì)它做一些處理后才可以轉(zhuǎn)發(fā)。
,處理HTTPS 協(xié)議
HttpClient 提供了對(duì)SSL 的支持,在使用SSL 之前必須安裝JSSE 。在Sun 提供的1.4以后的版本中,JSSE 已經(jīng)集成到JDK 中,如 果你使用的是JDK1.4以前的版本則必須安裝JSSE 。JSSE 不同的廠家有不同的實(shí)現(xiàn)。下面介紹怎么使用HttpClient 來(lái)打開(kāi)Https 連接。 這里有兩種方法可以打開(kāi)https 連接,第一種就是得到服務(wù)器頒發(fā)的證書(shū),然后導(dǎo)入到本地的keystore 中;另外一種辦法就是通過(guò)擴(kuò)展 HttpClient 的類來(lái)實(shí)現(xiàn)自動(dòng)接受證書(shū)。
方法1,取得證書(shū),并導(dǎo)入本地的keystore :
安裝JSSE (如果你使用的JDK 版本是1.4或者1.4以上就可以跳過(guò)這一步)。本文以IBM 的JSSE 為例子說(shuō)明。先到IBM 網(wǎng)站上下載JSSE 的安裝包。然后解 壓開(kāi)之后將ibmjsse.jar 包拷貝到 home>libext目錄下。 取得并且導(dǎo)入證書(shū)。證書(shū)可以通過(guò)IE 來(lái)獲得: 1. 用IE 打開(kāi)需要連接的https 網(wǎng)址,會(huì)彈出如下對(duì)話框: 2. 單擊"View Certificate",在彈出的對(duì)話框中選擇"Details" ,然后再單擊"Copy to File",根據(jù)提供的向?qū)纱L問(wèn)網(wǎng)頁(yè)的證書(shū)文件 3. 向?qū)У谝徊?,歡迎界面,直接單擊"Next" , 4. 向?qū)У诙剑x擇導(dǎo)出的文件格式,默認(rèn),單擊"Next" , 5. 向?qū)У谌剑斎雽?dǎo)出的文件名,輸入后,單擊"Next" , 6. 向?qū)У谒牟?,單?Finish" ,完成向?qū)?/p> 7. 最后彈出一個(gè)對(duì)話框,顯示導(dǎo)出成功 用keytool 工具把剛才導(dǎo)出的證書(shū)倒入本地keystore 。Keytool 命令在 home>libsecurity目錄下,運(yùn)行下面的命令: keytool -import -noprompt -keystore cacerts -storepass changeit -alias yourEntry1 -file your.cer 其中參數(shù)alias 后跟的值是當(dāng)前證書(shū)在keystore 中的唯一標(biāo)識(shí)符,但是大小寫(xiě)不區(qū)分;參數(shù)file 后跟的是剛才通過(guò)IE 導(dǎo)出的證書(shū)所在的路徑和文件名;如果你想刪除剛才導(dǎo)入到keystore 的證書(shū),可以用命令: keytool -delete -keystore cacerts -storepass changeit -alias yourEntry1 寫(xiě)程序訪問(wèn)https 地址。如果想測(cè)試是否能連上https ,只需要稍改一下GetSample 例子,把請(qǐng)求的目標(biāo)變成一個(gè)https 地址。 GetMethod getMethod = new GetMethod("your url"); 運(yùn)行該程序可能出現(xiàn)的問(wèn)題: 1. 拋出異常java.net.SocketException: Algorithm SSL not available 。出現(xiàn)這個(gè)異??赡苁且?yàn)闆](méi)有加JSSEProvider ,如果用的是IBM 的JSSE Provider,在程序中加入這樣的一行: if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider") == null) Security.addProvider(new IBMJSSEProvider()); 或者也可以打開(kāi) 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)這個(gè)異??赡苁悄銢](méi)有把ibmjsse.jar 拷貝到 3. 拋出異常javax.net.ssl.SSLHandshakeException: unknown certificate 。出現(xiàn)這個(gè)異常表明你的JSSE 應(yīng)該已經(jīng)安裝正確,但是可能因?yàn)槟銢](méi)有把證書(shū)導(dǎo)入到當(dāng)前運(yùn)行JRE 的keystore 中,請(qǐng)按照前 面介紹的步驟來(lái)導(dǎo)入你的證書(shū)。 方法2,擴(kuò)展HttpClient 類實(shí)現(xiàn)自動(dòng)接受證書(shū)