1. 程式人生 > >爬蟲三式 —— HttpClient

爬蟲三式 —— HttpClient

可以先看一個簡單Demo

HttpClient簡介

HTTP 協議可能是現在 Internet 上使用得最多、最重要的協議了,越來越多的 Java 應用程式需要直接通過 HTTP 協議來訪問網路資源。雖然在 JDK 的 java.net 包中已經提供了訪問 HTTP 協議的基本功能,但是對於大部分應用程式來說,JDK 庫本身提供的功能還不夠豐富和靈活。HttpClient 是 Apache Jakarta Common 下的子專案,用來提供高效的、最新的、功能豐富的支援 HTTP 協議的客戶端程式設計工具包,並且它支援 HTTP 協議最新的版本和建議。

應用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)內容

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

/** 
 *最簡單的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的基本流程。其實用一行程式碼也就可以搞定整個請求的過程,非常的簡單!

2.使用POST方式提交資料(httpClient3)

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;
    }

3.處理頁面重定向

在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方法重定向到另外一個頁面用來測試上面的例子。

 

4.HttpClient的失敗自定重試

呼叫例項httpClient的executeMethod方法來執行getMethod。由於是執行在網路上的程式,在執行executeMethod方法的時候,需要處理兩個異常,分別是HttpException和IOException。引起第一種異常的原因主要可能是在構造getMethod的時候傳入的協議不對,比如不小心將"http"寫成"htp",或者伺服器端返回的內容不正常等,並且該異常發生是不可恢復的;第二種異常一般是由於網路原因引起的異常,對於這種異常 (IOException),HttpClient會根據你指定的恢復策略自動試著重新執行executeMethod方法。HttpClient的恢復策略可以自定義(通過實現介面HttpMethodRetryHandler來實現)。通過httpClient的方法setParameter設定你實現的恢復策略,本文中使用的是系統提供的預設恢復策略,該策略在碰到第二類異常的時候將自動重試3次。executeMethod返回值是一個整數,表示了執行該方法後伺服器返回的狀態碼,該狀態碼能表示出該方法執行是否成功、需要認證或者頁面發生了跳轉(預設狀態下GetMethod的例項是自動處理跳轉的)等。

//設定成了預設的恢復策略,在發生異常時候將自動重試3次,在這裡你也可以設定成自定義的恢復策略
getMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler()); 
//執行getMethod
int statusCode = client.executeMethod(getMethod);
if (statusCode != HttpStatus.SC_OK) {
  System.err.println("Method failed: " + getMethod.getStatusLine());
}

/**或者**/

//加入url重試 
DefaultHttpMethodRetryHandler retryhandler = new DefaultHttpMethodRetryHandler(3,false);  
client.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryhandler); 

5. 模擬登入開心網

本小節應該說是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();

    }
}

6. 提交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(); 
   } 
}

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

MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();

HttpClient client = new HttpClient(connectionManager);

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

9.httpClient完整封裝

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入門