HttpClient簡介
HttpClient 功能介紹
1. 讀取網頁(HTTP/HTTPS)內容
2、使用POST方式提交數據(httpClient3)
3. 處理頁面重定向
4. 模擬登錄開心網
5. 提交XML格式參數
6. 訪問啓用認證的頁面
7. 多線程模式下使用httpclient
httpClient完整封裝
HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient。更多信息請關注http://hc.apache.org/
以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。
-
實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
-
支持自動轉向
-
支持 HTTPS 協議
-
支持代理服務器等
應用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來解決它們,爲了讓讀者更快的熟悉這個項目我們最開始先給出一個簡單的例子來讀取一個網頁的內容,然後循序漸進解決掉前進中的所有問題。
下面是我們給出的一個簡單的例子用來訪問某個頁面
/**
*最簡單的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
HttpMethod method=new GetMethod("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的基本流程。其實用一行代碼也就可以搞定整個請求的過程,非常的簡單!
httpclient使用了單獨的一個HttpMethod子類來處理文件的上傳,這個類就是MultipartPostMethod,該類已經封裝了文件上傳的細節,我們要做的僅僅是告訴它我們要上傳文件的全路徑即可,下面這裏將給出關於兩種模擬上傳方式的代碼
第一種:模擬上傳url文件(該方式也適合做普通post請求):
/**
* 上傳url文件到指定URL
* @param fileUrl 上傳圖片url
* @param postUrl 上傳路徑及參數,注意有些中文參數需要使用預先編碼 eg : URLEncoder.encode(appName, "UTF-8")
* @return
* @throws IOException
*/
public static String doUploadFile(String postUrl) throws IOException {
if(StringUtils.isEmpty(postUrl))
return null;
String response = "";
PostMethod postMethod = new PostMethod(postUrl);
try {
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(50000);// 設置連接時間
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
response = stringBuffer.toString();
} else {
response = "fail";
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放連接
postMethod.releaseConnection();
}
return response;
}
第二種:模擬文件上傳到指定位置
/**
* 上傳文件到指定URL
* @param file
* @param url
* @return
* @throws IOException
*/
public static String doUploadFile(File file, String url) throws IOException {
String response = "";
if (!file.exists()) {
return "file not exists";
}
PostMethod postMethod = new PostMethod(url);
try {
//----------------------------------------------
// FilePart:用來上傳文件的類,file即要上傳的文件
FilePart fp = new FilePart("file", file);
Part[] parts = { fp };
// 對於MIME類型的請求,httpclient建議全用MulitPartRequestEntity進行包裝
MultipartRequestEntity mre = new MultipartRequestEntity(parts,
postMethod.getParams());
postMethod.setRequestEntity(mre);
//---------------------------------------------
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(50000);// 由於要上傳的文件可能比較大 , 因此在此設置最大的連接超時時間
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
response = stringBuffer.toString();
} else {
response = "fail";
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放連接
postMethod.releaseConnection();
}
return response;
}
在JSP/Servlet編程中response.sendRedirect方法就是使用HTTP協議中的重定向機制。它與JSP中的<jsp:forward …>的區別在於後者是在服務器中實現頁面的跳轉,也就是說應用容器加載了所要跳轉的頁面的內容並返回給客戶端;而前者是返回一個狀態碼,這些狀態碼的可能值見下表,然後客戶端讀取需要跳轉到的頁面的URL並重新加載新的頁面。就是這樣一個過程,所以我們編程的時候就要通過HttpMethod.getStatusCode()方法判斷返回值是否爲下表中的某個值來判斷是否需要跳轉。如果已經確認需要進行頁面跳轉了,那麼可以通過讀取HTTP頭中的location屬性來獲取新的地址。
下面的代碼片段演示如何處理頁面的重定向
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){
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方法重定向到另外一個頁面用來測試上面的例子。
本小節應該說是HTTP客戶端編程中最常碰見的問題,很多網站的內容都只是對註冊用戶可見的,這種情況下就必須要求使用正確的用戶名和口令登錄成功後,方可瀏覽到想要的頁面。因爲HTTP協議是無狀態的,也就是連接的有效期只限於當前請求,請求內容結束後連接就關閉了。在這種情況下爲了保存用戶的登錄信息必須使用到Cookie機制。以JSP/Servlet爲例,當瀏覽器請求一個JSP或者是Servlet的頁面時,應用服務器會返回一個參數,名爲jsessionid(因不同應用服務器而異),值是一個較長的唯一字符串的Cookie,這個字符串值也就是當前訪問該站點的會話標識。瀏覽器在每訪問該站點的其他頁面時候都要帶上jsessionid這樣的Cookie信息,應用服務器根據讀取這個會話標識來獲取對應的會話信息。
對於需要用戶登錄的網站,一般在用戶登錄成功後會將用戶資料保存在服務器的會話中,這樣當訪問到其他的頁面時候,應用服務器根據瀏覽器送上的Cookie中讀取當前請求對應的會話標識以獲得對應的會話信息,然後就可以判斷用戶資料是否存在於會話信息中,如果存在則允許訪問頁面,否則跳轉到登錄頁面中要求用戶輸入帳號和口令進行登錄。這就是一般使用JSP開發網站在處理用戶登錄的比較通用的方法。
這樣一來,對於HTTP的客戶端來講,如果要訪問一個受保護的頁面時就必須模擬瀏覽器所做的工作,首先就是請求登錄頁面,然後讀取Cookie值;再次請求登錄頁面並加入登錄頁所需的每個參數;最後就是請求最終所需的頁面。當然在除第一次請求外其他的請求都需要附帶上Cookie信息以便服務器能判斷當前請求是否已經通過驗證。說了這麼多,可是如果你使用httpclient的話,你甚至連一行代碼都無需增加,你只需要先傳遞登錄信息執行登錄過程,然後直接訪問想要的頁面,跟訪問一個普通的頁面沒有任何區別,因爲類HttpClient已經幫你做了所有該做的事情了,太棒了!下面的例子實現了模擬登陸開心網並向自己好友發送消息的功能。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
class Login {
public static String loginurl = "https://security.kaixin001.com/login/login_post.php";
static Cookie[] cookies = {};
static HttpClient httpClient = new HttpClient();
static String email = "[email protected]";//你的email
static String psw = "xxx";//你的密碼
// 消息發送的action
String url = "http://www.kaixin001.com/home/";
public static void getUrlContent()
throws Exception {
HttpClientParams httparams = new HttpClientParams();
httparams.setSoTimeout(30000);
httpClient.setParams(httparams);
httpClient.getHostConfiguration().setHost("www.kaixin001.com", 80);
httpClient.getParams().setParameter(
HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod login = new PostMethod(loginurl);
login.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8");
NameValuePair Email = new NameValuePair("loginemail", email);// 郵箱
NameValuePair password = new NameValuePair("password", psw);// 密碼
// NameValuePair code = new NameValuePair( "code"
// ,"????");//有時候需要驗證碼,暫時未解決
NameValuePair[] data = { Email, password };
login.setRequestBody(data);
httpClient.executeMethod(login);
int statuscode = login.getStatusCode();
System.out.println(statuscode + "-----------");
String result = login.getResponseBodyAsString();
System.out.println(result+"++++++++++++");
cookies = httpClient.getState().getCookies();
System.out.println("==========Cookies============");
int i = 0;
for (Cookie c : cookies) {
System.out.println(++i + ": " + c);
}
httpClient.getState().addCookies(cookies);
// 當state爲301或者302說明登陸頁面跳轉了,登陸成功了
if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY)
|| (statuscode == HttpStatus.SC_MOVED_PERMANENTLY)
|| (statuscode == HttpStatus.SC_SEE_OTHER)
|| (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) {
// 讀取新的 URL 地址
Header header = login.getResponseHeader("location");
// 釋放連接
login.releaseConnection();
System.out.println("獲取到跳轉header>>>" + header);
if (header != null) {
String newuri = header.getValue();
if ((newuri == null) || (newuri.equals("")))
newuri = "/";
GetMethod redirect = new GetMethod(newuri);
// ////////////
redirect.setRequestHeader("Cookie", cookies.toString());
httpClient.executeMethod(redirect);
System.out.println("Redirect:"
+ redirect.getStatusLine().toString());
redirect.releaseConnection();
} else
System.out.println("Invalid redirect");
} else {
// 用戶名和密碼沒有被提交,當登陸多次後需要驗證碼的時候會出現這種未提交情況
System.out.println("用戶沒登陸");
System.exit(1);
}
}
public static void sendMsg() throws Exception {
// 登錄後發消息
System.out.println("*************發消息***********");
String posturl = "http://www.kaixin001.com/msg/post.php";
PostMethod poster = new PostMethod(posturl);
poster.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8");
poster.setRequestHeader("Cookie", cookies.toString());
NameValuePair uids = new NameValuePair("uids", "89600585");// 發送的好友對象的id,此處換成你的好友id
NameValuePair content = new NameValuePair("content", "你好啊!");// 需要發送的信息的內容
NameValuePair liteeditor_0 = new NameValuePair("liteeditor_0", "你好啊!");// 需要發送的信息的內容
NameValuePair texttype = new NameValuePair("texttype", "plain");
NameValuePair send_separate = new NameValuePair("send_separate", "0");
NameValuePair service = new NameValuePair("service", "0");
NameValuePair[] msg = { uids, content, texttype, send_separate, service,liteeditor_0 };
poster.setRequestBody(msg);
httpClient.executeMethod(poster);
String result = poster.getResponseBodyAsString();
System.out.println(result+"++++++++++++");
//System.out.println(StreamOut(result, "iso8859-1"));
int statuscode = poster.getStatusCode();
System.out.println(statuscode + "-----------");
if(statuscode == 301 || statuscode == 302){
// 讀取新的 URL 地址
Header header = poster.getResponseHeader("location");
System.out.println("獲取到跳轉header>>>" + header);
if (header != null) {
String newuri = header.getValue();
if ((newuri == null) || (newuri.equals("")))
newuri = "/";
GetMethod redirect = new GetMethod(newuri);
// ////////////
redirect.setRequestHeader("Cookie", cookies.toString());
httpClient.executeMethod(redirect);
System.out.println("Redirect:"
+ redirect.getStatusLine().toString());
redirect.releaseConnection();
} else
System.out.println("Invalid redirect");
}
poster.releaseConnection();
}
public static String StreamOut(InputStream txtis, String code)
throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(txtis,
code));
String tempbf;
StringBuffer html = new StringBuffer(100);
while ((tempbf = br.readLine()) != null) {
html.append(tempbf + "\n");
}
return html.toString();
}
}
提交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();
}
}
我們經常會碰到這樣的頁面,當訪問它的時候會彈出一個瀏覽器的對話框要求輸入用戶名和密碼後方可,這種用戶認證的方式不同於我們在前面介紹的基於表單的用戶身份驗證。這是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();
}
}
多線程同時訪問httpclient,例如同時從一個站點上下載多個文件。對於同一個HttpConnection同一個時間只能有一個線程訪問,爲了保證多線程工作環境下不產生衝突,httpclient使用了一個多線程連接管理器的類:MultiThreadedHttpConnectionManager,要使用這個類很簡單,只需要在構造HttpClient實例的時候傳入即可,代碼如下:
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
以後儘管訪問client實例即可。
HttpInvoke.java:封裝了HttpClient調度的必要參數設置,以及post,get等常用方法
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import java.util.Iterator;
import java.util.Map;
import java.net.SocketTimeoutException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class HttpInvoker {
private Log logger = LogFactory.getLog(HttpInvoker.class);
private static HttpInvoker httpInvoker = new HttpInvoker();
private HttpClient client = null;
private String charset = "gbk";
private int timeout = 10000;
private boolean useProxy = false;
private String proxyHost = null;
private int proxyPort;
private String proxyUsername = null;
private String proxyPassword = null;
private boolean initialized = false;
public static HttpInvoker getInstance() {
return httpInvoker;
}
private HttpInvoker() {
client = new HttpClient(new MultiThreadedHttpConnectionManager());
client.getParams().setParameter("http.protocol.content-charset", "gbk");
client.getParams().setContentCharset("gbk");
client.getParams().setSoTimeout(timeout);
}
public HttpInvoker(String charset, int timeout, boolean useProxy,
String proxyHost, int proxyPort, String proxyUsername,
String proxyPassword) {
client = new HttpClient(new MultiThreadedHttpConnectionManager());
if(charset != null && !charset.trim().equals("")) {
this.charset = charset;
}
if(timeout > 0) {
this.timeout = timeout;
}
client.getParams().setParameter("http.protocol.content-charset", charset);
client.getParams().setContentCharset(charset);
client.getParams().setSoTimeout(timeout);
if(useProxy && proxyHost != null &&
!proxyHost.trim().equals("") && proxyPort > 0) {
HostConfiguration hc = new HostConfiguration();
hc.setProxy(proxyHost, proxyPort);
client.setHostConfiguration(hc);
if (proxyUsername != null && !proxyUsername.trim().equals("") &&
proxyPassword != null && !proxyPassword.trim().equals("")) {
client.getState().setProxyCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
}
initialized = true;
logger.debug("HttpInvoker初始化完成");
}
public synchronized void init() {
if(charset != null && !charset.trim().equals("")) {
client.getParams().setParameter("http.protocol.content-charset", charset);
client.getParams().setContentCharset(charset);
}
if(timeout > 0) {
client.getParams().setSoTimeout(timeout);
}
if(useProxy && proxyHost != null &&
!proxyHost.trim().equals("") && proxyPort > 0) {
HostConfiguration hc = new HostConfiguration();
hc.setProxy(proxyHost, proxyPort);
client.setHostConfiguration(hc);
if (proxyUsername != null && !proxyUsername.trim().equals("") &&
proxyPassword != null && !proxyPassword.trim().equals("")) {
client.getState().setProxyCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
}
initialized = true;
logger.debug("HttpInvoker初始化完成");
}
public String invoke(String url) throws Exception {
return invoke(url, null, false);
}
public String invoke(String url, Map params, boolean isPost) throws Exception {
logger.debug("HTTP調用[" + (isPost?"POST":"GET") + "][" + url + "][" + params + "]");
HttpMethod httpMethod = null;
String result = "";
try {
if(isPost && params != null && params.size() > 0) {
Iterator paramKeys = params.keySet().iterator();
httpMethod = new PostMethod(url);
NameValuePair[] form = new NameValuePair[params.size()];
int formIndex = 0;
while(paramKeys.hasNext()) {
String key = (String)paramKeys.next();
Object value = params.get(key);
if(value != null && value instanceof String && !value.equals("")) {
form[formIndex] = new NameValuePair(key, (String)value);
formIndex++;
} else if(value != null && value instanceof String[] &&
((String[])value).length > 0) {
NameValuePair[] tempForm =
new NameValuePair[form.length + ((String[])value).length - 1];
for(int i=0; i<formIndex; i++) {
tempForm[i] = form[i];
}
form = tempForm;
for(String v : (String[])value) {
form[formIndex] = new NameValuePair(key, (String)v);
formIndex++;
}
}
}
((PostMethod)httpMethod).setRequestBody(form);
} else {
if(params != null && params.size() > 0) {
Iterator paramKeys = params.keySet().iterator();
StringBuffer getUrl = new StringBuffer(url.trim());
if(url.trim().indexOf("?") > -1) {
if(url.trim().indexOf("?") < url.trim().length()-1 &&
url.trim().indexOf("&") < url.trim().length()-1) {
getUrl.append("&");
}
} else {
getUrl.append("?");
}
while(paramKeys.hasNext()) {
String key = (String)paramKeys.next();
Object value = params.get(key);
if(value != null && value instanceof String && !value.equals("")) {
getUrl.append(key).append("=").append(value).append("&");
} else if(value != null && value instanceof String[] &&
((String[])value).length > 0) {
for(String v : (String[])value) {
getUrl.append(key).append("=").append(v).append("&");
}
}
}
if(getUrl.lastIndexOf("&") == getUrl.length()-1) {
httpMethod = new GetMethod(getUrl.substring(0, getUrl.length()-1));
} else {
httpMethod = new GetMethod(getUrl.toString());
}
} else {
httpMethod = new GetMethod(url);
}
}
client.executeMethod(httpMethod);
// result = httpMethod.getResponseBodyAsString();
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpMethod.getResponseBodyAsStream(),"ISO-8859-1"));
String line = null;
String html = null;
while((line = reader.readLine()) != null){
if(html == null) {
html = "";
} else {
html += "\r\n";
}
html += line;
}
if(html != null) {
result = new String(html.getBytes("ISO-8859-1"), charset);
}
} catch (SocketTimeoutException e) {
logger.error("連接超時[" + url + "]");
throw e;
} catch (java.net.ConnectException e) {
logger.error("連接失敗[" + url + "]");
throw e;
} catch (Exception e) {
logger.error("連接時出現異常[" + url + "]");
throw e;
} finally {
if (httpMethod != null) {
try {
httpMethod.releaseConnection();
} catch (Exception e) {
logger.error("釋放網絡連接失敗[" + url + "]");
throw e;
}
}
}
return result;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
}
public void setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
}
public void setProxyUsername(String proxyUsername) {
this.proxyUsername = proxyUsername;
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
}
public void setUseProxy(boolean useProxy) {
this.useProxy = useProxy;
}
public synchronized boolean isInitialized() {
return initialized;
}
}
http訪問網絡的代理ip和端口,還有使用用戶及密碼都可以在Spring容器中注入進來:
<bean id="httpInvoker" class="HttpInvoker">
<constructor-arg type="java.lang.String" value="gbk" /><!--useProxy-->
<constructor-arg type="int" value="10000" /><!--useProxy-->
<constructor-arg type="boolean" value="true" /><!--useProxy-->
<!--代理地址 -->
<constructor-arg type="java.lang.String" value="192.168.1.1" />
<constructor-arg type="int" value="8080" />
<constructor-arg type="java.lang.String" value="" /><!--用戶名-->
<constructor-arg type="java.lang.String" value="" /><!--密碼-->
</bean>
使用方式:post
Map<String,String> params = new HashMap<String,String>();
params.put("check", check);
String result = httpInvoker.invoke( "someURL", params, true);
使用方式:get
String content = httpInvoker.invoke(url);
參考資料:
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
原地址:https://www.cnblogs.com/ITtangtang/p/3968093.html#a6
HttpClient簡介
HttpClient 功能介紹
1. 讀取網頁(HTTP/HTTPS)內容
2、使用POST方式提交數據(httpClient3)
3. 處理頁面重定向
4. 模擬登錄開心網
5. 提交XML格式參數
6. 訪問啓用認證的頁面
7. 多線程模式下使用httpclient
httpClient完整封裝
HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程序需要直接通過 HTTP 協議來訪問網絡資源。雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程序來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子項目,用來提供高效的、最新的、功能豐富的支持 HTTP 協議的客戶端編程工具包,並且它支持 HTTP 協議最新的版本和建議。HttpClient 已經應用在很多的項目中,比如 Apache Jakarta 上很著名的另外兩個開源項目 Cactus 和 HTMLUnit 都使用了 HttpClient。更多信息請關注http://hc.apache.org/
以下列出的是 HttpClient 提供的主要的功能,要知道更多詳細的功能可以參見 HttpClient 的主頁。
-
實現了所有 HTTP 的方法(GET,POST,PUT,HEAD 等)
-
支持自動轉向
-
支持 HTTPS 協議
-
支持代理服務器等
應用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來解決它們,爲了讓讀者更快的熟悉這個項目我們最開始先給出一個簡單的例子來讀取一個網頁的內容,然後循序漸進解決掉前進中的所有問題。
下面是我們給出的一個簡單的例子用來訪問某個頁面
/**
*最簡單的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
HttpMethod method=new GetMethod("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的基本流程。其實用一行代碼也就可以搞定整個請求的過程,非常的簡單!
httpclient使用了單獨的一個HttpMethod子類來處理文件的上傳,這個類就是MultipartPostMethod,該類已經封裝了文件上傳的細節,我們要做的僅僅是告訴它我們要上傳文件的全路徑即可,下面這裏將給出關於兩種模擬上傳方式的代碼
第一種:模擬上傳url文件(該方式也適合做普通post請求):
/**
* 上傳url文件到指定URL
* @param fileUrl 上傳圖片url
* @param postUrl 上傳路徑及參數,注意有些中文參數需要使用預先編碼 eg : URLEncoder.encode(appName, "UTF-8")
* @return
* @throws IOException
*/
public static String doUploadFile(String postUrl) throws IOException {
if(StringUtils.isEmpty(postUrl))
return null;
String response = "";
PostMethod postMethod = new PostMethod(postUrl);
try {
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(50000);// 設置連接時間
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
response = stringBuffer.toString();
} else {
response = "fail";
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放連接
postMethod.releaseConnection();
}
return response;
}
第二種:模擬文件上傳到指定位置
/**
* 上傳文件到指定URL
* @param file
* @param url
* @return
* @throws IOException
*/
public static String doUploadFile(File file, String url) throws IOException {
String response = "";
if (!file.exists()) {
return "file not exists";
}
PostMethod postMethod = new PostMethod(url);
try {
//----------------------------------------------
// FilePart:用來上傳文件的類,file即要上傳的文件
FilePart fp = new FilePart("file", file);
Part[] parts = { fp };
// 對於MIME類型的請求,httpclient建議全用MulitPartRequestEntity進行包裝
MultipartRequestEntity mre = new MultipartRequestEntity(parts,
postMethod.getParams());
postMethod.setRequestEntity(mre);
//---------------------------------------------
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams()
.setConnectionTimeout(50000);// 由於要上傳的文件可能比較大 , 因此在此設置最大的連接超時時間
int status = client.executeMethod(postMethod);
if (status == HttpStatus.SC_OK) {
InputStream inputStream = postMethod.getResponseBodyAsStream();
BufferedReader br = new BufferedReader(new InputStreamReader(
inputStream));
StringBuffer stringBuffer = new StringBuffer();
String str = "";
while ((str = br.readLine()) != null) {
stringBuffer.append(str);
}
response = stringBuffer.toString();
} else {
response = "fail";
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放連接
postMethod.releaseConnection();
}
return response;
}
在JSP/Servlet編程中response.sendRedirect方法就是使用HTTP協議中的重定向機制。它與JSP中的<jsp:forward …>的區別在於後者是在服務器中實現頁面的跳轉,也就是說應用容器加載了所要跳轉的頁面的內容並返回給客戶端;而前者是返回一個狀態碼,這些狀態碼的可能值見下表,然後客戶端讀取需要跳轉到的頁面的URL並重新加載新的頁面。就是這樣一個過程,所以我們編程的時候就要通過HttpMethod.getStatusCode()方法判斷返回值是否爲下表中的某個值來判斷是否需要跳轉。如果已經確認需要進行頁面跳轉了,那麼可以通過讀取HTTP頭中的location屬性來獲取新的地址。
下面的代碼片段演示如何處理頁面的重定向
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){
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方法重定向到另外一個頁面用來測試上面的例子。
本小節應該說是HTTP客戶端編程中最常碰見的問題,很多網站的內容都只是對註冊用戶可見的,這種情況下就必須要求使用正確的用戶名和口令登錄成功後,方可瀏覽到想要的頁面。因爲HTTP協議是無狀態的,也就是連接的有效期只限於當前請求,請求內容結束後連接就關閉了。在這種情況下爲了保存用戶的登錄信息必須使用到Cookie機制。以JSP/Servlet爲例,當瀏覽器請求一個JSP或者是Servlet的頁面時,應用服務器會返回一個參數,名爲jsessionid(因不同應用服務器而異),值是一個較長的唯一字符串的Cookie,這個字符串值也就是當前訪問該站點的會話標識。瀏覽器在每訪問該站點的其他頁面時候都要帶上jsessionid這樣的Cookie信息,應用服務器根據讀取這個會話標識來獲取對應的會話信息。
對於需要用戶登錄的網站,一般在用戶登錄成功後會將用戶資料保存在服務器的會話中,這樣當訪問到其他的頁面時候,應用服務器根據瀏覽器送上的Cookie中讀取當前請求對應的會話標識以獲得對應的會話信息,然後就可以判斷用戶資料是否存在於會話信息中,如果存在則允許訪問頁面,否則跳轉到登錄頁面中要求用戶輸入帳號和口令進行登錄。這就是一般使用JSP開發網站在處理用戶登錄的比較通用的方法。
這樣一來,對於HTTP的客戶端來講,如果要訪問一個受保護的頁面時就必須模擬瀏覽器所做的工作,首先就是請求登錄頁面,然後讀取Cookie值;再次請求登錄頁面並加入登錄頁所需的每個參數;最後就是請求最終所需的頁面。當然在除第一次請求外其他的請求都需要附帶上Cookie信息以便服務器能判斷當前請求是否已經通過驗證。說了這麼多,可是如果你使用httpclient的話,你甚至連一行代碼都無需增加,你只需要先傳遞登錄信息執行登錄過程,然後直接訪問想要的頁面,跟訪問一個普通的頁面沒有任何區別,因爲類HttpClient已經幫你做了所有該做的事情了,太棒了!下面的例子實現了模擬登陸開心網並向自己好友發送消息的功能。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.apache.commons.httpclient.Cookie;
import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.NameValuePair;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpClientParams;
import org.apache.commons.httpclient.params.HttpMethodParams;
class Login {
public static String loginurl = "https://security.kaixin001.com/login/login_post.php";
static Cookie[] cookies = {};
static HttpClient httpClient = new HttpClient();
static String email = "[email protected]";//你的email
static String psw = "xxx";//你的密碼
// 消息發送的action
String url = "http://www.kaixin001.com/home/";
public static void getUrlContent()
throws Exception {
HttpClientParams httparams = new HttpClientParams();
httparams.setSoTimeout(30000);
httpClient.setParams(httparams);
httpClient.getHostConfiguration().setHost("www.kaixin001.com", 80);
httpClient.getParams().setParameter(
HttpMethodParams.HTTP_CONTENT_CHARSET, "UTF-8");
PostMethod login = new PostMethod(loginurl);
login.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8");
NameValuePair Email = new NameValuePair("loginemail", email);// 郵箱
NameValuePair password = new NameValuePair("password", psw);// 密碼
// NameValuePair code = new NameValuePair( "code"
// ,"????");//有時候需要驗證碼,暫時未解決
NameValuePair[] data = { Email, password };
login.setRequestBody(data);
httpClient.executeMethod(login);
int statuscode = login.getStatusCode();
System.out.println(statuscode + "-----------");
String result = login.getResponseBodyAsString();
System.out.println(result+"++++++++++++");
cookies = httpClient.getState().getCookies();
System.out.println("==========Cookies============");
int i = 0;
for (Cookie c : cookies) {
System.out.println(++i + ": " + c);
}
httpClient.getState().addCookies(cookies);
// 當state爲301或者302說明登陸頁面跳轉了,登陸成功了
if ((statuscode == HttpStatus.SC_MOVED_TEMPORARILY)
|| (statuscode == HttpStatus.SC_MOVED_PERMANENTLY)
|| (statuscode == HttpStatus.SC_SEE_OTHER)
|| (statuscode == HttpStatus.SC_TEMPORARY_REDIRECT)) {
// 讀取新的 URL 地址
Header header = login.getResponseHeader("location");
// 釋放連接
login.releaseConnection();
System.out.println("獲取到跳轉header>>>" + header);
if (header != null) {
String newuri = header.getValue();
if ((newuri == null) || (newuri.equals("")))
newuri = "/";
GetMethod redirect = new GetMethod(newuri);
// ////////////
redirect.setRequestHeader("Cookie", cookies.toString());
httpClient.executeMethod(redirect);
System.out.println("Redirect:"
+ redirect.getStatusLine().toString());
redirect.releaseConnection();
} else
System.out.println("Invalid redirect");
} else {
// 用戶名和密碼沒有被提交,當登陸多次後需要驗證碼的時候會出現這種未提交情況
System.out.println("用戶沒登陸");
System.exit(1);
}
}
public static void sendMsg() throws Exception {
// 登錄後發消息
System.out.println("*************發消息***********");
String posturl = "http://www.kaixin001.com/msg/post.php";
PostMethod poster = new PostMethod(posturl);
poster.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded; charset=UTF-8");
poster.setRequestHeader("Cookie", cookies.toString());
NameValuePair uids = new NameValuePair("uids", "89600585");// 發送的好友對象的id,此處換成你的好友id
NameValuePair content = new NameValuePair("content", "你好啊!");// 需要發送的信息的內容
NameValuePair liteeditor_0 = new NameValuePair("liteeditor_0", "你好啊!");// 需要發送的信息的內容
NameValuePair texttype = new NameValuePair("texttype", "plain");
NameValuePair send_separate = new NameValuePair("send_separate", "0");
NameValuePair service = new NameValuePair("service", "0");
NameValuePair[] msg = { uids, content, texttype, send_separate, service,liteeditor_0 };
poster.setRequestBody(msg);
httpClient.executeMethod(poster);
String result = poster.getResponseBodyAsString();
System.out.println(result+"++++++++++++");
//System.out.println(StreamOut(result, "iso8859-1"));
int statuscode = poster.getStatusCode();
System.out.println(statuscode + "-----------");
if(statuscode == 301 || statuscode == 302){
// 讀取新的 URL 地址
Header header = poster.getResponseHeader("location");
System.out.println("獲取到跳轉header>>>" + header);
if (header != null) {
String newuri = header.getValue();
if ((newuri == null) || (newuri.equals("")))
newuri = "/";
GetMethod redirect = new GetMethod(newuri);
// ////////////
redirect.setRequestHeader("Cookie", cookies.toString());
httpClient.executeMethod(redirect);
System.out.println("Redirect:"
+ redirect.getStatusLine().toString());
redirect.releaseConnection();
} else
System.out.println("Invalid redirect");
}
poster.releaseConnection();
}
public static String StreamOut(InputStream txtis, String code)
throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(txtis,
code));
String tempbf;
StringBuffer html = new StringBuffer(100);
while ((tempbf = br.readLine()) != null) {
html.append(tempbf + "\n");
}
return html.toString();
}
}
提交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();
}
}
我們經常會碰到這樣的頁面,當訪問它的時候會彈出一個瀏覽器的對話框要求輸入用戶名和密碼後方可,這種用戶認證的方式不同於我們在前面介紹的基於表單的用戶身份驗證。這是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();
}
}
多線程同時訪問httpclient,例如同時從一個站點上下載多個文件。對於同一個HttpConnection同一個時間只能有一個線程訪問,爲了保證多線程工作環境下不產生衝突,httpclient使用了一個多線程連接管理器的類:MultiThreadedHttpConnectionManager,要使用這個類很簡單,只需要在構造HttpClient實例的時候傳入即可,代碼如下:
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
以後儘管訪問client實例即可。
HttpInvoke.java:封裝了HttpClient調度的必要參數設置,以及post,get等常用方法
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import java.util.Iterator;
import java.util.Map;
import java.net.SocketTimeoutException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class HttpInvoker {
private Log logger = LogFactory.getLog(HttpInvoker.class);
private static HttpInvoker httpInvoker = new HttpInvoker();
private HttpClient client = null;
private String charset = "gbk";
private int timeout = 10000;
private boolean useProxy = false;
private String proxyHost = null;
private int proxyPort;
private String proxyUsername = null;
private String proxyPassword = null;
private boolean initialized = false;
public static HttpInvoker getInstance() {
return httpInvoker;
}
private HttpInvoker() {
client = new HttpClient(new MultiThreadedHttpConnectionManager());
client.getParams().setParameter("http.protocol.content-charset", "gbk");
client.getParams().setContentCharset("gbk");
client.getParams().setSoTimeout(timeout);
}
public HttpInvoker(String charset, int timeout, boolean useProxy,
String proxyHost, int proxyPort, String proxyUsername,
String proxyPassword) {
client = new HttpClient(new MultiThreadedHttpConnectionManager());
if(charset != null && !charset.trim().equals("")) {
this.charset = charset;
}
if(timeout > 0) {
this.timeout = timeout;
}
client.getParams().setParameter("http.protocol.content-charset", charset);
client.getParams().setContentCharset(charset);
client.getParams().setSoTimeout(timeout);
if(useProxy && proxyHost != null &&
!proxyHost.trim().equals("") && proxyPort > 0) {
HostConfiguration hc = new HostConfiguration();
hc.setProxy(proxyHost, proxyPort);
client.setHostConfiguration(hc);
if (proxyUsername != null && !proxyUsername.trim().equals("") &&
proxyPassword != null && !proxyPassword.trim().equals("")) {
client.getState().setProxyCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
}
initialized = true;
logger.debug("HttpInvoker初始化完成");
}
public synchronized void init() {
if(charset != null && !charset.trim().equals("")) {
client.getParams().setParameter("http.protocol.content-charset", charset);
client.getParams().setContentCharset(charset);
}
if(timeout > 0) {
client.getParams().setSoTimeout(timeout);
}
if(useProxy && proxyHost != null &&
!proxyHost.trim().equals("") && proxyPort > 0) {
HostConfiguration hc = new HostConfiguration();
hc.setProxy(proxyHost, proxyPort);
client.setHostConfiguration(hc);
if (proxyUsername != null && !proxyUsername.trim().equals("") &&
proxyPassword != null && !proxyPassword.trim().equals("")) {
client.getState().setProxyCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(proxyUsername, proxyPassword));
}
}
initialized = true;
logger.debug("HttpInvoker初始化完成");
}
public String invoke(String url) throws Exception {
return invoke(url, null, false);
}
public String invoke(String url, Map params, boolean isPost) throws Exception {
logger.debug("HTTP調用[" + (isPost?"POST":"GET") + "][" + url + "][" + params + "]");
HttpMethod httpMethod = null;
String result = "";
try {
if(isPost && params != null && params.size() > 0) {
Iterator paramKeys = params.keySet().iterator();
httpMethod = new PostMethod(url);
NameValuePair[] form = new NameValuePair[params.size()];
int formIndex = 0;
while(paramKeys.hasNext()) {
String key = (String)paramKeys.next();
Object value = params.get(key);
if(value != null && value instanceof String && !value.equals("")) {
form[formIndex] = new NameValuePair(key, (String)value);
formIndex++;
} else if(value != null && value instanceof String[] &&
((String[])value).length > 0) {
NameValuePair[] tempForm =
new NameValuePair[form.length + ((String[])value).length - 1];
for(int i=0; i<formIndex; i++) {
tempForm[i] = form[i];
}
form = tempForm;
for(String v : (String[])value) {
form[formIndex] = new NameValuePair(key, (String)v);
formIndex++;
}
}
}
((PostMethod)httpMethod).setRequestBody(form);
} else {
if(params != null && params.size() > 0) {
Iterator paramKeys = params.keySet().iterator();
StringBuffer getUrl = new StringBuffer(url.trim());
if(url.trim().indexOf("?") > -1) {
if(url.trim().indexOf("?") < url.trim().length()-1 &&
url.trim().indexOf("&") < url.trim().length()-1) {
getUrl.append("&");
}
} else {
getUrl.append("?");
}
while(paramKeys.hasNext()) {
String key = (String)paramKeys.next();
Object value = params.get(key);
if(value != null && value instanceof String && !value.equals("")) {
getUrl.append(key).append("=").append(value).append("&");
} else if(value != null && value instanceof String[] &&
((String[])value).length > 0) {
for(String v : (String[])value) {
getUrl.append(key).append("=").append(v).append("&");
}
}
}
if(getUrl.lastIndexOf("&") == getUrl.length()-1) {
httpMethod = new GetMethod(getUrl.substring(0, getUrl.length()-1));
} else {
httpMethod = new GetMethod(getUrl.toString());
}
} else {
httpMethod = new GetMethod(url);
}
}
client.executeMethod(httpMethod);
// result = httpMethod.getResponseBodyAsString();
BufferedReader reader = new BufferedReader(new InputStreamReader(
httpMethod.getResponseBodyAsStream(),"ISO-8859-1"));
String line = null;
String html = null;
while((line = reader.readLine()) != null){
if(html == null) {
html = "";
} else {
html += "\r\n";
}
html += line;
}
if(html != null) {
result = new String(html.getBytes("ISO-8859-1"), charset);
}
} catch (SocketTimeoutException e) {
logger.error("連接超時[" + url + "]");
throw e;
} catch (java.net.ConnectException e) {
logger.error("連接失敗[" + url + "]");
throw e;
} catch (Exception e) {
logger.error("連接時出現異常[" + url + "]");
throw e;
} finally {
if (httpMethod != null) {
try {
httpMethod.releaseConnection();
} catch (Exception e) {
logger.error("釋放網絡連接失敗[" + url + "]");
throw e;
}
}
}
return result;
}
public void setCharset(String charset) {
this.charset = charset;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
}
public void setProxyPort(int proxyPort) {
this.proxyPort = proxyPort;
}
public void setProxyUsername(String proxyUsername) {
this.proxyUsername = proxyUsername;
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
}
public void setUseProxy(boolean useProxy) {
this.useProxy = useProxy;
}
public synchronized boolean isInitialized() {
return initialized;
}
}
http訪問網絡的代理ip和端口,還有使用用戶及密碼都可以在Spring容器中注入進來:
<bean id="httpInvoker" class="HttpInvoker">
<constructor-arg type="java.lang.String" value="gbk" /><!--useProxy-->
<constructor-arg type="int" value="10000" /><!--useProxy-->
<constructor-arg type="boolean" value="true" /><!--useProxy-->
<!--代理地址 -->
<constructor-arg type="java.lang.String" value="192.168.1.1" />
<constructor-arg type="int" value="8080" />
<constructor-arg type="java.lang.String" value="" /><!--用戶名-->
<constructor-arg type="java.lang.String" value="" /><!--密碼-->
</bean>
使用方式:post
Map<String,String> params = new HashMap<String,String>();
params.put("check", check);
String result = httpInvoker.invoke( "someURL", params, true);
使用方式:get
String content = httpInvoker.invoke(url);
參考資料:
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