標籤:blog http io ar os 使用 java for sp
http://lovelaomao.blogbus.com/tag/HttpClient/ 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:[email protected]:/home/cvspublic login
password: anoncvs
cvs -d :pserver:[email protected]:/home/cvspublic checkout jakarta-commons/logging
cvs -d :pserver:[email protected]:/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.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
7、字元編碼(character encoding)
一個HTTP協議的請求或應答的頭部(在http協議中,資料包分為兩部分,一部分是頭部,由一些名值對構成,一部分是主體(body),是真正傳辦理的資料(如HTML頁面等)),必須以US-ASCII編碼,這是因為頭部不傳資料而只描述被要傳輸的資料的一些資訊,一個例外是cookie,它是資料但是通過頭部進行傳輸的,所以它也要用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、Cookies
HttpClient能自動管理cookie,包括允許伺服器設定cookie並在需要的時候自動將cookie返回伺服器,它也支援手工設定cookie後發送到伺服器端。不幸的是,對如何處理cookie,有幾個規範互相衝突:Netscape Cookie 草案, RFC2109, RFC2965,而且還有很大數量的軟體商的cookie實現不遵循任何規範. 為了處理這種狀況,HttpClient提供了策略驅動的cookie管理方式。HttpClient支援的cookie規範有:
Netscape cookie草案,是最早的cookie規範,基於rfc2109。儘管這個規範與rc2109有較大的差別,這樣做可以與一些伺服器相容。
rfc2109,是w3c發布的第一個官方cookie規範。理論上講,所有的伺服器在處理cookie(版本1)時,都要遵循此規範,正因如此,HttpClient將其設為預設的規範。遺憾的是,這個規範太嚴格了,以致很多伺服器不正確的實施了該規範或仍在作用Netscape規範。在這種情況下,應使用相容規範。
相容性規範,設計用來相容儘可能多的伺服器,即使它們並沒有遵循標準規範。當解析cookie出現問題時,應考慮採用相容性規範。
RFC2965規範暫時沒有被HttpClient支援(在以後的版本為會加上),它定義了cookie版本2,並說明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
在HttpClient中,有兩種方法來指定cookie規範的使用,
HttpClient client = new HttpClient();
client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
這種方法設定的規範只對當前的HttpState有效,參數可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。
System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
此法指的規範,對以後每個建立立的HttpState對象都有效,參數可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
常有不能解析cookie的問題,但更換到相容規範大都能解決。
9、使用HttpClient遇到問題怎麼辦?
用一個瀏覽器訪問伺服器,以確認伺服器應答正常
如果在使代理,關掉代理試試
另找一個伺服器來試試(如果運行著不同的伺服器軟體更好)
檢查代碼是否按教程中講的思路編寫
設定log層級為debug,找出問題出現的原因
開啟wiretrace,來追蹤用戶端與伺服器的通訊,以確實問題出現在什麼地方
用telnet或netcat手工將資訊發送到伺服器,適合於猜測已經找到了原因而進行實驗時
將netcat以監聽方式運行,用作伺服器以檢查httpclient如何處理應答的。
利用最新的httpclient試試,bug可能在最新的版本中修複了
向郵件清單求協助
向bugzilla報告bug.
10、SSL
藉助Java Secure Socket Extension (JSSE),HttpClient全面支援Secure Sockets Layer (SSL)或IETF Transport Layer Security (TLS)協議上的HTTP。JSSE已經jre1.4及以後的版本中,以前的版本則需要手工安裝設定,具體過程參見Sun網站或本學習筆記。
HttpClient中使用SSL非常簡單,參考下面兩個例子:
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
,如果通過需要授權的代理,則如下:
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.verisign.com/");
httpclient.executeMethod(httpget);
System.out.println(httpget.getStatusLine().toString());
在HttpClient中定製SSL的步驟如下:
提供了一個實現了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory介面的socket factory。這個 socket factory負責打一個到伺服器的連接埠,使用標準的或第三方的SSL函數庫,並進行象串連握手等初始化操作。通常情況下,這個初始化操作在連接埠被建立時自動進行的。
執行個體化一個org.apache.commons.httpclient.protocol.Protocol對象。建立這個執行個體時,需要一個合法的協議類型(如https),一個定製的socket factory,和一個預設的端中號(如https的443連接埠).
Protocol myhttps = new Protocol("https", new MySSLSocketFactory(), 443);
然後,這個執行個體可被設定為協議的處理器。
HttpClient httpclient = new HttpClient();
httpclient.getHostConfiguration().setHost("www.whatever.com", 443, myhttps);
GetMethod httpget = new GetMethod("/");
httpclient.executeMethod(httpget);
通過調用Protocol.registerProtocol方法,將此定製的執行個體,註冊為某一特定協議的預設的處理器。由此,可以很方便地定製自己的協議類型(如myhttps)。
Protocol.registerProtocol("myhttps",
new Protocol("https", new MySSLSocketFactory(), 9443));
...
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("myhttps://www.whatever.com/");
httpclient.executeMethod(httpget);
如果想用自己定製的處理器取代https預設的處理器,只需要將其註冊為"https"即可。
Protocol.registerProtocol("https",
new Protocol("https", new MySSLSocketFactory(), 443));
HttpClient httpclient = new HttpClient();
GetMethod httpget = new GetMethod("https://www.whatever.com/");
httpclient.executeMethod(httpget);
已知的限制和問題
持續的SSL串連在Sun的低於1.4JVM上不能工作,這是由於JVM的bug造成。
通過代理訪問伺服器時,非搶先認證( Non-preemptive authentication)會失敗,這是由於HttpClient的設計缺陷造成的,以後的版本中會修改。
遇到問題的處理
很多問題,特別是在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.verisign.com";
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();
}
}
}
11、httpclient的多執行緒
使用多線程的主要目的,是為了實現並行的下載。在httpclient啟動並執行過程中,每個http協議的方法,使用一個HttpConnection執行個體。由於串連是一種有限的資源,每個串連在某一時刻只能供一個線程和方法使用,所以需要確保在需要時正確地分配串連。HttpClient採用了一種類似jdbc串連池的方法來管理串連,這個管理工作由 MultiThreadedHttpConnectionManager完成。
MultiThreadedHttpConnectionManager connectionManager =
new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
此是,client可以在多個線程中被用來執行多個方法。每次調用HttpClient.executeMethod() 方法,都會去連結管理器申請一個串連執行個體,申請成功這個連結執行個體被簽出(checkout),隨之在連結使用完後必須歸還管理器。管理器支援兩個設定:maxConnectionsPerHost 每個主機的最大並行連結數,預設為2
maxTotalConnections 用戶端總並行連結最大數,預設為20
管理器重新利用連結時,採取早歸還者先重用的方式(least recently used approach)。
由於是使用HttpClient的程式而不是HttpClient本身來讀取應答包的主體,所以HttpClient無法決定什麼時間串連不再使用了,這也就要求在讀完應答包的主體後必須手工顯式地調用releaseConnection()來釋放申請的連結。
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
...
// 在某個線程中。
GetMethod get = new GetMethod("http://jakarta.apache.org/");
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()與之匹配.
12、HTTP方法
HttpClient支援的HTTP方法有8種,下面分述之。
1、Options
HTTP方法Options用來向伺服器發送請求,希望獲得針對由請求URL(request url)標誌的資源在請求/應答的通訊過程可以使用的功能選項。通過這個方法,用戶端可以在採取具體行動之前,就可對某一資源決定採取什麼動作和/或以及一些必要條件,或者瞭解伺服器提供的功能。這個方法最典型的應用,就是用來擷取伺服器支援哪些HTTP方法。
HttpClient中有一個類叫OptionsMethod,來支援這個HTTP方法,利用這個類的getAllowedMethods方法,就可以很簡單地實現上述的典型應用。
OptionsMethod options = new OptionsMethod("http://jakarta.apache.org");
// 執行方法並做相應的異常處理
...
Enumeration allowedMethods = options.getAllowedMethods();
options.releaseConnection();
2、Get
HTTP方法GET用來取回請求URI(request-URI)標誌的任何資訊(以實體(entity)的形式),"get"這個單詞本意就是”擷取“的意思。如果請求URI指向的一個資料處理過程,那這個過程產生的資料,在應答中以實體的形式被返回,而不是將這個過程的代碼的返回。
如果HTTP包中含有If-ModifiedSince, If-Unmodified-Since, If-Match, If-None-Match, 或 If-Range等頭欄位,則GET也就變成了”條件GET“,即只有滿足上述欄位描述的條件的實體才被取回,這樣可以減少一些非必需的網路傳輸,或者減少為擷取某一資源的多次請求(如第一次檢查,第二次下載)。(一般的瀏覽器,都有一個臨時目錄,用來緩衝一些網頁資訊,當再次瀏覽某個頁面的時候,只下載那些修改過的內容,以加快瀏覽速度,就是這個道理。至於檢查,則常用比GET更好的方法HEAD來實現。)如果HTTP包中含有Range頭欄位,那麼請求URI指定的實體中,只有決定範圍條件的那部分才被取回來。(用過多線程下載工具的朋友,可能比較容易理解這一點)
這個方法的典型應用,用來從web伺服器下載文檔。HttpClient定義了一個類叫GetMethod來支援這個方法,用GetMethod類中getResponseBody, getResponseBodyAsStream 或 getResponseBodyAsString函數就可以取到應答包包體中的文檔(如HTML頁面)資訊。這這三個函數中,getResponseBodyAsStream通常是最好的方法,主要是因為它可以避免在處理下載的文檔之前緩衝所有的下載的資料。
GetMethod get = new GetMethod("http://jakarta.apache.org");
// 執行方法,並處理失敗的請求.
...
InputStream in = get.getResponseBodyAsStream();
// 利用輸入資料流來處理資訊。
get.releaseConnection();
對GetMethod的最常見的不正確的使用,是沒有將全部的應答主體的資料讀出來。還有,必須注意要手工明確地將連結釋放。
3、Head
HTTP的Head方法,與Get方法完全一致,唯一的差別是伺服器不能在應答包中包含主體(message-body),而且一定不能包含主體。使用這個方法,可以使得客戶無需將資源下載回就可就以得到一些關於它的基本資料。這個方法常用來檢查超鏈的可訪問性以及資源最近有沒有被修改。
HTTP的head方法最典型的應用,是擷取資源的基本資料。HttpClient定義了HeadMethod類支援這個方法,HeadMethod類與其它*Method類一樣,用 getResponseHeaders()取回頭部資訊,而沒有自己的特殊方法。
HeadMethod head = new HeadMethod("http://jakarta.apache.org");
// 執行方法,並處理失敗的請求.
...
// 取回應答包的頭欄位資訊.
Header[] headers = head.getResponseHeaders();
// 只取回最後修改日期欄位的資訊.
String lastModified = head.getResponseHeader("last-modified").getValue();
4、Post
Post在英文有“派駐”的意思,HTTP方法POST就是要求伺服器接受請求包中的實體,並將其作為請求URI的下屬資源。從本質上說,這意味著伺服器要儲存這個實體資訊,而且通常由伺服器端的程式進行處理。Post方法的設計意圖,是要以一種統一的方式實現下列功能:
對已有的資源做評註
將資訊發布到BBS、新聞群組、郵件清單,或類似的文章組中
將一塊資料,提交給資料處理進程
通過追加操作,來擴充一個資料庫
這些都操作期待著在伺服器端產生一定的“副作用”,如修改了資料庫等。
HttpClient定義PostMethod類以支援該HTTP方法,在httpclient中,使用post方法有兩個基本的步驟:為請求包準備資料,然後讀取伺服器來的應答包的資訊。通過調用 setRequestBody()函數,來為請求包提供資料,它可以接收三類參數:輸入資料流、名值對數組或字串。至於讀取應答包需要調用 getResponseBody* 那一系列的方法,與GET方法處理應答包的方法相同。
常見問題是,沒有將全部應答讀取(無論它對程式是否有用),或沒有釋放連結資源。
HttpClient簡易介紹