一、HttpClient入門
HttpClient 是 Apache Jakarta Common 下的子項目,可以用來提供高效的、最新的、功能豐富的支持 HTTP 協(xié)議的客戶端編程工具包,并且它支持 HTTP 協(xié)議最新的版本和建議。本文首先介紹 HTTPClient,然后根據(jù)作者實(shí)際工作經(jīng)驗給出了一些常見問題的解決方法。 HttpClient 基本功能的使用 (一)、GET 方法 使用 HttpClient 需要以下 6 個步驟: 1. 創(chuàng)建 HttpClient 的實(shí)例 2. 創(chuàng)建某種連接方法的實(shí)例,在這里是 GetMethod。在 GetMethod 的構(gòu)造函數(shù)中傳入待連接的地址 3. 調(diào)用第一步中創(chuàng)建好的實(shí)例的 execute 方法來執(zhí)行第二步中創(chuàng)建好的 method 實(shí)例 4. 讀 response 5. 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接 6. 對得到后的內(nèi)容進(jìn)行處理 根據(jù)以上步驟,我們來編寫用GET方法來取得某網(wǎng)頁內(nèi)容的代碼。 • 大部分情況下 HttpClient 默認(rèn)的構(gòu)造函數(shù)已經(jīng)足夠使用。 HttpClient httpClient = new HttpClient(); • 創(chuàng)建GET方法的實(shí)例。在GET方法的構(gòu)造函數(shù)中傳入待連接的地址即可。用GetMethod將會自動處理轉(zhuǎn)發(fā)過程,如果想要把自動處理轉(zhuǎn)發(fā)過程去掉的話,可以調(diào)用方法setFollowRedirects(false)。 GetMethod getMethod = new GetMethod("http://www.ibm.com/"); • 調(diào)用實(shí)例httpClient的executeMethod方法來執(zhí)行g(shù)etMethod。由于是執(zhí)行在網(wǎng)絡(luò)上的程序,在運(yùn)行executeMethod方法的時候,需要處理兩個異常,分別是HttpException和IOException。引起第一種異常的原因主要可能是在構(gòu)造getMethod的時候傳入的協(xié)議不對,比如不小心將"http"寫成"htp",或者服務(wù)器端返回的內(nèi)容不正常等,并且該異常發(fā)生是不可恢復(fù)的;第二種異常一般是由于網(wǎng)絡(luò)原因引起的異常,對于這種異常 (IOException),HttpClient會根據(jù)你指定的恢復(fù)策略自動試著重新執(zhí)行executeMethod方法。HttpClient的恢復(fù)策略可以自定義(通過實(shí)現(xiàn)接口HttpMethodRetryHandler來實(shí)現(xiàn))。通過httpClient的方法setParameter設(shè)置你實(shí)現(xiàn)的恢復(fù)策略,本文中使用的是系統(tǒng)提供的默認(rèn)恢復(fù)策略,該策略在碰到第二類異常的時候?qū)⒆詣又卦?次。executeMethod返回值是一個整數(shù),表示了執(zhí)行該方法后服務(wù)器返回的狀態(tài)碼,該狀態(tài)碼能表示出該方法執(zhí)行是否成功、需要認(rèn)證或者頁面發(fā)生了跳轉(zhuǎn)(默認(rèn)狀態(tài)下GetMethod的實(shí)例是自動處理跳轉(zhuǎn)的)等。 //設(shè)置成了默認(rèn)的恢復(fù)策略,在發(fā)生異常時候?qū)⒆詣又卦?次,在這里你也可以設(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,這個方法返回的是String類型,值得注意的是該方法返回的String的編碼是根據(jù)系統(tǒng)默認(rèn)的編碼方式,所以返回的String值可能編碼類型有誤,在本文的"字符編碼"部分中將對此做詳細(xì)介紹;第三種,getResponseBodyAsStream,這個方法對于目標(biāo)地址中有大量數(shù)據(jù)需要傳輸是最佳的。在這里我們使用了最簡單的getResponseBody方法。 byte[] responseBody = method.getResponseBody(); • 釋放連接。無論執(zhí)行方法是否成功,都必須釋放連接。 method.releaseConnection(); • 處理內(nèi)容。在這一步中根據(jù)你的需要處理內(nèi)容,在例子中只是簡單的將內(nè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("http://www.ibm.com"); //使用系統(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é)議不對或者返回的內(nèi)容有問題 System.out.println("Please check your provided http address!"); e.printStackTrace(); } catch (IOException e) { //發(fā)生網(wǎng)絡(luò)異常 e.printStackTrace(); } finally { //釋放連接 getMethod.releaseConnection(); } } } (二)、POST方法 根據(jù)RFC2616,對POST的解釋如下:POST方法用來向目的服務(wù)器發(fā)出請求,要求它接受被附在請求后的實(shí)體,并把它當(dāng)作請求隊列(Request-Line)中請求URI所指定資源的附加新子項。POST被設(shè)計成用統(tǒng)一的方法實(shí)現(xiàn)下列功能: • 對現(xiàn)有資源的注釋(Annotation of existing resources) • 向電子公告欄、新聞組,郵件列表或類似討論組發(fā)送消息 • 提交數(shù)據(jù)塊,如將表單的結(jié)果提交給數(shù)據(jù)處理過程 • 通過附加操作來擴(kuò)展數(shù)據(jù)庫 調(diào)用HttpClient中的PostMethod與GetMethod類似,除了設(shè)置PostMethod的實(shí)例與GetMethod有些不同之外,剩下的步驟都差不多。在下面的例子中,省去了與GetMethod相同的步驟,只說明與上面不同的地方,并以登錄清華大學(xué)BBS為例子進(jìn)行說明。 • 構(gòu)造PostMethod之前的步驟都相同,與GetMethod一樣,構(gòu)造PostMethod也需要一個URI參數(shù),在本例中,登錄的地址是http://www./bbslogin2.php。在創(chuàng)建了PostMethod的實(shí)例之后,需要給method實(shí)例填充表單的值,在BBS的登錄表單中需要有兩個域,第一個是用戶名(域名叫id),第二個是密碼(域名叫passwd)。表單中的域用類NameValuePair來表示,該類的構(gòu)造函數(shù)第一個參數(shù)是域名,第二參數(shù)是該域的值;將表單所有的值設(shè)置到PostMethod中用方法setRequestBody。另外由于BBS登錄成功后會轉(zhuǎn)向另外一個頁面,但是HttpClient對于要求接受后繼服務(wù)的請求,比如POST和PUT,不支持自動轉(zhuǎn)發(fā),因此需要自己對頁面轉(zhuǎn)向做處理。具體的頁面轉(zhuǎn)向處理請參見下面的"自動轉(zhuǎn)向"部分。代碼如下: String url = "http://www./bbslogin2.php"; 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對于要求接受后繼服務(wù)的請求,象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; } 二、HttpClient一些問題
下面介紹在使用HttpClient過程中常見的一些問題。 (一)、字符編碼 某目標(biāo)頁的編碼可能出現(xiàn)在兩個地方,第一個地方是服務(wù)器返回的http頭中,另外一個地方是得到的html/xml頁面中。 • 在http頭的Content-Type字段可能會包含字符編碼信息。例如可能返回的頭會包含這樣子的信息:Content-Type: text/html; charset=UTF-8。這個頭信息表明該頁的編碼是UTF-8,但是服務(wù)器返回的頭信息未必與內(nèi)容能匹配上。比如對于一些雙字節(jié)語言國家,可能服務(wù)器返回的編碼類型是UTF-8,但真正的內(nèi)容卻不是UTF-8編碼的,因此需要在另外的地方去得到頁面的編碼信息;但是如果服務(wù)器返回的編碼不是UTF-8,而是具體的一些編碼,比如gb2312等,那服務(wù)器返回的可能是正確的編碼信息。通過method對象的getResponseCharSet()方法就可以得到http頭中的編碼信息。 • 對于象xml或者h(yuǎn)tml這樣的文件,允許作者在頁面中直接指定編碼類型。比如在html中會有<meta http-equiv="Content-Type" content="text/html; charset=gb2312"/>這樣的標(biāo)簽;或者在xml中會有<?xml version="1.0" encoding="gb2312"?>這樣的標(biāo)簽,在這些情況下,可能與http頭中返回的編碼信息沖突,需要用戶自己判斷到底那種編碼類型應(yīng)該是真正的編碼。 編碼問題解決 private static final String CONTENT_CHARSET = "GBK";// httpclient讀取內(nèi)容時使用的字符集 HttpClient client = new HttpClient(); client.getParams().setParameter( HttpMethodParams.HTTP_CONTENT_CHARSET, CONTENT_CHARSET); 字符串編碼改變的方法:String target = new String(orig.getBytes("ISO-8859-1"),"GBK");
(二)、認(rèn)證 HttpClient三種不同的認(rèn)證方案: Basic, Digest and NTLM. 這些方案可用于服務(wù)器或代理對客戶端的認(rèn)證,簡稱服務(wù)器認(rèn)證或代理認(rèn)證。 1) 服務(wù)器認(rèn)證(Server Authentication) HttpClient處理服務(wù)器認(rèn)證幾乎是透明的,僅需要開發(fā)人員提供登錄信息(login credentials)。登錄信息保存在HttpState類的實(shí)例中,可以通過 setCredentials(String realm, Credentials cred)和getCredentials(String realm)來獲取或設(shè)置。注意,設(shè)定對非特定站點(diǎn)訪問所需要的登錄信息,將realm參數(shù)置為null. HttpClient內(nèi)建的自動認(rèn)證,可以通過HttpMethod類的setDoAuthentication(boolean doAuthentication)方法關(guān)閉,而且這次關(guān)閉只影響HttpMethod當(dāng)前的實(shí)例。 搶先認(rèn)證(Preemptive Authentication)可以通過下述方法打開. client.getState().setAuthenticationPreemptive(true); 在這種模式時,HttpClient會主動將basic認(rèn)證應(yīng)答信息傳給服務(wù)器,即使在某種情況下服務(wù)器可能返回認(rèn)證失敗的應(yīng)答,這樣做主要是為了減少連接的建立。為使每個新建的 HttpState實(shí)例都實(shí)行搶先認(rèn)證,可以如下設(shè)置系統(tǒng)屬性。 setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true"); Httpclient實(shí)現(xiàn)的搶先認(rèn)證遵循rfc2617. 2)代理認(rèn)證(proxy authentication) 除了登錄信息需單獨(dú)存放以外,代理認(rèn)證與服務(wù)器認(rèn)證幾乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)設(shè)、取登錄信息。 3) 認(rèn)證方案(authentication schemes) Basic 是HTTP中規(guī)定最早的也是最兼容(?)的方案,遺憾的是也是最不安全的一個方案,因為它以明碼傳送用戶名和密碼。它要求一個UsernamePasswordCredentials實(shí)例,可以指定服務(wù)器端的訪問空間或采用默認(rèn)的登錄信息。 Digest 是在HTTP1.1中增加的一個方案,雖然不如Basic得到的軟件支持多,但還是有廣泛的使用。Digest方案比Basic方案安全得多,因它根本就不通過網(wǎng)絡(luò)傳送實(shí)際的密碼,傳送的是利用這個密碼對從服務(wù)器傳來的一個隨機(jī)數(shù)(nonce)的加密串。它要求一個UsernamePasswordCredentials實(shí)例,可以指定服務(wù)器端的訪問空間或采用默認(rèn)的登錄信息。 NTLM 這是HttpClient支持的最復(fù)雜的認(rèn)證協(xié)議。它M$設(shè)計的一個私有協(xié)議,沒有公開的規(guī)范說明。一開始由于設(shè)計的缺陷,NTLM的安全性比Digest差,后來經(jīng)過一個ServicePack補(bǔ)丁后,安全性則比較Digest高。NTLM需要一個NTCredentials實(shí)例. 注意,由于NTLM不使用訪問空間(realms)的概念,HttpClient利用服務(wù)器的域名作訪問空間的名字。還需要注意,提供給NTCredentials的用戶名,不要用域名的前綴 - 如: "adrian" 是正確的,而 "DOMAIN\adrian" 則是錯的. NTLM認(rèn)證的工作機(jī)制與basic和digest有很大的差別。這些差別一般由HttpClient處理,但理解這些差別有助避免在使用NTLM認(rèn)證時出現(xiàn)錯誤。 從HttpClientAPI的角度來看,NTLM與其它認(rèn)證方式一樣的工作,差別是需要提供'NTCredentials'實(shí)例而不是'UsernamePasswordCredentials'(其實(shí),前者只是擴(kuò)展了后者)對NTLM認(rèn)證,訪問空間是連接到的機(jī)器的域名,這對多域名主機(jī)會有一些麻煩.只有HttpClient連接中指定的域名才是認(rèn)證用的域名。建議將realm設(shè)為null以使用默認(rèn)的設(shè)置。NTLM只是認(rèn)證了一個連接而不是一請求,所以每當(dāng)一個新的連接建立就要進(jìn)行一次認(rèn)證,且在認(rèn)證的過程中保持連接是非常重要的。 因此,NTLM不能同時用于代理認(rèn)證和服務(wù)器認(rèn)證,也不能用于http1.0連接或服務(wù)器不支持持久連接的情況。 (三)、重定向
由于技術(shù)限制,以及為保證2.0發(fā)布版API的穩(wěn)定,HttpClient還不能自動處重定向,但對重定向到同一主機(jī)、同一端口且采用同一協(xié)議的情況HttpClient可以支持。不能自動的處理的情況,包括需要人工交互的情況,或超出httpclient的能力。 當(dāng)服務(wù)器重定向指令指到不同的主機(jī)時,HttpClient只是簡單地將重定向狀態(tài)碼作為應(yīng)答狀態(tài)。所有的300到399(包含兩端)的返回碼,都表示是重定向應(yīng)答。常見的有: 301 永久移動. HttpStatus.SC_MOVED_PERMANENTLY 302 臨時移動. HttpStatus.SC_MOVED_TEMPORARILY 303 See Other. HttpStatus.SC_SEE_OTHER 307 臨時重定向. HttpStatus.SC_TEMPORARY_REDIRECT 當(dāng)收到簡單的重定向時,程序應(yīng)從HttpMethod對象中抽取新的URL并將其下載。另外,限制一下重定向次數(shù)是個好的主意,這可以避免遞歸循環(huán)。新的URL可以從頭字段Location中抽取,如下: String redirectLocation; Header locationHeader = method.getResponseHeader("location"); if (locationHeader != null) { redirectLocation = locationHeader.getValue(); } else { // The response is invalid and did not provide the new location for // the resource. Report an error or possibly handle the response // like a 404 Not Found error. } 特殊重定向: 300 多重選擇. HttpStatus.SC_MULTIPLE_CHOICES 304 沒有改動. HttpStatus.SC_NO T_MODIFIED 305 使用代理. HttpStatus.SC_USE_PROXY (四)、自動轉(zhuǎn)向
根據(jù)RFC2616中對自動轉(zhuǎn)向的定義,主要有兩種:301和302。301表示永久的移走(Moved Permanently),當(dāng)返回的是301,則表示請求的資源已經(jīng)被移到一個固定的新地方,任何向該地址發(fā)起請求都會被轉(zhuǎn)到新的地址上。302表示暫時的轉(zhuǎn)向,比如在服務(wù)器端的servlet程序調(diào)用了sendRedirect方法,則在客戶端就會得到一個302的代碼,這時服務(wù)器返回的頭信息中l(wèi)ocation的值就是sendRedirect轉(zhuǎn)向的目標(biāo)地址。 HttpClient支持自動轉(zhuǎn)向處理,但是象POST和PUT方式這種要求接受后繼服務(wù)的請求方式,暫時不支持自動轉(zhuǎn)向,因此如果碰到POST方式提交后返回的是301或者302的話需要自己處理。就像剛才在POSTMethod中舉的例子:如果想進(jìn)入登錄BBS后的頁面,必須重新發(fā)起登錄的請求,請求的地址可以在頭字段location中得到。不過需要注意的是,有時候location返回的可能是相對路徑,因此需要對location返回的值做一些處理才可以發(fā)起向新地址的請求。 另外除了在頭中包含的信息可能使頁面發(fā)生重定向外,在頁面中也有可能會發(fā)生頁面的重定向。引起頁面自動轉(zhuǎn)發(fā)的標(biāo)簽是:<meta http-equiv="refresh" content="5; url=http://www.ibm.com/us">。如果你想在程序中也處理這種情況的話得自己分析頁面來實(shí)現(xiàn)轉(zhuǎn)向。需要注意的是,在上面那個標(biāo)簽中url的值也可以是一個相對地址,如果是這樣的話,需要對它做一些處理后才可以轉(zhuǎn)發(fā)。 (五)、Cookies HttpClient能自動管理cookie,包括允許服務(wù)器設(shè)置cookie并在需要的時候自動將cookie返回服務(wù)器,它也支持手工設(shè)置cookie后發(fā)送到服務(wù)器端。不幸的是,對如何處理cookie,有幾個規(guī)范互相沖突:Netscape Cookie 草案, RFC2109, RFC2965,而且還有很大數(shù)量的軟件商的cookie實(shí)現(xiàn)不遵循任何規(guī)范. 為了處理這種狀況,HttpClient提供了策略驅(qū)動的cookie管理方式。HttpClient支持的cookie規(guī)范有: Netscape cookie草案,是最早的cookie規(guī)范,基于rfc2109。盡管這個規(guī)范與rc2109有較大的差別,這樣做可以與一些服務(wù)器兼容。rfc2109,是w3c發(fā)布的第一個官方cookie規(guī)范。理論上講,所有的服務(wù)器在處理cookie(版本1)時,都要遵循此規(guī)范,正因如此,HttpClient將其設(shè)為默認(rèn)的規(guī)范。遺憾的是,這個規(guī)范太嚴(yán)格了,以致很多服務(wù)器不正確的實(shí)施了該規(guī)范或仍在作用Netscape規(guī)范。在這種情況下,應(yīng)使用兼容規(guī)范。兼容性規(guī)范,設(shè)計用來兼容盡可能多的服務(wù)器,即使它們并沒有遵循標(biāo)準(zhǔn)規(guī)范。當(dāng)解析cookie出現(xiàn)問題時,應(yīng)考慮采用兼容性規(guī)范。 RFC2965規(guī)范暫時沒有被HttpClient支持(在以后的版本為會加上),它定義了cookie版本2,并說明了版本1cookie的不足,RFC2965有意有久取代rfc2109. 在HttpClient中,有兩種方法來指定cookie規(guī)范的使用, HttpClient client = new HttpClient(); client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY); 這種方法設(shè)置的規(guī)范只對當(dāng)前的HttpState有效,參數(shù)可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。 System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY"); 此法指的規(guī)范,對以后每個新建立的HttpState對象都有效,參數(shù)可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。 常有不能解析cookie的問題,但更換到兼容規(guī)范大都能解決。 (六)、使用HttpClient遇到問題怎么辦? 用一個瀏覽器訪問服務(wù)器,以確認(rèn)服務(wù)器應(yīng)答正常如果在使代理,關(guān)掉代理試試另找一個服務(wù)器來試試(如果運(yùn)行著不同的服務(wù)器軟件更好)檢查代碼是否按教程中講的思路編寫設(shè)置log級別為debug,找出問題出現(xiàn)的原因打開wiretrace,來追蹤客戶端與服務(wù)器的通信,以確實(shí)問題出現(xiàn)在什么地方用telnet或netcat手工將信息發(fā)送到服務(wù)器,適合于猜測已經(jīng)找到了原因而進(jìn)行試驗時將netcat以監(jiān)聽方式運(yùn)行,用作服務(wù)器以檢查httpclient如何處理應(yīng)答的。利用最新的httpclient試試,bug可能在最新的版本中修復(fù)了向郵件列表求幫助向bugzilla報告bug. (七)、SSL 借助Java Secure Socket Extension (JSSE),HttpClient全面支持Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)協(xié)議上的HTTP。JSSE已經(jīng)jre1.4及以后的版本中,以前的版本則需要手工安裝設(shè)置,具體過程參見Sun網(wǎng)站或本學(xué)習(xí)筆記。 HttpClient中使用SSL非常簡單,參考下面兩個例子: HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www./"); httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine().toString()); ,如果通過需要授權(quán)的代理,則如下: HttpClient httpclient = new HttpClient(); httpclient.getHostConfiguration().setProxy("myproxyhost", 8080); httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost", new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password")); GetMethod httpget = new GetMethod("https://www./"); httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine().toString()); 在HttpClient中定制SSL的步驟如下: 提供了一個實(shí)現(xiàn)了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。這個 socket factory負(fù)責(zé)打一個到服務(wù)器的端口,使用標(biāo)準(zhǔn)的或第三方的SSL函數(shù)庫,并進(jìn)行象連接握手等初始化操作。通常情況下,這個初始化操作在端口被創(chuàng)建時自動進(jìn)行的。實(shí)例化一個org.apache.commons.httpclient.protocol.Protocol對象。創(chuàng)建這個實(shí)例時,需要一個合法的協(xié)議類型(如https),一個定制的socket factory,和一個默認(rèn)的端中號(如https的443端口). Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);然后,這個實(shí)例可被設(shè)置為協(xié)議的處理器。 HttpClient httpclient = new HttpClient(); httpclient.getHostConfiguration().setHost("www.", 443, myhttps); GetMethod httpget = new GetMethod("/"); httpclient.executeMethod(httpget); 通過調(diào)用Protocol.registerProtocol方法,將此定制的實(shí)例,注冊為某一特定協(xié)議的默認(rèn)的處理器。由此,可以很方便地定制自己的協(xié)議類型(如myhttps)。 Protocol.registerProtocol("myhttps", new Protocol("https", new MySSLSocketFactory(), 9443)); ... HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("myhttps://www./"); httpclient.executeMethod(httpget); 如果想用自己定制的處理器取代https默認(rèn)的處理器,只需要將其注冊為"https"即可。 Protocol.registerProtocol("https", new Protocol("https", new MySSLSocketFactory(), 443)); HttpClient httpclient = new HttpClient(); GetMethod httpget = new GetMethod("https://www./"); httpclient.executeMethod(httpget); 已知的限制和問題持續(xù)的SSL連接在Sun的低于1.4JVM上不能工作,這是由于JVM的bug造成。通過代理訪問服務(wù)器時,非搶先認(rèn)證( Non-preemptive authentication)會失敗,這是由于HttpClient的設(shè)計缺陷造成的,以后的版本中會修改。 遇到問題的處理 很多問題,特別是在jvm低于1.4時,是由jsse的安裝造成的。 下面的代碼,可作為最終的檢測手段。 import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.Socket; import javax.net.ssl.SSLSocketFactory; public class Test { public static final String TARGET_HTTPS_SERVER = "www."; public static final int TARGET_HTTPS_PORT = 443; public static void main(String[] args) throws Exception { Socket socket = SSLSocketFactory.getDefault(). createSocket(TARGET_HTTPS_SERVER, TARGET_HTTPS_PORT); try { Writer out = new OutputStreamWriter( socket.getOutputStream(), "ISO-8859-1"); out.write("GET / HTTP/1.1\r\n"); out.write("Host: " + TARGET_HTTPS_SERVER + ":" + TARGET_HTTPS_PORT + "\r\n"); out.write("Agent: SSL-TEST\r\n"); out.write("\r\n"); out.flush(); BufferedReader in = new BufferedReader( new InputStreamReader(socket.getInputStream(), "ISO-8859-1")); String line = null; while ((line = in.readLine()) != null) { System.out.println(line); } } finally { socket.close(); } } } (八)、httpclient的多線程處理 使用多線程的主要目的,是為了實(shí)現(xiàn)并行的下載。在httpclient運(yùn)行的過程中,每個http協(xié)議的方法,使用一個HttpConnection實(shí)例。由于連接是一種有限的資源,每個連接在某一時刻只能供一個線程和方法使用,所以需要確保在需要時正確地分配連接。HttpClient采用了一種類似jdbc連接池的方法來管理連接,這個管理工作由 MultiThreadedHttpConnectionManager完成。 MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); HttpClient client = new HttpClient(connectionManager); 此是,client可以在多個線程中被用來執(zhí)行多個方法。每次調(diào)用HttpClient.executeMethod() 方法,都會去鏈接管理器申請一個連接實(shí)例,申請成功這個鏈接實(shí)例被簽出(checkout),隨之在鏈接使用完后必須歸還管理器。管理器支持兩個設(shè)置:maxConnectionsPerHost 每個主機(jī)的最大并行鏈接數(shù),默認(rèn)為2 maxTotalConnections 客戶端總并行鏈接最大數(shù),默認(rèn)為20 管理器重新利用鏈接時,采取早歸還者先重用的方式(least recently used approach)。 由于是使用HttpClient的程序而不是HttpClient本身來讀取應(yīng)答包的主體,所以HttpClient無法決定什么時間連接不再使用了,這也就要求在讀完應(yīng)答包的主體后必須手工顯式地調(diào)用releaseConnection()來釋放申請的鏈接。 MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); HttpClient client = new HttpClient(connectionManager); ... // 在某個線程中。 GetMethod get = new GetMethod("http://jakarta./"); try { client.executeMethod(get); // print response to stdout System.out.println(get.getResponseBodyAsStream()); } finally { // be sure the connection is released back to the connection // manager get.releaseConnection(); } 對每一個HttpClient.executeMethod須有一個method.releaseConnection()與之匹配. (九)、HttpClient的連接釋放問題 在method.releaseConnection()后并沒有把鏈接關(guān)閉,這個方法只是將鏈接返回給connection manager。 如果使用HttpClient client = new HttpClient()實(shí)例化一個HttpClient connection manager默認(rèn)實(shí)現(xiàn)是使用SimpleHttpConnectionManager。 SimpleHttpConnectionManager有個構(gòu)造函數(shù)如下 public SimpleHttpConnectionManager(boolean alwaysClose) { super(); this.alwaysClose = alwaysClose; } alwaysClose設(shè)為true在鏈接釋放之后connection manager 就會關(guān)閉鏈。在我們HttpClient client = new HttpClient()這樣實(shí)例化一個client時connection manager是這樣被實(shí)例化的 this.httpConnectionManager = new SimpleHttpConnectionManager(); 因此alwaysClose默認(rèn)是false,connection是不會被主動關(guān)閉的,因此我們就有了一個客戶端關(guān)閉鏈接的方法。 解決方法: 1、把事例代碼中的第一行實(shí)例化代碼改為如下即可,在method.releaseConnection();之后connection manager會關(guān)閉connection 。 HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) ); 2、實(shí)例化代碼使用:HttpClient client = new HttpClient(); 在method.releaseConnection();之后加上 ((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown(); 其實(shí)shutdown源代碼是 public void shutdown() { httpConnection.close(); } 3、實(shí)例化代碼使用:HttpClient client = new HttpClient(); 在method.releaseConnection();之后加上 client.getHttpConnectionManager().closeIdleConnections(0);此方法源碼代碼如下: public void closeIdleConnections(long idleTimeout) { long maxIdleTime = System.currentTimeMillis() - idleTimeout; if (idleStartTime <= maxIdleTime) { httpConnection.close(); } } 4、代碼實(shí)現(xiàn)很簡單,所有代碼就和最上面的事例代碼一樣。只需要在HttpMethod method = new GetMethod("http://www.");加上一行HTTP頭的設(shè)置即可method.setRequestHeader("Connection", "close"); (十)、HttpClient的一個應(yīng)用的例子(圖片下載) 1. import java.io.File; 2. import java.io.FileOutputStream; 3. import java.io.IOException; 4. import org.apache.commons.httpclient.HttpClient; 5. import org.apache.commons.httpclient.methods.GetMethod; 6. 7. /** 8. * 用HttpClient下載圖片 9. * @author wei 10. */ 11. public class TestDownImage { 12. 13. public static void main(String[] args) throws IOException{ 14. HttpClient client = new HttpClient(); 15. GetMethod get = new GetMethod("http://images.sohu.com/uiue/sohu_logo/beijing2008/2008sohu.gif"); 16. client.executeMethod(get); 17. File storeFile = new File("c:/2008sohu.gif"); 18. FileOutputStream output = new FileOutputStream(storeFile); 19. //得到網(wǎng)絡(luò)資源的字節(jié)數(shù)組,并寫入文件 20. output.write(get.getResponseBody()); 21. output.close(); 22. } 23. } |
|