httpclient

httpclient

HttpClient 是Apache Jakarta Common下的子項目,可以用來提供高效的、最新的、功能豐富的支持HTTP協定的客戶端編程工具包,並且它支持HTTP協定最新的版本和建議。

基本信息

簡介

HTTP協定可能是現在Internet上使用得最多、最重要的協定了,越來越多的Java應用程式需要直接通過HTTP協定來訪問網路資源。雖然在JDK的javanet包中已經提供了訪問HTTP協定的基本功能,但是對於大部分應用程式來說,JDK庫本身提供的功能還不夠豐富和靈活。HttpClient是ApacheJakartaCommon下的子項目,用來提供高效的、最新的、功能豐富的支持HTTP協定的客戶端編程工具包,並且它支持HTTP協定最新的版本和建議。HttpClient已經套用在很多的項目中,比如ApacheJakarta上很著名的另外兩個開源項目Cactus和HTMLUnit都使用了HttpClient。現在HttpClient最新版本為HttpClient4.5(GA)(2015-09-11)

功能介紹

以下列出的是HttpClient提供的主要的功能,要知道更多詳細的功能可以參見HttpClient的主頁。
(1)實現了所有HTTP的方法(GET,POST,PUT,HEAD等)
(2)支持自動轉向
(3)支持HTTPS協定
(4)支持代理伺服器等

基本功能

(1)GET方法
使用HttpClient需要以下6個步驟:
1.創建HttpClient的實例
2.創建某種連線方法的實例,在這裡是GetMethod。在GetMethod的構造函式中傳入待連線的地址
3.調用第一步中創建好的實例的execute方法來執行第二步中創建好的method實例
4.讀response
5.釋放連線。無論執行方法是否成功,都必須釋放連線
6.對得到後的內容進行處理
根據以上步驟,我們來編寫用GET方法來取得某網頁內容的代碼。
大部分情況下HttpClient默認的構造函式已經足夠使用。HttpClienthttpClient=newDefaultHttpClient();
創建GET方法的實例。在GET方法的構造函式中傳入待連線的地址即可。用GetMethod將會自動處理轉發過程,如果想要把自動處理轉發過程去掉的話,可以調用方法setFollowRedirects(false)。GetMethodgetMethod=newGetMethod(".....");
調用實例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,
newDefaultHttpMethodRetryHandler());
//執行getMethod
intstatusCode=client.executeMethod(getMethod);
if(statusCode!=HttpStatus.SC_OK){
System.err.println("Methodfailed:"+getMethod.getStatusLine());
}
在返回的狀態碼正確後,即可取得內容。取得目標地址的內容有三種方法:第一種,getResponseBody,該方法返回的是目標的二進制的byte流;第二種,getResponseBodyAsString,這個方法返回的是String類型,值得注意的是該方法返回的String的編碼是根據系統默認的編碼方式,所以返回的String值可能編碼類型有誤,在本文的"字元編碼"部分中將對此做詳細介紹;第三種,getResponseBodyAsStream,這個方法對於目標地址中有大量數據需要傳輸是最佳的。在這裡我們使用了最簡單的getResponseBody方法。byte[]responseBody=method.getResponseBody();
釋放連線。無論執行方法是否成功,都必須釋放連線。method.releaseConnection();
處理內容。在這一步中根據你的需要處理內容,在例子中只是簡單的將內容列印到控制台。System.out.println(newString(responseBody));
下面是程式的完整代碼
packagetest;
importjava.io.IOException;
importorg.apache.commons.httpclient.*;
importorg.apache.commons.httpclient.methods.GetMethod;
importorg.apache.commons.httpclient.params.HttpMethodParams;
publicclassGetSample{
publicstaticvoidmain(String[]args){
//構造HttpClient的實例
HttpClienthttpClient=newDefaultHttpClient();
//創建GET方法的實例
GetMethodgetMethod=newGetMethod("...");
//使用系統提供的默認的恢復策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
newDefaultHttpMethodRetryHandler());
try{
//執行getMethod
intstatusCode=httpClient.executeMethod(getMethod);
if(statusCode!=HttpStatus.SC_OK){
System.err.println("Methodfailed:"
+getMethod.getStatusLine());
}
//讀取內容
byte[]responseBody=getMethod.getResponseBody();
//處理內容
System.out.println(newString(responseBody));
}catch(HttpExceptione){
//發生致命的異常,可能是協定不對或者返回的內容有問題
System.out.println("Pleasecheckyourprovidedhttpaddress!");
e.printStackTrace();
}catch(IOExceptione){
//發生網路異常
e.printStackTrace();
}finally{
//釋放連線
getMethod.releaseConnection();
}
}
}
(2)POST方法
根據RFC2616,對POST的解釋如下:POST方法用來向目的伺服器發出請求,要求它接受被附在請求後的實體,並把它當作請求佇列(Request-Line)中請求URI所指定資源的附加新子項。POST被設計成用統一的方法實現下列功能:
對現有資源的注釋(Annotationofexistingresources)
向電子公告欄、新聞組,郵件列表或類似討論組傳送訊息
提交數據塊,如將表單的結果提交給數據處理過程
通過附加操作來擴展資料庫
調用HttpClient中的PostMethod與GetMethod類似,除了設定PostMethod的實例與GetMethod有些不同之外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,並以登錄清華大學BBS為例子進行說明。
構造PostMethod之前的步驟都相同,與GetMethod一樣,構造PostMethod也需要一個URI參數。在創建了PostMethod的實例之後,需要給method實例填充表單的值,在BBS的登錄表單中需要有兩個域,第一個是用戶名(域名叫id),第二個是密碼(域名叫passwd)。表單中的域用類NameValuePair來表示,該類的構造函式第一個參數是域名,第二參數是該域的值;將表單所有的值設定到PostMethod中用方法setRequestBody。另外由於BBS登錄成功後會轉向另外一個頁面,但是HttpClient對於要求接受後繼服務的請求,比如POST和PUT,不支持自動轉發,因此需要自己對頁面轉向做處理。具體的頁面轉向處理請參見下面的"自動轉向"部分。代碼如下:
Stringurl="....";
PostMethodpostMethod=newPostMethod(url);
//填入各個表單域的值
NameValuePair[]data={newNameValuePair("id","yourUserName"),
newNameValuePair("passwd","yourPwd")};
//將表單的值放入postMethod中
postMethod.setRequestBody(data);
//執行postMethod
intstatusCode=httpClient.executeMethod(postMethod);
//HttpClient對於要求接受後繼服務的請求,象POST和PUT等不能自動處理轉發
//301或者302
if(statusCode==HttpStatusSC_MOVED_PERMANENTLY||
statusCode==HttpStatusSC_MOVED_TEMPORARILY){
//從頭中取出轉向的地址
HeaderlocationHeader=postMethod.getResponseHeader("location");
Stringlocation=null;
if(locationHeader!=null){
location=locationHeader.getValue();
System.out.println("Thepagewasredirectedto:"+location);
}else{
System.err.println("Locationfieldvalueisnull.");
}
return;
}

