雖然用telnet這樣的程序都可把頁面取回來,但是在與web服務器的交互中,如果涉及或https或ssl等內容,一般功能相對完備的http客戶端
還是非常必要的。IE或NetScape等瀏覽器確實不錯,可是如果為實現持續互動而在程序調用瀏覽器,我個人認為其中的工作量還是不小的,這還沒考慮版
權問題。最好的辦法,就是能有一個開源的包,能實現http客戶端的功能,供我們開發的程序調用。
httpclient就是這么一個包,我相信可能有比它的實現更好的,但目前我只關注這個。:)
下面是nogoop做的功能比較表:
Features |
nogoop |
Sun JRE < 1.4.2 |
Sun JRE 1.4.2 |
Innovation |
Apache/Jakarta |
s |
|
|
|
X |
X |
plug compatible |
X |
X |
X |
X |
[partial] |
true request output stream |
|
|
|
X |
X |
true response input stream |
X |
|
|
X |
X |
connection keep alive |
X |
X |
X |
X |
X |
connection pool throttling |
X |
|
|
|
X |
connection/request timeout |
X |
|
X [uns] |
X |
X |
idle connection timeout |
X |
|
|
|
X |
pipelining of requests |
|
|
|
X |
|
alternate DNS resolution (dnsjava) |
X |
|
|
|
|
SSL |
X |
X |
X |
X |
X |
basic authentication |
X |
X |
X |
X |
X |
digest authentication |
X |
X |
X |
X |
X |
NTLM authentication |
X |
|
[Windows only] |
|
X |
proxy authentication |
X |
X |
X |
X |
X |
minimum JRE version |
1.2 |
1 |
01年4月2日 |
1.2 |
1.2 |
price |
$499 |
free |
free |
free |
free |
source available |
X |
|
|
X |
X |
diagnostic tracing |
X |
|
|
X |
X |
actively supported |
X |
X |
X |
|
X |
fix turnaround |
fast |
slow |
slow |
none |
medium |
license |
purchase |
Sun JRE |
Sun JRE |
LGPL |
Apache |
1、HttpClient的功能
- 基于標準,純正java,實現了http1.0和1.1。
- 在一個可擴展的OO框架內,實現了HTTP的全部方法(GET, POST,
PUT, DELETE, HEAD, OPTIONS, and TRACE)
- 支持HTTPS(ssl上的HTTP)的加密操作
- 透明地穿過HTTP代理建立連接
- 通過CONNECT方法,利用通過建立穿過HTTP代理的HTTPS連接
- 利用本地Java socket,透明地穿過SOCKS(版本5和4)代理建立連接
- 支持利用Basic、Digest和NTLM加密的認證
- 支持用于上傳大文件的Multi-Part表單POST方法
- 插件式安全socket實現,易于使用第三方的解決方案
- 連接管理,支持多線程應用,支持設定單個主機總連接和最高連接數量,自動檢測和關閉失效連接
- 直接將請求信息流送到服務器的端口
- 直接讀取從服務器的端口送出的應答信息
- 支持HTTP/1.0中用KeepAlive和HTTP/1.1中用persistance設置的持久連接
- 直接訪問由服務器送出的應答代碼和頭部信息
- 可設置連接超時時間
-
- HttpMethods 實現Command Pattern,以允許并行請求或高效連接復用
- 遵循the Apache Software License協議,源碼免費可得
2、預備工作
對jre1.3.*,如果要HttpClient支持https,則需要下載并安裝
jsse和
jce.安裝的步驟如下:
1)下載jsse和jce.
2)檢查CLASSPATH中沒有與jsse和jce相關的jar包
3)將 US_export_policy.jar、local_policy.jar、jsse.jar、jnet.jar、jce1_2_x.jar、sunjce_provider.jar、jcert.jar復制到目錄:
UNIX:$JDK_HOME/jre/lib/ext
Windows:%JDK_HOME%"jre"lib"ext
4)修改下述目錄下的java.security文件。
UNIX:$JDK_HOME/jre/lib/security/
Windows:%JDK_HOME%"jre"lib"security"
5)
將
#
# List of providers and their preference orders:
#
security.provider.1=sun.security.provider.Sun
security.provider.2=com.sun.rsajca.Provider
改為:
#
# List of providers and their preference orders:
#
security.provider.1=com.sun.crypto.provider.SunJCE
security.provider.2=sun.security.provider.Sun
security.provider.3=com.sun.rsajca.Provider
security.provider.4=com.sun.net.ssl.internal.ssl.Provider
HttpClient還要求安裝commons-logging,下面跟httpclient一塊安裝。
3、取得源碼
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic login
password: anoncvs
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/logging
cvs -d :pserver:anoncvs@cvs.apache.org:/home/cvspublic checkout jakarta-commons/httpclient
編譯:
cd jakarta-commons/logging
ant dist
cp dis/*.jar ../httpclient/lib/
cd ../httpclient
ant dist
4、使用HttpClient編程的基本步聚
- 創建 HttpClient 的一個實例.
- 創建某個方法(DeleteMethod,EntityEnclosingMethod,ExpectContinueMethod,
GetMethod,HeadMethod,MultipartPostMethod,OptionsMethod,PostMethod,
PutMethod,TraceMethod)的一個實例,一般可用要目標URL為參數。
- 讓 HttpClient 執行這個方法.
- 讀取應答信息.
- 釋放連接.
- 處理應答.
在執行方法的過程中,有兩種異常,一種是HttpRecoverableException,表示偶然性錯誤發生,一般再試可能成功,另一種是IOException,嚴重錯誤。
這兒有這個教程中的一個例程,可以
下載。
5、認證
HttpClient三種不同的認證方案: Basic, Digest and NTLM. 這些方案可用于服務器或代理對客戶端的認證,簡稱服務器認證或代理認證。
1)服務器認證(Server Authentication)
HttpClient處理服務器認證幾乎是透明的,僅需要開發人員提供登錄信息(login
credentials)。登錄信息保存在HttpState類的實例中,可以通過 setCredentials(String realm,
Credentials cred)和getCredentials(String
realm)來獲取或設置。注意,設定對非特定站點訪問所需要的登錄信息,將realm參數置為null.
HttpClient內建的自動認證,可以通過HttpMethod類的setDoAuthentication(boolean
doAuthentication)方法關閉,而且這次關閉只影響HttpMethod當前的實例。
搶先認證(Preemptive Authentication)可以通過下述方法打開.
client.getState().setAuthenticationPreemptive(true);
在這種模式時,HttpClient會主動將basic認證應答信息傳給服務器,即使在某種情況下服務器可能返回認證失敗的應答,這樣做主要是為了減少連接的建立。為使每個新建的 HttpState實例都實行搶先認證,可以如下設置系統屬性。
setSystemProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");
Httpclient實現的搶先認證遵循rfc2617.
2)代理認證(proxy authentication)
除了登錄信息需單獨存放以外,代理認證與服務器認證幾乎一致。用 setProxyCredentials(String realm, Credentials cred)和 getProxyCredentials(String realm)設、取登錄信息。
3)認證方案(authentication schemes)
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連接或服務器不支持持久連接的情況。
6、重定向
由于技術限制,以及為保證2.0發布版API的穩定,HttpClient還不能自動處重定向,但對重定向到同一主機、同一端口且采用同一協議的情況HttpClient可以支持。不能自動的處理的情況,包括需要人工交互的情況,或超出httpclient的能力。
當服務器重定向指令指到不同的主機時,HttpClient只是簡單地將重定向狀態碼作為應答狀態。所有的300到399(包含兩端)的返回碼,都表示是重定向應答。常見的有:
- 301 永久移動. HttpStatus.SC_MOVED_PERMANENTLY
- 302 臨時移動. HttpStatus.SC_MOVED_TEMPORARILY
- 303 See Other. HttpStatus.SC_SEE_OTHER
- 307 臨時重定向. HttpStatus.SC_TEMPORARY_REDIRECT
當收到簡單的重定向時,程序應從HttpMethod對象中抽取新的URL并將其下載。另外,限制一下重定向次數是個好的主意,這可以避免遞歸循環。新的URL可以從頭字段Location中抽取,如下:
String redirectLocation;
Header locationHeader = method.getResponseHeader("location");
if (locationHeader != null) {
redirectLocation = locationHeader.get();
} 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
7、字符編碼(character encoding)
一個HTTP協議的請求或應答的頭部(在http協議中,數據包分
為兩部分,一部分是頭部,由一些名值對構成,一部分是主體(body),是真正傳辦理的數據(如HTML頁面等)),必須以US-ASCII編碼,這是因
為頭部不傳數據而只描述被要傳輸的數據的一些信息,一個例外是,它是數據但是通過頭部進行傳輸的,所以它也要用US-ASCII編碼。
HTTP數據包的主體部分,可以用任何一種方式進行編碼,默認是ISO-8859-1,具體可以用頭部字段Content-Type指定。可以利用
addRequestHeader方法,設定編碼方式;用
getResponseCharSet取得編碼方式。對HTML或XML等類型的文檔,它們的本身的Content-Type也可以指定編碼方式,主要區
分兩者的作用范圍以得到正確實的解碼。
URL的編碼標準,由RFC1738指定為,只能是由可打印8位/字節的us-ascii字符組成,80-ff不是us-ascii字符,而00-1F是控制字符,這兩個區域中用的字符都須加以編碼(encoded)。
8、s
HttpClient能自動管理,包括允許服務器設置并在需要的時候自動將返回服務器,它也支持手工設置后發送到服務器端。不幸的是,對如何處理,有幾個
規范互相沖突:Netscape 草案, RFC2109, RFC2965,而且還有很大數量的軟件商的實現不遵循任何規范.
為了處理這種狀況,HttpClient提供了策略驅動的管理方式。HttpClient支持的規范有:
- Netscape 草案,是最早的規范,基于rfc2109。盡管這個規范與rc2109有較大的差別,這樣做可以與一些服務器兼容。
- rfc2109,
是w3c發布的第一個官方規范。理論上講,所有的服務器在處理(版本1)時,都要遵循此規范,正因如此,HttpClient將其設為默認的規范。遺憾的
是,這個規范太嚴格了,以致很多服務器不正確的實施了該規范或仍在作用Netscape規范。在這種情況下,應使用兼容規范。
- 兼容性規范,設計用來兼容盡可能多的服務器,即使它們并沒有遵循標準規范。當解析出現問題時,應考慮采用兼容性規范。
RFC2965規范暫時沒有被HttpClient支持(在以后的版本為會加上),它定義了版本2,并說明了版本1的不足,RFC2965有意有久取代rfc2109.
在HttpClient中,有兩種方法來指定規范的使用,
-
HttpClient client = new HttpClient();
client.getState().setPolicy(Policy.COMPATIBILITY);
這種方法設置的規范只對當前的HttpState有效,參數可取值Policy.COMPATIBILITY,Policy.NETSCAPE_DRAFT或Policy.RFC2109。
-
System.setProperty("apache.commons.httpclient.spec", "COMPATIBILITY");
此法指的規范,對以后每個新建立的HttpState對象都有效,參數可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
常有不能解析的問題,但更換到兼容規范大都能解決。
9、使用HttpClient遇到問題怎么辦?
- 用一個瀏覽器訪問服務器,以確認服務器應答正常
- 如果在使代理,關掉代理試試
- 另找一個服務器來試試(如果運行著不同的服務器軟件更好)
- 檢查代碼是否按教程中講的思路編寫
- 設置log級別為debug,找出問題出現的原因
- 打開wiretrace,來追蹤客戶端與服務器的通信,以確實問題出現在什么地方
- 用telnet或netcat手工將信息發送到服務器,適合于猜測已經找到了原因而進行試驗時
- 將netcat以監聽方式運行,用作服務器以檢查httpclient如何處理應答的。
- 利用最新的httpclient試試,bug可能在最新的版本中修復了
- 向郵件列表求幫助
- 向bugzilla報告bug.