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來對付各類頑固的WEBserver
php
通常的狀況下咱們都是使用IE或者Navigator瀏覽器來訪問一個WEBserver,用來瀏覽頁面查看信息或者提交一些數據等等。所訪問的這些頁面有的僅僅是一些普通的頁面,有的需要用戶登陸後方可以使用,或者需要認證以及是一些經過加密方式傳輸,好比HTTPS。眼下咱們使用的瀏覽器處理這些狀況都不會構成問題。只是你可能在某些時候需要經過程序來訪問這種一些頁面,比方從別人的網頁中「偷」一些數據;利用某些站點提供的頁面來完畢某種功能,好比說咱們想知道某個手機號碼的歸屬地而咱們本身又沒有這種數據,所以僅僅好藉助其它公司已有的站點來完畢這個功能,這個時候咱們需要向網頁提交手機號碼並從返回的頁面中解析出咱們想要的數據來。假設對方僅僅是一個很是easy的頁面,那咱們的程序會很是easy,本文也就沒有必要大張旗鼓的在這裏浪費口舌。但是考慮到一些服務受權的問題,很是多公司提供的頁面每每並不是可以經過一個簡單的URL就可以訪問的,而必須通過註冊而後登陸後方可以使用提供服務的頁面,這個時候就涉及到COOKIE問題的處理。咱們知道眼下流行的動態網頁技術好比ASP、JSP無不是經過COOKIE來處理會話信息的。爲了使咱們的程序能使用別人所提供的服務頁面,就要求程序首先登陸後再訪問服務頁面,這過程就需要自行處理cookie,想一想當你用java.net.HttpURLConnection來完畢這些功能時是多麼恐怖的事情啊!何況這僅僅是咱們所說的頑固的WEBserver中的一個很是常見的「頑固」!再有如經過HTTP來上傳文件呢?不需要頭疼,這些問題有了「它」就很是easy攻克了! html
咱們不可能列舉所有可能的頑固,咱們會針對幾種最多見的問題進行處理。固然了,正如前面說到的,假設咱們本身使用java.net.HttpURLConnection來搞定這些問題是很是恐怖的事情,所以在開始以前咱們先要介紹一下一個開放源代碼的項目,這個項目就是Apache開源組織中的httpclient,它隸屬於Jakarta的commons項目,眼下的版本號是2.0RC2。commons下原本已經有一個net的子項目,但是又把httpclient單獨提出來,可見httpserver的訪問絕非易事。java
Commons-httpclient項目就是專門設計來簡化HTTP客戶端與server進行各類通信編程。經過它可以讓原來很是頭疼的事情現在輕鬆的解決,好比你再也不管是HTTP或者HTTPS的通信方式,告訴它你想使用HTTPS方式,剩下的事情交給httpclient替你完畢。本文會針對咱們在編寫HTTP客戶端程序時經常碰到的幾個問題進行分別介紹怎樣使用httpclient來解決它們,爲了讓讀者更快的熟悉這個項目咱們最開始先給出一個簡單的樣例來讀取一個網頁的內容,而後按部就班解決掉前進中的所有問題。apache
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.*; cookie
/**
*最簡單的HTTP客戶端,用來演示經過GET或者POST方式訪問某個頁面
*@authorLiudong
*/網絡
public class SimpleClient {
public static void main(String[] args) throws IOException
{
HttpClient client = new HttpClient();
// 設置代理server地址和端口 session
//client.getHostConfiguration().setProxy("proxy_host_addr",proxy_port);
// 使用 GET 方法 ,假設server需要經過 HTTPS 鏈接,那僅僅需要將如下 URL 中的 http 換成 https
HttpMethodmethod=newGetMethod("http://java.sun.com");
//使用POST方法
//HttpMethod method = new PostMethod("http://java.sun.com");
client.executeMethod(method);
//打印server返回的狀態
System.out.println(method.getStatusLine());
//打印返回的信息
System.out.println(method.getResponseBodyAsString());
//釋放鏈接
method.releaseConnection();
}
}
在這個樣例中首先建立一個HTTP客戶端(HttpClient)的實例,而後選擇提交的方法是GET或者POST,最後在HttpClient實例上運行提交的方法,最後從所選擇的提交方法中讀取server反饋回來的結果。這就是使用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); //打印server返回的狀態
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,這個參數值爲手機號碼段,即手機號碼的前七位,server會返回提交的手機號碼相應的省份、城市以及其它具體信息。GET的提交方法僅僅需要在URL後增長參數信息,而POST則需要經過NameValuePair類來設置參數名稱和它所相應的值。
3. 處理頁面重定向
在JSP/Servlet編程中response.sendRedirect方法就是使用HTTP協議中的重定向機制。它與JSP中的<jsp:forward …>的差異在於後者是在server中實現頁面的跳轉,也就是說應用容器載入了所要跳轉的頁面的內容並返回給客戶端;而前者是返回一個狀態碼,這些狀態碼的可能值見下表,而後客戶端讀取需要跳轉到的頁面的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的頁面時,應用server會返回一個參數,名爲jsessionid(因不一樣應用server而異),值是一個較長的惟一字符串的Cookie,這個字符串值也就是當前訪問該站點的會話標識。瀏覽器在每訪問該站點的其它頁面時候都要帶上jsessionid這種Cookie信息,應用server依據讀取這個會話標識來獲取相應的會話信息。
對於需要用戶登陸的站點,通常在用戶登陸成功後會將用戶資料保存在server的會話中,這樣當訪問到其它的頁面時候,應用server依據瀏覽器送上的Cookie中讀取當前請求相應的會話標識以得到相應的會話信息,而後就可以推斷用戶資料是否存在於會話信息中,假設存在則贊成訪問頁面,不然跳轉到登陸頁面中要求用戶輸入賬號和口令進行登陸。這就是通常使用JSP開發站點在處理用戶登陸的比較通用的方法。
這樣一來,對於HTTP的客戶端來說,假設要訪問一個受保護的頁面時就必須模擬瀏覽器所作的工做,首先就是請求登陸頁面,而後讀取Cookie值;再次請求登陸頁面並增長登陸頁所需的每個參數;最後就是請求終於所需的頁面。固然在除第一次請求外其它的請求都需要附帶上Cookie信息以便server能推斷當前請求是否已經經過驗證。說了這麼多,但是假設你使用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格式的參數很是easy,僅僅是一個提交時候的ContentType問題,如下的樣例演示從文件文件裏讀取XML信息並提交給server的過程,該過程可以用來測試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. 訪問啓用認證的頁面
咱們經常會碰到這種頁面,當訪問它的時候會彈出一個瀏覽器的對話框要求輸入username與password後方可,這種用戶認證的方式不一樣於咱們在前面介紹的基於表單的用戶身份驗證。這是HTTP的認證策略,httpclient支持三種認證方式包含:基本、摘要以及NTLM認證。當中基本認證最簡單、通用但也最不安全;摘要認證是在HTTP 1.1中增長的認證方式,而NTLM則是微軟公司定義的而不是通用的規範,最新版本號的NTLM是比摘要認證還要安全的一種方式。
如下樣例是從httpclient的CVSserver中下載的,它簡單演示怎樣訪問一個認證保護的頁面:
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,要使用這個類很是easy,僅僅需要在構造HttpClient實例的時候傳入就能夠,代碼例如如下:
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
之後雖然訪問client實例就能夠。
參考資料:
httpclient首頁: http://jakarta.apache.org/commons/httpclient/
關於NTLM是怎樣工做: http://davenport.sourceforge.net/ntlm.html
--------------------------------------------
HttpClient入門
http://blog.csdn.net/ambitiontan/archive/2006/01/07/572644.aspx
Jakarta Commons HttpClient 學習筆記
http://blog.csdn.net/cxl34/archive/2005/01/19/259051.aspx
Cookies,SSL,httpclient的多線程處理,HTTP方法
http://blog.csdn.net/bjbs_270/archive/2004/11/05/168233.aspx