一般的情況下我們都是使用IE或者Navigator瀏覽器來(lái)訪(fǎng)問(wèn)一個(gè)WEB服務(wù)器,用來(lái)瀏覽頁(yè)面查看信息或者提交 一些數(shù)據(jù)等等。所訪(fǎng)問(wèn)的這些頁(yè)面有的僅僅是一些普通的頁(yè)面,有的需要用戶(hù)登錄后方可使用,或者需要認(rèn)證以及是一些通過(guò)加密方式傳輸,例如HTTPS。目前 我們使用的瀏覽器處理這些情況都不會(huì)構(gòu)成問(wèn)題。不過(guò)你可能在某些時(shí)候需要通過(guò)程序來(lái)訪(fǎng)問(wèn)這樣的一些頁(yè)面,比如從別人的網(wǎng)頁(yè)中“偷”一些數(shù)據(jù);利用某些站點(diǎn) 提供的頁(yè)面來(lái)完成某種功能,例如說(shuō)我們想知道某個(gè)手機(jī)號(hào)碼的歸屬地而我們自己又沒(méi)有這樣的數(shù)據(jù),因此只好借助其他公司已有的網(wǎng)站來(lái)完成這個(gè)功能,這個(gè)時(shí)候 我們需要向網(wǎng)頁(yè)提交手機(jī)號(hào)碼并從返回的頁(yè)面中解析出我們想要的數(shù)據(jù)來(lái)。如果對(duì)方僅僅是一個(gè)很簡(jiǎn)單的頁(yè)面,那我們的程序會(huì)很簡(jiǎn)單,本文也就沒(méi)有必要大張旗鼓 的在這里浪費(fèi)口舌。但是考慮到一些服務(wù)授權(quán)的問(wèn)題,很多公司提供的頁(yè)面往往并不是可以通過(guò)一個(gè)簡(jiǎn)單的URL就可以訪(fǎng)問(wèn)的,而必須經(jīng)過(guò)注冊(cè)然后登錄后方可使 用提供服務(wù)的頁(yè)面,這個(gè)時(shí)候就涉及到COOKIE問(wèn)題的處理。我們知道目前流行的***頁(yè)技術(shù)例如ASP、JSP無(wú)不是通過(guò)COOKIE來(lái)處理會(huì)話(huà)信息 的。為了使我們的程序能使用別人所提供的服務(wù)頁(yè)面,就要求程序首先登錄后再訪(fǎng)問(wèn)服務(wù)頁(yè)面,這過(guò)程就需要自行處理cookie,想想當(dāng)你用 java.net.HttpURLConnection來(lái)完成這些功能時(shí)是多么恐怖的事情??!況且這僅僅是我們所說(shuō)的頑固的WEB服務(wù)器中的一個(gè)很常見(jiàn)的 “頑固”!再有如通過(guò)HTTP來(lái)上傳文件呢?不需要頭疼,這些問(wèn)題有了“它”就很容易解決了!
我們不可能列舉所有可能的頑固,我們會(huì)針對(duì)幾種最常見(jiàn)的問(wèn)題進(jìn)行處理。當(dāng)然了,正如前面說(shuō)到的,如果我們自己使用
java.net.HttpURLConnection來(lái)搞定這些問(wèn)題是很恐怖的事情,因此在開(kāi)始之前我們先要介紹一下一個(gè)開(kāi)放源碼的項(xiàng)目,這個(gè)項(xiàng)目就是 Apache開(kāi)源組織中的httpclient,它隸屬于Jakarta的commons項(xiàng)目,目前的版本是2.0RC2。commons下本來(lái)已經(jīng)有一 個(gè)net的子項(xiàng)目,但是又把httpclient單獨(dú)提出來(lái),可見(jiàn)http服務(wù)器的訪(fǎng)問(wèn)絕非易事。 Commons-httpclient項(xiàng)目就是專(zhuān)門(mén)設(shè)計(jì)來(lái)簡(jiǎn)化HTTP客戶(hù)端與服務(wù)器進(jìn)行各種通訊編程。通過(guò)它可以 讓原來(lái)很頭疼的事情現(xiàn)在輕松的解決,例如你不再管是HTTP或者HTTPS的通訊方式,告訴它你想使用HTTPS方式,剩下的事情交給 httpclient替你完成。本文會(huì)針對(duì)我們?cè)诰帉?xiě)HTTP客戶(hù)端程序時(shí)經(jīng)常碰到的幾個(gè)問(wèn)題進(jìn)行分別介紹如何使用httpclient來(lái)解決它們,為了 讓讀者更快的熟悉這個(gè)項(xiàng)目我們最開(kāi)始先給出一個(gè)簡(jiǎn)單的例子來(lái)讀取一個(gè)網(wǎng)頁(yè)的內(nèi)容,然后循序漸進(jìn)解決掉前進(jìn)中的所形侍狻?/font> 1. 讀取網(wǎng)頁(yè)(HTTP/HTTPS)內(nèi)容
下面是我們給出的一個(gè)簡(jiǎn)單的例子用來(lái)訪(fǎng)問(wèn)某個(gè)頁(yè)面 /*
* 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.*; /**
* 最簡(jiǎn)單的HTTP客戶(hù)端,用來(lái)演示通過(guò)GET或者POST方式訪(fǎng)問(wèn)某個(gè)頁(yè)面
* @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\
//使用GET方法,如果服務(wù)器需要通過(guò)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(); } }
在這個(gè)例子中首先創(chuàng)建一個(gè)HTTP客戶(hù)端(HttpClient)的實(shí)例,然后選擇 提交的方法是GET或者POST,最后在HttpClient實(shí)例上執(zhí)行提交的方法,最后從所選擇的提交方法中讀取服務(wù)器反饋回來(lái)的結(jié)果。這就是使用 HttpClient的基本流程。其實(shí)用一行代碼也就可以搞定整個(gè)請(qǐng)求的過(guò)程,非常的簡(jiǎn)單!
2. 以GET或者POST方式向網(wǎng)頁(yè)提交參數(shù)
其實(shí)前面一個(gè)最簡(jiǎn)單的示例中我們已經(jīng)介紹了如何使用GET或者POST方式來(lái)請(qǐng)求一 個(gè)頁(yè)面,本小節(jié)與之不同的是多了提交時(shí)設(shè)定頁(yè)面所需的參數(shù),我們知道如果是GET的請(qǐng)求方式,那么所有參數(shù)都直接放到頁(yè)面的URL后面用問(wèn)號(hào)與頁(yè)面地址隔 開(kāi),每個(gè)參數(shù)用&隔開(kāi),例如:http://java.sun.com?name=liudong&mobile=123456,但是當(dāng)使用POST方法時(shí)就會(huì)稍微有一點(diǎn)點(diǎn)麻煩。本小節(jié)的例子演示向如何查詢(xún)手機(jī)號(hào)碼所在的城市,代碼如下:
/*
* 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ù)演示
* 該程序連接到一個(gè)用于查詢(xún)手機(jī)號(hào)碼所屬地的頁(yè)面 * 以便查詢(xún)號(hào)碼段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\
HttpMethod method = getPostMethod();//使用POST方式提交數(shù)據(jù)
client.executeMethod(method); //打印服務(wù)器返回的狀態(tài)
System.out.println(method.getStatusLine()); //打印結(jié)果頁(yè)面 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\
post.setRequestBody(new NameValuePair[] { simcard}); return post; } }
在上面的例子中頁(yè)面http://www.imobile.com.cn/simcard.php需要一個(gè)參數(shù)是simcard,這個(gè)參數(shù)值為手機(jī)號(hào)碼段,即手機(jī)號(hào)碼的前七位,服務(wù)器會(huì)返回提交的手機(jī)號(hào)碼對(duì)應(yīng)的省份、城市以及其他詳細(xì)信息。GET的提交方法只需要在URL后加入?yún)?shù)信息,而POST則需要通過(guò)NameValuePair類(lèi)來(lái)設(shè)置參數(shù)名稱(chēng)和它所對(duì)應(yīng)的值 3. 處理頁(yè)面重定向
在JSP/Servlet編程中response.sendRedirect方法就 是使用HTTP協(xié)議中的重定向機(jī)制。它與JSP中的 的內(nèi)容并返回給客戶(hù)端;而前者是返回一個(gè)狀態(tài)碼,這些狀態(tài)碼 的可能值見(jiàn)下表,然后客戶(hù)端讀取需要跳轉(zhuǎn)到的頁(yè)面的URL并重新加載新的頁(yè)面。就是這樣一個(gè)過(guò)程,所以我們編程的時(shí)候就要通過(guò) HttpMethod.getStatusCode()方法判斷返回值是否為下表中的某個(gè)值來(lái)判斷是否需要跳轉(zhuǎn)。如果已經(jīng)確認(rèn)需要進(jìn)行頁(yè)面跳轉(zhuǎn)了,那么可 以通過(guò)讀取HTTP頭中的location屬性來(lái)獲取新的地址。 狀態(tài)碼 對(duì)應(yīng)HttpServletResponse的常量 詳細(xì)描述 301 SC_MOVED_PERMANENTLY 頁(yè)面已經(jīng)永久移到另外一個(gè)新地址 302 SC_MOVED_TEMPORARILY 頁(yè)面暫時(shí)移動(dòng)到另外一個(gè)新的地址 303 SC_SEE_OTHER 客戶(hù)端請(qǐng)求的地址必須通過(guò)另外的URL來(lái)訪(fǎng)問(wèn) 307 SC_TEMPORARY_REDIRECT 同SC_MOVED_TEMPORARILY 下面的代碼片段演示如何處理頁(yè)面的重定向 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\"); } 我們可以自行編寫(xiě)兩個(gè)JSP頁(yè)面,其中一個(gè)頁(yè)面用 response.sendRedirect方法重定向到另外一個(gè)頁(yè)面用來(lái)測(cè)試上面的例子。 4. 模擬輸入用戶(hù)名和口令進(jìn)行登錄 本小節(jié)應(yīng)該說(shuō)是HTTP客戶(hù)端編程中最常碰見(jiàn)的問(wèn)題,很多網(wǎng)站的內(nèi)容都只是對(duì)注冊(cè)用 戶(hù)可見(jiàn)的,這種情況下就必須要求使用正確的用戶(hù)名和口令登錄成功后,方可瀏覽到想要的頁(yè)面。因?yàn)镠TTP協(xié)議是無(wú)狀態(tài)的,也就是連接的有效期只限于當(dāng)前請(qǐng) 求,請(qǐng)求內(nèi)容結(jié)束后連接就關(guān)閉了。在這種情況下為了保存用戶(hù)的登錄信息必須使用到Cookie機(jī)制。以JSP/Servlet為例,當(dāng)瀏覽器請(qǐng)求一個(gè) JSP或者是Servlet的頁(yè)面時(shí),應(yīng)用服務(wù)器會(huì)返回一個(gè)參數(shù),名為jsessionid(因不同應(yīng)用服務(wù)器而異),值是一個(gè)較長(zhǎng)的唯一字符串的 Cookie,這個(gè)字符串值也就是當(dāng)前訪(fǎng)問(wèn)該站點(diǎn)的會(huì)話(huà)標(biāo)識(shí)。瀏覽器在每訪(fǎng)問(wèn)該站點(diǎn)的其他頁(yè)面時(shí)候都要帶上jsessionid這樣的Cookie信息, 應(yīng)用服務(wù)器根據(jù)讀取這個(gè)會(huì)話(huà)標(biāo)識(shí)來(lái)獲取對(duì)應(yīng)的會(huì)話(huà)信息。 對(duì)于需要用戶(hù)登錄的網(wǎng)站,一般在用戶(hù)登錄成功后會(huì)將用戶(hù)資料保存在服務(wù)器的會(huì)話(huà)中, 這樣當(dāng)訪(fǎng)問(wèn)到其他的頁(yè)面時(shí)候,應(yīng)用服務(wù)器根據(jù)瀏覽器送上的Cookie中讀取當(dāng)前請(qǐng)求對(duì)應(yīng)的會(huì)話(huà)標(biāo)識(shí)以獲得對(duì)應(yīng)的會(huì)話(huà)信息,然后就可以判斷用戶(hù)資料是否存 在于會(huì)話(huà)信息中,如果存在則允許訪(fǎng)問(wèn)頁(yè)面,否則跳轉(zhuǎn)到登錄頁(yè)面中要求用戶(hù)輸入帳號(hào)和口令進(jìn)行登錄。這就是一般使用JSP開(kāi)發(fā)網(wǎng)站在處理用戶(hù)登錄的比較通用 的方法。 這樣一來(lái),對(duì)于HTTP的客戶(hù)端來(lái)講,如果要訪(fǎng)問(wèn)一個(gè)受保護(hù)的頁(yè)面時(shí)就必須模擬瀏覽 器所做的工作,首先就是請(qǐng)求登錄頁(yè)面,然后讀取Cookie值;再次請(qǐng)求登錄頁(yè)面并加入登錄頁(yè)所需的每個(gè)參數(shù);最后就是 請(qǐng)求最終所需的頁(yè)面。當(dāng)然在除第一 次請(qǐng)求外其他的請(qǐng)求都需要附帶上Cookie信息以便服務(wù)器能判斷當(dāng)前請(qǐng)求是否已經(jīng)通過(guò)驗(yàn)證。說(shuō)了這么多,可是如果你使用httpclient的話(huà),你甚 至連一行代碼都無(wú)需增加,你只需要先傳遞登錄信息執(zhí)行登錄過(guò)程,然后直接訪(fǎng)問(wèn)想要的頁(yè)面,跟訪(fǎng)問(wèn)一個(gè)普通的頁(yè)面沒(méi)有任何區(qū)別,因?yàn)轭?lèi)HttpClient 已經(jīng)幫你做了所有該做的事情了,太棒了!下面的例子實(shí)現(xiàn)了這樣一個(gè)訪(fǎng)問(wèn)的過(guò)程。 /* * 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.*; /** * 用來(lái)演示登錄表單的示例 * @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); //模擬登錄頁(yè)面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, \"/\ if (cookies.length == 0) { System.out.println(\"None\"); } else { for (int i = 0; i < cookies.length; i++) { System.out.println(cookies[i].toString()); } } //訪(fǎng)問(wèn)所需的頁(yè)面main2.jsp GetMethod get = new GetMethod(\"/main2.jsp\"); client.executeMethod(get); System.out.println(get.getResponseBodyAsString()); get.releaseConnection(); } } 5. 提交XML格式參數(shù) 提交XML格式的參數(shù)很簡(jiǎn)單,僅僅是一個(gè)提交時(shí)候的ContentType問(wèn)題,下面的例子演示從文件文件中讀取XML信息并提交給服務(wù)器的過(guò)程,該過(guò)程可以用來(lái)測(cè)試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; /** * 用來(lái)演示提交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è)置請(qǐng)求的內(nèi)容直接從文件中讀取 post.setRequestBody(new FileInputStream(input)); if (input.length() < Integer.MAX_VALUE) post.setRequestContentLength(input.length()); else post.setRequestContentLength(EntityEnclosingMethod.CONTENT_LENGTH_CHUNKED); // 指定請(qǐng)求內(nèi)容的類(lèi)型 post.setRequestHeader(\"Content-type\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. 通過(guò)HTTP上傳文件 httpclient使用了單獨(dú)的一個(gè)HttpMethod子類(lèi)來(lái)處理文件的上傳,這個(gè)類(lèi)就是MultipartPostMethod,該類(lèi)已經(jīng)封裝了文件上傳的細(xì)節(jié),我們要做的僅僅是告訴它我們要上傳文件的全路徑即可,下面的代碼片段演示如何使用這個(gè)類(lèi)。 MultipartPostMethod filePost = new MultipartPostMethod(targetURL); filePost.addParameter(\"fileName\ HttpClient client = new HttpClient(); //由于要上傳的文件可能比較大,因此在此設(shè)置最大的連接超時(shí)時(shí)間 client.getHttpConnectionManager().getParams().setConnectionTimeout(5000); int status = client.executeMethod(filePost); 上面代碼中,targetFilePath即為要上傳的文件所在的路徑。 7. 訪(fǎng)問(wèn)啟用認(rèn)證的頁(yè)面 我們經(jīng)常會(huì)碰到這樣的頁(yè)面,當(dāng)訪(fǎng)問(wèn)它的時(shí)候會(huì)彈出一個(gè)瀏覽器的對(duì)話(huà)框要求輸入用戶(hù)名 和密碼后方可,這種用戶(hù)認(rèn)證的方式不同于我們?cè)谇懊娼榻B的基于表單的用戶(hù)身份驗(yàn)證。這是HTTP的認(rèn)證策略,httpclient支持三種認(rèn)證方式包括: 基本、摘要以及NTLM認(rèn)證。其中基本認(rèn)證最簡(jiǎn)單、通用但也最不安全;摘要認(rèn)證是在HTTP 1.1中加入的認(rèn)證方式,而NTLM則是微軟公司定義的而不是通用的規(guī)范,最新版本的NTLM是比摘要認(rèn)證還要安全的一種方式。 下面例子是從httpclient的CVS服務(wù)器中下載的,它簡(jiǎn)單演示如何訪(fǎng)問(wèn)一個(gè)認(rèn)證保護(hù)的頁(yè)面: 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+\"\"+ get.getResponseBodyAsString()); get.releaseConnection(); } } 8. 多線(xiàn)程模式下使用httpclient 多線(xiàn)程同時(shí)訪(fǎng)問(wèn)httpclient,例如同時(shí)從一個(gè)站點(diǎn)上下載多個(gè)文件。對(duì)于同一 個(gè)HttpConnection同一個(gè)時(shí)間只能有一個(gè)線(xiàn)程訪(fǎng)問(wèn),為了保證多線(xiàn)程工作環(huán)境下不產(chǎn)生沖突,httpclient使用了一個(gè)多線(xiàn)程連接管理器的 類(lèi):MultiThreadedHttpConnectionManager,要使用這個(gè)類(lèi)很簡(jiǎn)單,只需要在構(gòu)造HttpClient實(shí)例的時(shí)候傳入即 可,代碼如下: MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); HttpClient client = new HttpClient(connectionManager); 以后盡管訪(fǎng)問(wèn)client實(shí)例即可。 參考資料: httpclient首 頁(yè): http://jakarta.apache.org/commons/httpclient/ 關(guān)于NTLM是如何工 作: http://davenport.sourceforge.net/ntlm.html 因篇幅問(wèn)題不能全部顯示,請(qǐng)點(diǎn)此查看更多更全內(nèi)容
Copyright ? 2019- 91gzw.com 版權(quán)所有 湘ICP備2023023988號(hào)-2
違法及侵權(quán)請(qǐng)聯(lián)系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市萬(wàn)商天勤律師事務(wù)所王興未律師提供法律服務(wù)