常見問題

下面介紹在使用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中會有<metahttp-equiv="Content-Type"content="text/html;charset=gb2312"/>這樣的標籤;或者在xml中會有<?xmlversion="1.0"encoding="gb2312"?>這樣的標籤,在這些情況下,可能與http頭中返回的編碼信息衝突,需要用戶自己判斷到底那種編碼類型應該是真正的編碼。
自動轉向
根據RFC2616中對自動轉向的定義,主要有兩種:301和302。301表示永久的移走(MovedPermanently),當返回的是301,則表示請求的資源已經被移到一個固定的新地方,任何向該地址發起請求都會被轉到新的地址上。302表示暫時的轉向,比如在伺服器端的servlet程式調用了sendRedirect方法,則在客戶端就會得到一個302的代碼,這時伺服器返回的頭信息中location的值就是sendRedirect轉向的目標地址。
HttpClient支持自動轉向處理,但是象POST和PUT方式這種要求接受後繼服務的請求方式,暫時不支持自動轉向,因此如果碰到POST方式提交後返回的是301或者302的話需要自己處理。就像剛才在POSTMethod中舉的例子:如果想進入登錄BBS後的頁面,必須重新發起登錄的請求,請求的地址可以在頭欄位location中得到。不過需要注意的是,有時候location返回的可能是相對路徑,因此需要對location返回的值做一些處理才可以發起向新地址的請求。
另外除了在頭中包含的信息可能使頁面發生重定向外,在頁面中也有可能會發生頁面的重定向。引起頁面自動轉發的標籤是:<metahttp-equiv="refresh"content="5;url=....">。如果你想在程式中也處理這種情況的話得自己分析頁面來實現轉向。需要注意的是,在上面那個標籤中url的值也可以是一個相對地址,如果是這樣的話,需要對它做一些處理後才可以轉發。
處理HTTPS協定
HttpClient提供了對SSL的支持,在使用SSL之前必須安裝JSSE。在Sun提供的1.4以後的版本中,JSSE已經集成到JDK中,如果你使用的是JDK1.4以前的版本則必須安裝JSSE。JSSE不同的廠家有不同的實現。下面介紹怎么使用HttpClient來打開Https連線。這裡有兩種方法可以打開https連線,第一種就是得到伺服器頒發的證書,然後導入到本地的keystore中;另外一種辦法就是通過擴展HttpClient的類來實現自動接受證書。
方法1,導入證書
安裝JSSE(如果你使用的JDK版本是1.4或者1.4以上就可以跳過這一步)。本文以IBM的JSSE為例子說明。先到IBM網站上下載JSSE的安裝包。然後解壓開之後將ibmjsse.jar包拷貝到<java-home>\lib\ext\目錄下。
取得並且導入證書。證書可以通過IE來獲得:
1.用IE打開需要連線的https網址,會彈出如下對話框:
2.單擊"ViewCertificate",在彈出的對話框中選擇"Details",然後再單擊"CopytoFile",根據提供的嚮導生成待訪問網頁的證書檔案
3.嚮導第一步,歡迎界面,直接單擊"Next",
4.嚮導第二步,選擇導出的檔案格式,默認,單擊"Next",
5.嚮導第三步,輸入導出的檔案名稱,輸入後,單擊"Next",
6.嚮導第四步,單擊"Finish",完成嚮導
7.最後彈出一個對話框,顯示導出成功
用keytool工具把剛才導出的證書倒入本地keystore。Keytool命令在<java-home>\bin\下,打開命令行視窗,併到<java-home>\lib\security\目錄下,運行下面的命令:
keytool-import-noprompt-keystorecacerts-storepasschangeit-aliasyourEntry1-fileyour.cer
其中參數alias後跟的值是當前證書在keystore中的唯一標識符,但是大小寫不區分;參數file後跟的是剛才通過IE導出的證書所在的路徑和檔案名稱;如果你想刪除剛才導入到keystore的證書,可以用命令:
keytool-delete-keystorecacerts-storepasschangeit-aliasyourEntry1
寫程式訪問https地址。如果想測試是否能連上https,只需要稍改一下GetSample例子,把請求的目標變成一個https地址。
GetMethodgetMethod=newGetMethod("yoururl");
運行該程式可能出現的問題:
1.拋出異常javanet.SocketException:AlgorithmSSLnotavailable。出現這個異常可能是因為沒有加JSSEProvider,如果用的是IBM的JSSEProvider,在程式中加入這樣的一行:
if(Security.getProvider("com.ibm.jsse.IBMJSSEProvider")==null)
Security.addProvider(newIBMJSSEProvider());
或者也可以打開<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.拋出異常javanet.SocketException:SSLimplementationnotavailable。出現這個異常可能是你沒有把ibmjsse.jar拷貝到<java-home>\lib\ext\目錄下。
3.拋出異常javax.net.ssl.SSLHandshakeException:unknowncertificate。出現這個異常表明你的JSSE應該已經安裝正確,但是可能因為你沒有把證書導入到當前運行JRE的keystore中,請按照前面介紹的步驟來導入你的證書。
方法2,自動接受證書
因為這種方法自動接收所有證書,因此存在一定的安全問題,所以在使用這種方法前請仔細考慮您的系統的安全需求。具體的步驟如下:
步驟一
提供一個自定義的socketfactory(test.MySecureProtocolSocketFactory)。這個自定義的類必須實現接口org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory,在實現接口的類中調用自定義的X509TrustManager(test.MyX509TrustManager),這兩個類可以在隨本文帶的附屬檔案中得到。
步驟二
創建一個org.apache.commons.httpclient.protocol.Protocol的實例,指定協定名稱和默認的連線埠號Protocolmyhttps=newProtocol("https",newMySecureProtocolSocketFactory(),443);
註冊剛才創建的https協定對象Protocol.registerProtocol("https",myhttps);
結束
然後按照普通編程方式打開https的目標地址,代碼請參見test.NoCertificationHttpsGetSample

