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("my
https://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").get();
4、Post
Post在英文有“派駐”的意思,HTTP方法POST就是要求服務器接受請求包中的實體,并將其作為請求URI的下屬資源。從本質上說,這意味著服務
器要保存這個實體信息,而且通常由服務器端的程序進行處理。Post方法的設計意圖,是要以一種統一的方式實現下列功能:
- 對已有的資源做評注
- 將信息發布到BBS、新聞組、郵件列表,或類似的文章組中
- 將一塊數據,提交給數據處理進程
- 通過追加操作,來擴展一個數據庫
這些都操作期待著在服務器端產生一定的“副作用”,如修改了數據庫等。
HttpClient定義PostMethod類以支持該HTTP方法,在httpclient中,使用post方法有兩個基本的步驟:為請求包準備數
據,然后讀取服務器來的應答包的信息。通過調用
setRequestBody()函數,來為請求包提供數據,它可以接收三類參數:輸入流、名值對數組或字符串。至于讀取應答包需要調用
getResponseBody* 那一系列的方法,與GET方法處理應答包的方法相同。
常見問題是,沒有將全部應答讀取(無論它對程序是否有用),或沒有釋放鏈接資源。
參考資料:
http://www.systinet.com/doc/wasp_uddi/uddi/igpreliminary.html