Cookies,SSL,httpclient的多線程處理,HTTP方法

HttpClient能自動管理cookie,包括允許服務器設置cookie並在需要的時候自動將cookie返回服務器,它也支持手工設置cookie後發送到服務器端。不幸的是,對如何處理cookie,有幾個規範互相沖突:Netscape Cookie 草案, RFC2109, RFC2965,而且還有很大數量的軟件商的cookie實現不遵循任何規範. 爲了處理這種狀況,HttpClient提供了策略驅動的cookie管理方式。HttpClient支持的cookie規範有:

  1. Netscape cookie草案,是最早的cookie規範,基於rfc2109。儘管這個規範與rc2109有較大的差別,這樣做可以與一些服務器兼容。 
  2. rfc2109,是w3c發佈的第一個官方cookie規範。理論上講,所有的服務器在處理cookie(版本1)時,都要遵循此規範,正因如此,HttpClient將其設爲默認的規範。遺憾的是,這個規範太嚴格了,以致很多服務器不正確的實施了該規範或仍在作用Netscape規範。在這種情況下,應使用兼容規範。
  3. 兼容性規範,設計用來兼容儘可能多的服務器,即使它們並沒有遵循標準規範。當解析cookie出現問題時,應考慮採用兼容性規範。

   RFC2965規範暫時沒有被HttpClient支持(在以後的版本爲會加上),它定義了cookie版本2,並說明了版本1cookie的不足,RFC2965有意有久取代rfc2109.
  在HttpClient中,有兩種方法來指定cookie規範的使用,
  1. HttpClient client = new HttpClient();
    client.getState().setCookiePolicy(CookiePolicy.COMPATIBILITY);
    這種方法設置的規範只對當前的HttpState有效,參數可取值CookiePolicy.COMPATIBILITY,CookiePolicy.NETSCAPE_DRAFT或CookiePolicy.RFC2109。
  2. System.setProperty("apache.commons.httpclient.cookiespec", "COMPATIBILITY");
    此法指的規範,對以後每個新建立的HttpState對象都有效,參數可取值"COMPATIBILITY","NETSCAPE_DRAFT"或"RFC2109"。
      常有不能解析cookie的問題,但更換到兼容規範大都能解決。
  

9、使用HttpClient遇到問題怎麼辦?


  1. 用一個瀏覽器訪問服務器,以確認服務器應答正常
  2. 如果在使代理,關掉代理試試
  3. 另找一個服務器來試試(如果運行着不同的服務器軟件更好)
  4. 檢查代碼是否按教程中講的思路編寫
  5. 設置log級別爲debug,找出問題出現的原因
  6. 打開wiretrace,來追蹤客戶端與服務器的通信,以確實問題出現在什麼地方
  7. 用telnet或netcat手工將信息發送到服務器,適合於猜測已經找到了原因而進行試驗時
  8. 將netcat以監聽方式運行,用作服務器以檢查httpclient如何處理應答的。
  9. 利用最新的httpclient試試,bug可能在最新的版本中修復了
  10. 向郵件列表求幫助
  11. 向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的步驟如下:
  1. 提供了一個實現了org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory接口的socket factory。這個 socket factory負責打一個到服務器的端口,使用標準的或第三方的SSL函數庫,並進行象連接握手等初始化操作。通常情況下,這個初始化操作在端口被創建時自動進行的。
  2. 實例化一個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);

  3. 通過調用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);

  已知的限制和問題
  1. 持續的SSL連接在Sun的低於1.4JVM上不能工作,這是由於JVM的bug造成。
  2. 通過代理訪問服務器時,非搶先認證( 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方法的設計意圖,是要以一種統一的方式實現下列功能:
  1. 對已有的資源做評註
  2. 將信息發佈到BBS、新聞組、郵件列表,或類似的文章組中
  3. 將一塊數據,提交給數據處理進程
  4. 通過追加操作,來擴展一個數據庫
  這些都操作期待着在服務器端產生一定的“副作用”,如修改了數據庫等。
  HttpClient定義PostMethod類以支持該HTTP方法,在httpclient中,使用post方法有兩個基本的步驟:爲請求包準備數據,然後讀取服務器來的應答包的信息。通過調用 setRequestBody()函數,來爲請求包提供數據,它可以接收三類參數:輸入流、名值對數組或字符串。至於讀取應答包需要調用 getResponseBody* 那一系列的方法,與GET方法處理應答包的方法相同。
  常見問題是,沒有將全部應答讀取(無論它對程序是否有用),或沒有釋放鏈接資源。
發佈了268 篇原創文章 · 獲贊 30 · 訪問量 33萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章