代理器

HttpClient中使用代理伺服器非常簡單,調用HttpClient中setProxy方法就可以,方法的第一個參數是代理伺服器地址,第二個參數是連線埠號。另外HttpClient也支持SOCKS代理。
httpClient.getHostConfiguration().setProxy(hostName,port);

認證

HttpClient三種不同的認證方案:Basic,DigestandNTLM.這些方案可用於伺服器或代理對客戶端的認證,簡稱伺服器認證或代理認證。
伺服器認證
HttpClient處理伺服器認證幾乎是透明的,僅需要開發人員提供登錄信息(logincredentials)。登錄信息保存在HttpState類的實例中,可以通過setCredentials(Stringrealm,Credentialscred)和getCredentials(Stringrealm)來獲取或設定。注意,設定對非特定站點訪問所需要的登錄信息,將realm參數置為null.HttpClient內建的自動認證,可以通過HttpMethod類的setDoAuthentication(booleandoAuthentication)方法關閉,而且這次關閉只影響HttpMethod當前的實例。
搶先認證(PreemptiveAuthentication)可以通過下述方法打開.
client.getState().setAuthenticationPreemptive(true);
在這種模式時,HttpClient會主動將basic認證應答信息傳給伺服器,即使在某種情況下伺服器可能返回認證失敗的應答,這樣做主要是為了減少連線的建立。為使每個新建的HttpState實例都實行搶先認證,可以如下設定系統屬性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY,"true");
Httpclient實現的搶先認證遵循rfc2617.
代理認證
除了登錄信息需單獨存放以外,代理認證與伺服器認證幾乎一致。用setProxyCredentials(Stringrealm,Credentialscred)和getProxyCredentials(Stringrealm)設、取登錄信息。
認證方案(authenticationschemes)
Basic
是HTTP中規定最早的也是最兼容(?)的方案,遺憾的是也是最不安全的一個方案,因為它以明碼傳送用戶名和密碼。它要求一個UsernamePasswordCredentials實例,可以指定伺服器端的訪問空間或採用默認的登錄信息。
Digest
是在HTTP1.1中增加的一個方案,雖然不如Basic得到的軟體支持多,但還是有廣泛的使用。Digest方案比Basic方案安全得多,因它根本就不通過網路傳送實際的密碼,傳送的是利用這個密碼對從伺服器傳來的一個隨機數(nonce)的加密串。它要求一個UsernamePasswordCredentials實例,可以指定伺服器端的訪問空間或採用默認的登錄信息。
NTLM認證
這是HttpClient支持的最複雜的認證協定。它M$設計的一個私有協定,沒有公開的規範說明。一開始由於設計的缺陷,NTLM的安全性比Digest差,後來經過一個ServicePack補丁後,安全性則比較Digest高。NTLM需要一個NTCredentials實例.注意,由於NTLM不使用訪問空間(realms)的概念,HttpClient利用伺服器的域名作訪問空間的名字。還需要注意,提供給NTCredentials的用戶名,不要用域名的前綴-如:"adrian"是正確的,而"DOMAIN\adrian"則是錯的.
NTLM認證的工作機制與basic和digest有很大的差別。這些差別一般由HttpClient處理,但理解這些差別有助避免在使用NTLM認證時出現錯誤。
從HttpClientAPI的角度來看,NTLM與其它認證方式一樣的工作,差別是需要提供'NTCredentials'實例而不是'UsernamePasswordCredentials',對NTLM認證,訪問空間是連線到的機器的域名,這對多域名主機會有一些麻煩.只有HttpClient連線中指定的域名才是認證用的域名。建議將realm設為null以使用默認的設定。
NTLM只是認證了一個連線而不是一請求,所以每當一個新的連線建立就要進行一次認證,且在認證的過程中保持連線是非常重要的。因此,NTLM不能同時用於代理認證和伺服器認證,也不能用於http1.0連線或伺服器不支持持久連線的情況。

相關詞條

相關搜尋

熱門詞條

聯絡我們