HttpClient 學習整理

HttpClient 是我最近想研究的東西,以前想過的一些應用沒能有很好的實現,發現這個開源項目之後就有點眉目了,令人頭痛的cookie問題還是有辦法解決滴。在網上整理了一些東西,寫得很好,寄放在這裏。


HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient,更多使用 HttpClient 的應用可以參見http://wiki.apache.org/jakarta-httpclient/HttpClientPowered。HttpClient 項目非常活躍,使用的人還是非常多的。目前 HttpClient 版本是在 2005.10.11 發佈的 3.0 RC4 。

------------------------------------

應用HttpClient來對付各種頑固的WEB服務器
轉自:http://blog.csdn.net/ambitiontan/archive/2006/01/06/572171.aspx

一般的情況下我們都是使用IE或者Navigator瀏覽器來訪問一個WEB服務器,用來瀏覽頁面查看信息或者提交一些數據等等。所訪問的這些頁面有的僅僅是一些普通的頁面,有的需要用戶登錄後方可使用,或者需要認證以及是一些通過加密方式傳輸,例如HTTPS。目前我們使用的瀏覽器處理這些情況都不會構成問題。不過你可能在某些時候需要通過程序來訪問這樣的一些頁面,比如從別人的網頁中“偷”一些數據;利用某些站點提供的頁面來完成某種功能,例如說我們想知道某個手機號碼的歸屬地而我們自己又沒有這樣的數據,因此只好藉助其他公司已有的網站來完成這個功能,這個時候我們需要向網頁提交手機號碼並從返回的頁面中解析出我們想要的數據來。如果對方僅僅是一個很簡單的頁面,那我們的程序會很簡單,本文也就沒有必要大張旗鼓的在這裏浪費口舌。但是考慮到一些服務授權的問題,很多公司提供的頁面往往並不是可以通過一個簡單的URL就可以訪問的,而必須經過註冊然後登錄後方可使用提供服務的頁面,這個時候就涉及到COOKIE問題的處理。我們知道目前流行的動態網頁技術例如ASP、JSP無不是通過COOKIE來處理會話信息的。爲了使我們的程序能使用別人所提供的服務頁面,就要求程序首先登錄後再訪問服務頁面,這過程就需要自行處理cookie,想想當你用java.net.HttpURLConnection來完成這些功能時是多麼恐怖的事情啊!況且這僅僅是我們所說的頑固的WEB服務器中的一個很常見的“頑固”!再有如通過HTTP來上傳文件呢?不需要頭疼,這些問題有了“它”就很容易解決了!

我們不可能列舉所有可能的頑固,我們會針對幾種最常見的問題進行處理。當然了,正如前面說到的,如果我們自己使用java.net.HttpURLConnection來搞定這些問題是很恐怖的事情,因此在開始之前我們先要介紹一下一個開放源碼的項目,這個項目就是Apache開源組織中的httpclient,它隸屬於Jakarta的commons項目,目前的版本是2.0RC2。commons下本來已經有一個net的子項目,但是又把httpclient單獨提出來,可見http服務器的訪問絕非易事。

Commons-httpclient項目就是專門設計來簡化HTTP客戶端與服務器進行各種通訊編程。通過它可以讓原來很頭疼的事情現在輕鬆的解決,例如你不再管是HTTP或者HTTPS的通訊方式,告訴它你想使用HTTPS方式,剩下的事情交給httpclient替你完成。本文會針對我們在編寫HTTP客戶端程序時經常碰到的幾個問題進行分別介紹如何使用httpclient來解決它們,爲了讓讀者更快的熟悉這個項目我們最開始先給出一個簡單的例子來讀取一個網頁的內容,然後循序漸進解決掉前進中的所有問題。

1. 讀取網頁(HTTP/HTTPS)內容

下面是我們給出的一個簡單的例子用來訪問某個頁面

/*	
* Created on 2003-12-14 by Liudong	
*/	

package	http.demo;	
import	java.io.IOException;	
import	org.apache.commons.httpclient.*;	
import	org.apache.commons.httpclient.methods.*;	

/** 
 *最簡單的HTTP客戶端,用來演示通過GET或者POST方式訪問某個頁面
  *@authorLiudong
*/

public	class	SimpleClient	{
public	static	void	main(String[]	args)	throws	IOException	
{
 	HttpClient	client	=	new	HttpClient();	
      //	設置代理服務器地址和端口	 	   

      //client.getHostConfiguration().setProxy("proxy_host_addr",proxy_port); 
      //	使用	GET	方法	,如果服務器需要通過	HTTPS	連接,那只需要將下面	URL	中的	http	換成	https	
         HttpMethodmethod=newGetMethod("http://java.sun.com");
      //使用POST方法
      //HttpMethod method = new PostMethod("http://java.sun.com");
      client.executeMethod(method);

      //打印服務器返回的狀態
       System.out.println(method.getStatusLine());
      //打印返回的信息
      System.out.println(method.getResponseBodyAsString());
      //釋放連接
      method.releaseConnection();
   }
}

