posts - 23,comments - 66,trackbacks - 0

          from: 應(yīng)用HttpClient來對付各種頑固的WEB服務(wù)器

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

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

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

          1.?讀取網(wǎng)頁(HTTP/HTTPS)內(nèi)容

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

          /*

          ?* 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(); ???

          ??????? // 設(shè)置代理服務(wù)器地址和端口 ? ???

          ??????? //client.getHostConfiguration().setProxy("proxy_host_addr",proxy_port);

          ??? ??? // 使用 GET 方法 ,如果服務(wù)器需要通過 HTTPS 連接,那只需要將下面 URL 中的 http 換成 https

          ??? ??? HttpMethod method = new GetMethod( "http://java.sun.com" );

          ??? ??? // 使用 POST 方法

          ??? ??? //HttpMethod method = new PostMethod("http://java.sun.com");

          ??? ??? client.executeMethod(method);

          ??????? // 打印服務(wù)器返回的狀態(tài)

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

          ??????? // 打印返回的信息

          ??? ??? System.out.println(method.getResponseBodyAsString());

          ??????? // 釋放連接

          ??? ??? method.releaseConnection();

          ??? }
          }

          ?

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


          2.?以GET或者POST方式向網(wǎng)頁提交參數(shù)

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

          ?

          /*

          ?* 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.*;

          /**

          ? * 提交參數(shù)演示

          ? * 該程序連接到一個用于查詢手機號碼所屬地的頁面

          ? * 以便查詢號碼段 1330227 所在的省份以及城市

          ? * @author Liudong

          ? */

          public class SimpleHttpClient {

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

          ??? {

          ??? ??? HttpClient client = new HttpClient();

          ??? ??? client.getHostConfiguration().setHost( "www.imobile.com.cn" , 80, "http" );

          ??? ??? HttpMethod method = getPostMethod(); // 使用 POST 方式提交數(shù)據(jù)

          ??? ??? client.executeMethod(method);

          ?????? // 打印服務(wù)器返回的狀態(tài)

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

          ??? ??? // 打印 結(jié)果頁面

          ??? ??? String response =

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

          ?????? // 打印返回的信息

          ??? ??? System.out.println(response);

          ??? ??? method.releaseConnection();

          ??? }

          ??? /**

          ??? ? * 使用 GET 方式提交數(shù)據(jù)

          ??? ? * @return

          ??? ? */

          ??? private static HttpMethod getGetMethod(){

          ??? ??? return new GetMethod( "/simcard.php?simcard=1330227" );

          ??? }

          ??? /**

          ??? ? * 使用 POST 方式提交數(shù)據(jù)

          ??? ? * @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需要一個參數(shù)是simcard,這個參數(shù)值為手機號碼段,即手機號碼的前七位,服務(wù)器會返回提交的手機號碼對應(yīng)的省份、城市以及其他詳細信息。GET的提交方法只需要在URL后加入?yún)?shù)信息,而POST則需要通過NameValuePair類來設(shè)置參數(shù)名稱和它所對應(yīng)的值

          3.?處理頁面重定向

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

          狀態(tài)碼

          對應(yīng) HttpServletResponse 的常量

          詳細描述

          301

          SC_MOVED_PERMANENTLY

          頁面已經(jīng)永久移到另外一個新地址

          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.?模擬輸入用戶名和口令進行登錄

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

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

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

          /*

          ?* 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

          ??? ??? GetMethod get = new GetMethod( "/main2.jsp" );

          ??? ??? client.executeMethod(get);

          ??? ??? System.out.println(get.getResponseBodyAsString());

          ??? ??? get.releaseConnection();

          ??? }

          }

          5.?提交XML格式參數(shù)

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

          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 格式數(shù)據(jù)的例子

          ? */

          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”);

          ??????? // 設(shè)置請求的內(nèi)容直接從文件中讀取

          ??????? post.setRequestBody( new FileInputStream(input));

          ???????

          ??????? if (input.length() < Integer.MAX_VALUE)

          ??????????? post.setRequestContentLength(input.length());

          ??????? else ??????????? post.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED);

          ???????

          ??????? // 指定請求內(nèi)容的類型

          ??????? 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,該類已經(jīng)封裝了文件上傳的細節(jié),我們要做的僅僅是告訴它我們要上傳文件的全路徑即可,下面的代碼片段演示如何使用這個類。

          MultipartPostMethod filePost = new MultipartPostMethod(targetURL);

          filePost.addParameter( "fileName" , targetFilePath);

          HttpClient client = new HttpClient();

          // 由于要上傳的文件可能比較大 , 因此在此設(shè)置最大的連接超時時間

          client.getHttpConnectionManager(). getParams().setConnectionTimeout(5000);

          int status = client.executeMethod(filePost);

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

          7.?訪問啟用認證的頁面

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

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

          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同一個時間只能有一個線程訪問,為了保證多線程工作環(huán)境下不產(chǎn)生沖突,httpclient使用了一個多線程連接管理器的類: MultiThreadedHttpConnectionManager,要使用這個類很簡單,只需要在構(gòu)造HttpClient實例的時候傳入即可,代碼如下:

          MultiThreadedHttpConnectionManager connectionManager =

          ?? new MultiThreadedHttpConnectionManager();

          HttpClient client = new HttpClient(connectionManager);

          以后盡管訪問client實例即可。

          參考資料:

          httpclient首頁:????http://jakarta.apache.org/commons/httpclient/
          關(guān)于NTLM是如何工作:??http://davenport.sourceforge.net/ntlm.html

          posted on 2006-03-21 21:53 rd2pm 閱讀(634) 評論(0)  編輯  收藏

          只有注冊用戶登錄后才能發(fā)表評論。


          網(wǎng)站導航:
           

          主站蜘蛛池模板: 莎车县| 三门县| 重庆市| 营山县| 正镶白旗| 泰安市| 乐业县| 巢湖市| 资兴市| 汝州市| 辉南县| 息烽县| 池州市| 礼泉县| 监利县| 兴隆县| 政和县| 隆子县| 闸北区| 美姑县| 兴和县| 防城港市| 武强县| 方正县| 滨海县| 巴南区| 南投市| 五家渠市| 辽阳市| 南京市| 晋中市| 射阳县| 万荣县| 客服| 普定县| 德庆县| 婺源县| 库伦旗| 察哈| 津市市| 五大连池市|