轉:用HttpClient來模擬瀏覽器GET POST - jaddy0302

herosoft發表於2010-03-06

一般的情況下我們都是使用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來解決它們,為了 讓讀者更快的熟悉這個專案我們最開始先給出一個簡單的例子來讀取一個網頁的內容,然後循序漸進解決掉前進中的所形侍狻?/font> [@more@]

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方式訪問某個頁面

* @author Liudong

*/

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

HttpMethod method = new GetMethod("");

//使用POST方法

//HttpMethod method = new PostMethod("");

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後面用問號與頁面地址隔開,每個引數用&隔開, 例如:,但是當使用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所在的省份以及城市

* @author Liudong

*/

public class SimpleHttpClient {

public static void main(String[] args) throws IOException

{

HttpClient client = new HttpClient();

client.getHostConfiguration().setHost("", 80, "http");

HttpMethod method = getPostMethod();//使用POST方式提交資料

client.executeMethod(method);

//列印伺服器返回的狀態

System.out.println(method.getStatusLine());

//列印結果頁面

String response =

new String(method.getResponseBodyAsString().getBytes("8859_1"));

//列印返回的資訊

System.out.println(response);

method.releaseConnection();

}

/**

* 使用GET方式提交資料

* @return

*/

private static HttpMethod getGetMethod(){

return new GetMethod("/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;

}

}

在上面的例子中頁面需要一個引數是simcard,這個引數值為手機號碼段,即手機號碼的前七位,伺服器會返回提交的手機號碼對應的省份、城市以及其他詳細資訊。GET的提交方法只需要在URL後加入引數資訊,而POST則需要透過NameValuePair類來設定引數名稱和它所對應的值

3. 處理頁面重定向

在JSP/Servlet程式設計中response.sendRedirect方法就是使用HTTP協議中的重定向機 制。它與JSP中的的區別在於後者是在伺服器中實現頁面的跳轉,也就是說應用容器載入了所要跳轉的頁面的內容並返回給客戶端;而前者是返回一個狀態碼,這些狀態碼 的可能值見下表,然後客戶端讀取需要跳轉到的頁面的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地址

Header header = post.getResponseHeader("location");

if (header != null) {

String newuri = header.getValue();

if ((newuri == null) || (newuri.equals("")))

newuri = "/";

GetMethod redirect = new GetMethod(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 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

GetMethod get = new GetMethod("/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(“”);

// 設定請求的內容直接從檔案中讀取

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(

"",

"realm",

new UsernamePasswordCredentials("username", "password")

);

GetMethod get = new GetMethod("");

get.setDoAuthentication( true );

int status = client.executeMethod( get );

System.out.println(status+""+ get.getResponseBodyAsString());

get.releaseConnection();

}

}

8. 多執行緒模式下使用httpclient

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

MultiThreadedHttpConnectionManager connectionManager =

new MultiThreadedHttpConnectionManager();

HttpClient client = new HttpClient(connectionManager);

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

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/220284/viewspace-1031623/,如需轉載,請註明出處,否則將追究法律責任。

相關文章