在這個例子中首先創建一個HTTP客戶端(HttpClient)的實例,然後選擇提交的方法是GET或者POST,最後在HttpClient實例上執行提交的方法,最後從所選擇的提交方法中讀取服務器反饋回來的結果。這就是使用HttpClient的基本流程。其實用一行代碼也就可以搞定整個請求的過程,非常的簡單!


2. 以GET或者POST方式向網頁提交參數

其實前面一個最簡單的示例中我們已經介紹瞭如何使用GET或者POST方式來請求一個頁面,本小節與之不同的是多了提交時設定頁面所需的參數,我們知道如果是GET的請求方式,那麼所有參數都直接放到頁面的URL後面用問號與頁面地址隔開,每個參數用&隔開,例如:http://java.sun.com/?name=liudong&mobile=123456,但是當使用POST方法時就會稍微有一點點麻煩。本小節的例子演示向如何查詢手機號碼所在的城市,代碼如下:

	/*	
* Created on 2003-12-7 by Liudong 
*/

package	http.demo;	
import	java.io.IOException;	
import	org.apache.commons.httpclient.*;	
import	org.apache.commons.httpclient.methods.*;	

/** 
 *提交參數演示
 *該程序連接到一個用於查詢手機號碼所屬地的頁面
 *以便查詢號碼段1330227所在的省份以及城市
 *@authorLiudong
 */

public	class	SimpleHttpClient	{ 
   public	static	void	main(String[]	args)	throws	IOException	{
      HttpClient	client	=	new	HttpClient();
      client.getHostConfiguration().setHost(	"www.imobile.com.cn"	,	80,	"http"	);
      method	=	getPostMethod();    //	使用	POST	方式提交數據 
      client.executeMethod(method);   //打印服務器返回的狀態 
      System.out.println(method.getStatusLine());   //打印結果頁面
      Stringresponse=newString(method.getResponseBodyAsString().getBytes("8859_1"));

      //打印返回的信息
      System.out.println(response);
      method.releaseConnection();
   }

   /** 
    *	使用	GET	方式提交數據 
    *@return 
    */

   privatestaticHttpMethodgetGetMethod(){
      returnnewGetMethod("/simcard.php?simcard=1330227");
   }

    /** 
     *	使用	POST	方式提交數據 
     *@return 
     */

    private	static	HttpMethod	getPostMethod(){
      PostMethod	post	=	new	PostMethod(	"/simcard.php"	);
      NameValuePair	simcard	=	new	NameValuePair(	"simcard"	,	"1330227"	);
      post.setRequestBody(	new	NameValuePair[]	{	simcard});
      return	post;	
   }	
}	

在上面的例子中頁面http://www.imobile.com.cn/simcard.php需要一個參數是simcard,這個參數值爲手機號碼段,即手機號碼的前七位,服務器會返回提交的手機號碼對應的省份、城市以及其他詳細信息。GET的提交方法只需要在URL後加入參數信息,而POST則需要通過NameValuePair類來設置參數名稱和它所對應的值。


3. 處理頁面重定向

在JSP/Servlet編程中response.sendRedirect方法就是使用HTTP協議中的重定向機制。它與JSP中的<jsp:forward …>的區別在於後者是在服務器中實現頁面的跳轉,也就是說應用容器加載了所要跳轉的頁面的內容並返回給客戶端;而前者是返回一個狀態碼,這些狀態碼的可能值見下表,然後客戶端讀取需要跳轉到的頁面的URL並重新加載新的頁面。就是這樣一個過程,所以我們編程的時候就要通過HttpMethod.getStatusCode()方法判斷返回值是否爲下表中的某個值來判斷是否需要跳轉。如果已經確認需要進行頁面跳轉了,那麼可以通過讀取HTTP頭中的location屬性來獲取新的地址。

狀態碼

對應 HttpServletResponse 的常量

詳細描述

301

SC_MOVED_PERMANENTLY

頁面已經永久移到另外一個新地址

302

SC_MOVED_TEMPORARILY

頁面暫時移動到另外一個新的地址

303

SC_SEE_OTHER

客戶端請求的地址必須通過另外的 URL 來訪問

307

SC_TEMPORARY_REDIRECT

同 SC_MOVED_TEMPORARILY

下面的代碼片段演示如何處理頁面的重定向

client.executeMethod(post);
System.out.println(post.getStatusLine().toString());
post.releaseConnection();
//	檢查是否重定向
int statuscode=post.getStatusCode();
if((statuscode==HttpStatus.SC_MOVED_TEMPORARILY)||(statuscode==HttpStatus.SC_MOVED_PERMANENTLY)||
(statuscode==HttpStatus.SC_SEE_OTHER)||(statuscode	==	HttpStatus.SC_TEMPORARY_REDIRECT)){
//	讀取新的URL地址 
   Headerheader=post.getResponseHeader("location");
   if (header!=null){
      Stringnewuri=header.getValue();
      if((newuri==null)||(newuri.equals("")))
         newuri="/";
         GetMethodredirect=newGetMethod(newuri);
         client.executeMethod(redirect);
         System.out.println("Redirect:"+redirect.getStatusLine().toString());
         redirect.releaseConnection();
   }else 
    System.out.println("Invalid redirect");
}

我們可以自行編寫兩個JSP頁面,其中一個頁面用response.sendRedirect方法重定向到另外一個頁面用來測試上面的例子。


4. 模擬輸入用戶名和口令進行登錄

本小節應該說是HTTP客戶端編程中最常碰見的問題,很多網站的內容都只是對註冊用戶可見的,這種情況下就必須要求使用正確的用戶名和口令登錄成功後,方可瀏覽到想要的頁面。因爲HTTP協議是無狀態的,也就是連接的有效期只限於當前請求,請求內容結束後連接就關閉了。在這種情況下爲了保存用戶的登錄信息必須使用到Cookie機制。以JSP/Servlet爲例,當瀏覽器請求一個JSP或者是Servlet的頁面時,應用服務器會返回一個參數,名爲jsessionid(因不同應用服務器而異),值是一個較長的唯一字符串的Cookie,這個字符串值也就是當前訪問該站點的會話標識。瀏覽器在每訪問該站點的其他頁面時候都要帶上jsessionid這樣的Cookie信息,應用服務器根據讀取這個會話標識來獲取對應的會話信息。

對於需要用戶登錄的網站,一般在用戶登錄成功後會將用戶資料保存在服務器的會話中,這樣當訪問到其他的頁面時候,應用服務器根據瀏覽器送上的Cookie中讀取當前請求對應的會話標識以獲得對應的會話信息,然後就可以判斷用戶資料是否存在於會話信息中,如果存在則允許訪問頁面,否則跳轉到登錄頁面中要求用戶輸入帳號和口令進行登錄。這就是一般使用JSP開發網站在處理用戶登錄的比較通用的方法。

這樣一來,對於HTTP的客戶端來講,如果要訪問一個受保護的頁面時就必須模擬瀏覽器所做的工作,首先就是請求登錄頁面,然後讀取Cookie值;再次請求登錄頁面並加入登錄頁所需的每個參數;最後就是請求最終所需的頁面。當然在除第一次請求外其他的請求都需要附帶上Cookie信息以便服務器能判斷當前請求是否已經通過驗證。說了這麼多,可是如果你使用httpclient的話,你甚至連一行代碼都無需增加,你只需要先傳遞登錄信息執行登錄過程,然後直接訪問想要的頁面,跟訪問一個普通的頁面沒有任何區別,因爲類HttpClient已經幫你做了所有該做的事情了,太棒了!下面的例子實現了這樣一個訪問的過程。

/*	
* Created on 2003-12-7 by Liudong	
*/

package	http.demo;	
import	org.apache.commons.httpclient.*;
import	org.apache.commons.httpclient.cookie.*;
import	org.apache.commons.httpclient.methods.*;	

/**	
 *	用來演示登錄表單的示例	
 *	@author	Liudong	
 */	

public	class	FormLoginDemo	{
   static	final	String	LOGON_SITE	=	"localhost"	;
   static	final	int	    LOGON_PORT	=	8080;

   public	static	void	main(String[]	args)	throws	Exception{
      HttpClient	client	=	new	HttpClient();
      client.getHostConfiguration().setHost(LOGON_SITE,	LOGON_PORT);

      //	模擬登錄頁面	login.jsp->main.jsp
      PostMethod	post	=	new	PostMethod(	"/main.jsp"	);
      NameValuePair	name	=	new	NameValuePair(	"name"	,	"ld"	);
      NameValuePair	pass	=	new	NameValuePair(	"password"	,	"ld"	);
      post.setRequestBody(	new	NameValuePair[]{name,pass});
      int	status	=	client.executeMethod(post);
      System.out.println(post.getResponseBodyAsString());
      post.releaseConnection();

      //	查看	cookie	信息
      CookieSpec	cookiespec	=	CookiePolicy.getDefaultSpec();
      Cookie[]	cookies	=	cookiespec.match(LOGON_SITE,LOGON_PORT,"/",false,client.getState().getCookies());
      if	(cookies.length	==	0)	{
         System.out.println(	"None"	);
      }	else	{
         for	(	int	i	=	0;	i	<	cookies.length;	i++)	{
            System.out.println(cookies[i].toString());
         }
      }

      //	訪問所需的頁面	main2.jsp 
      GetMethodget=newGetMethod("/main2.jsp");
      client.executeMethod(get);
      System.out.println(get.getResponseBodyAsString());
      get.releaseConnection();
   }
}


5. 提交XML格式參數

提交XML格式的參數很簡單,僅僅是一個提交時候的ContentType問題,下面的例子演示從文件文件中讀取XML信息並提交給服務器的過程,該過程可以用來測試Web服務。

	import	java.io.File;	
import	java.io.FileInputStream;	
import	org.apache.commons.httpclient.HttpClient;	
import	org.apache.commons.httpclient.methods.EntityEnclosingMethod;	
import	org.apache.commons.httpclient.methods.PostMethod;	

/** 
 *用來演示提交XML格式數據的例子
*/

public	class	PostXMLClient	{

   public	static	void	main(String[]	args)	throws	Exception	{
      File	input	=	new	File(“test.xml”);
      PostMethod	post	=	new	PostMethod(“http://localhost:8080/httpclient/xml.jsp”);

      // 設置請求的內容直接從文件中讀取
      post.setRequestBody(	new	FileInputStream(input)); 
      if	(input.length()	<	Integer.MAX_VALUE)
         post.setRequestContentLength(input.length());
      else
         post.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);

      // 指定請求內容的類型
      post.setRequestHeader(	"Content-type"	,	"text/xml; charset=GBK"	);
      HttpClient	httpclient	=	new	HttpClient();
      int	result	=	httpclient.executeMethod(post);
      System.out.println(	"Response status code: "	+	result);
      System.out.println(	"Response body: "	);
      System.out.println(post.getResponseBodyAsString()); 
      post.releaseConnection(); 
   }	
}


6. 通過HTTP上傳文件

httpclient使用了單獨的一個HttpMethod子類來處理文件的上傳,這個類就是MultipartPostMethod,該類已經封裝了文件上傳的細節,我們要做的僅僅是告訴它我們要上傳文件的全路徑即可,下面的代碼片段演示如何使用這個類。

MultipartPostMethod filePost = new MultipartPostMethod(targetURL);	
filePost.addParameter("fileName",targetFilePath);	
HttpClient client = new HttpClient();	

// 由於要上傳的文件可能比較大,因此在此設置最大的連接超時時間	
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);	
int status = client.executeMethod(filePost);	

上面代碼中,targetFilePath即爲要上傳的文件所在的路徑。


7. 訪問啓用認證的頁面

我們經常會碰到這樣的頁面,當訪問它的時候會彈出一個瀏覽器的對話框要求輸入用戶名和密碼後方可,這種用戶認證的方式不同於我們在前面介紹的基於表單的用戶身份驗證。這是HTTP的認證策略,httpclient支持三種認證方式包括:基本、摘要以及NTLM認證。其中基本認證最簡單、通用但也最不安全;摘要認證是在HTTP 1.1中加入的認證方式,而NTLM則是微軟公司定義的而不是通用的規範,最新版本的NTLM是比摘要認證還要安全的一種方式。

下面例子是從httpclient的CVS服務器中下載的,它簡單演示如何訪問一個認證保護的頁面:

import	org.apache.commons.httpclient.HttpClient;	
import	org.apache.commons.httpclient.UsernamePasswordCredentials;	
import	org.apache.commons.httpclient.methods.GetMethod;	

public	class	BasicAuthenticationExample	{ 

   public	BasicAuthenticationExample()	{ 
   }

   public	static	void	main(String[]	args)	throws	Exception	{
      HttpClient	client	=	new	HttpClient();
      client.getState().setCredentials(	"www.verisign.com","realm"	,new UsernamePasswordCredentials("username","password"));

      GetMethod	get	=	new	GetMethod(	"https://www.verisign.com/products/index.html"	);
      get.setDoAuthentication(	true	);
      int	status	=	client.executeMethod(	get	);
      System.out.println(status+	"\n"	+	get.getResponseBodyAsString());
      get.releaseConnection();
   }	
}


8. 多線程模式下使用httpclient

多線程同時訪問httpclient,例如同時從一個站點上下載多個文件。對於同一個HttpConnection同一個時間只能有一個線程訪問,爲了保證多線程工作環境下不產生衝突,httpclient使用了一個多線程連接管理器的類:MultiThreadedHttpConnectionManager,要使用這個類很簡單,只需要在構造HttpClient實例的時候傳入即可,代碼如下:

MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);	

以後儘管訪問client實例即可。


文章轉自:http://www.blogjava.net/Alpha/archive/2007/01/22/95216.html

httpclient首頁:    http://hc.apache.org/httpclient-3.x/userguide.html
httpclient入門:http://www.ibm.com/developerworks/cn/opensource/os-httpclient/
httpclient使用之七種武器:http://laohuang.iteye.com/blog/55613
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章