java之httpclient的一些破事
本文偏重使用,簡單講述httpclient,其實在網路程式設計中,基於java的實現幾乎都是包裝了socket的通訊,然後來模擬各種各樣的協議;httpclient其實就是模擬瀏覽器發起想伺服器端的請求,而這種更加類似於JS的請求或頁面的POST、GET,不過這種資料的返回一般需要得到有意義的資料,才方便做其他的互動,否則得到一個頁面結果,全是標籤了,畢竟不是瀏覽器,所以我們用httpclient更多使得系統的互動更加的簡單,本文從如何使用httpclient開始說明到效能的優化方法切入:
1、httpclient客戶端呼叫例子,以及伺服器端需要做什麼。
2、安全性怎麼樣去控制。
3、httpclient在併發量較高的呼叫下問題如何去解決。
1、httpclient客戶端呼叫例子,以及伺服器端需要做什麼。
首先說明伺服器端需要做什麼,httpclient模擬的是一個瀏覽器,要伺服器端進行資料互動,那麼就是和瀏覽器一樣發起請求,接受請求的操作,但是它和瀏覽器與伺服器互動最大的區別是它沒有登入動作,當然也可以通過模擬登入來完成cookie的獲取,但是這個程式碼寫起來就費勁了,而且這個賬號必須在你的程式碼中寫明登入賬號才能獲取到cookie,如果程式是單獨自己用還是可以的,如果很多人用就有點亂了,因為每個人的密碼你也得用某種方法傳遞到伺服器端,但是沒有cookie很明顯會被伺服器端攔截到某個直接的登入介面上去,從而得不到自己想要的資料,所以,我們先拋開安全性問題,那麼就是將部分URL開放出來,也就是不經過過濾器的URL,或將某些目錄單獨開放出來訪問,安全性的問題,第二章來討論,OK,如果伺服器端開放了一個URL路徑後,客戶端訪問就像瀏覽器訪問一個URL一樣簡單,用httpclient如何去訪問呢?
在使用之前,需要先了解,httpclient是apache提供的,所以需要先引入相關的包,要使用它基本需要幾個包:
commons-logging、commons-httpclient、commons-codec具體的版本以及引入方式請自己根據專案和工程打包方法決定,目前來講maven引入是比較方便的方法,然後在程式碼前面引入:
- import org.apache.commons.httpclient.*;
<順便說下 .* 這個說法,有人說用這個 * 是很慢的,對於現在的JVM來說只能說它是在亂說,一個一個引入唯一的好處是可以很快知道這個類是那個包下面來的,但是絕對不是提高效能,jvm在編譯時早就決定了哪些是需要的,哪些是不需要的,如果引入同一個包太多,即使每個單獨寫,jvm也會給改成*,JVM的記憶體結構也不會因為某個類多引入幾個*,就會修改他們之間的連結結構,初始化是由父子整合關係以及包裝關係決定的,而執行是優化器決定的>
首先來看一個Get請求的非常簡單的例子:
- try {
- HttpClient client = new HttpClient();//定義client物件
- client.getHttpConnectionManager().getParams().setConnectionTimeout(2000);//設定連線超時時間為2秒(連線初始化時間)
- GetMethod method = new GetMethod("http://www.google.com.hk/");//訪問下谷歌的首頁
- int statusCode = client.executeMethod(method);//狀態,一般200為OK狀態,其他情況會丟擲如404,500,403等錯誤
- if (statusCode != HttpStatus.SC_OK) {
- System.out.println("遠端訪問失敗。");
- }
- System.out.println(method.getResponseBodyAsString());//輸出反饋結果
- client.getHttpConnectionManager().closeIdleConnections(1);
- }catch(....) {.....}
注意,上述反饋結果可能和你用一個socket去模擬一些系統沒有什麼區別,因為返回的內容沒有任何價值,都是頁面標籤,當你和另一個系統互動時,它做response資料時,可以返回指定的json、xml等格式,用處就非常好用了,下面還會提及到它的好處;注意,採用GET方法,引數放在URL上面,要將非英文字元傳遞過去,需要對資料進行編碼,如:
- String url = "http://www.xxx.xxx.com/xxx?name=" + URLEncoder.encode("謝宇" , "GBK") + "&otherName=" + URLEncoder.encode("謝宇" , "GBK") ;
其中URLEncoder使用apache或jdk自帶的均可。
順便再寫個POST的例子:
- try {
- HttpClient client = new HttpClient();//定義client物件
- client.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET, "GBK");//指定傳送字符集為GBK格式
- client.getHttpConnectionManager().getParams().setConnectionTimeout(2000);//設定連線超時時間為2秒(連線初始化時間)
- PostMethod method = new PostMethod("http://www.xxx.xxx.com/aaa/bbb.do");
- method.setRequestBody(new NameValuePair[] {
- new NameValuePair("name" , "謝宇"), new NameValuePair("otherName" , "謝宇")
- });
- int statusCode = client.executeMethod(method);
- if (statusCode != HttpStatus.SC_OK) {
- System.out.println("遠端訪問失敗。");
- }
- System.out.println(method.getResponseBodyAsString());//輸出反饋結果
- client.getHttpConnectionManager().closeIdleConnections(1);
- }catch(....) {.....}
可以看出,Post的例子和Get差不多,唯一的區別是傳入引數的方法post採用了單獨逐個寫引數的方法,而get是在URL上面。
如果上面兩個例子,在你的機器上跑通了,那麼好,我們開始來討論它的安全性問題:
2、安全性怎麼樣去控制。
其實介面既然是人定義的,也就是協議是自己控制的,我們基於了一種輕量級的程式設計模式,讓伺服器端和客戶端編碼都變得十分簡單,簡單了,安全問題來了,誰都可以呼叫,如果你的介面是十分open的,那麼這不是問題,只是控制好併發就可以,但是如果存在安全隱患的話,那麼就有問題了,這個需要雙方定義好一個加密方法,加密的粒度可以根據實際情況來決定,這種傳送最好不要使用簡單加密,一般有兩種方法:
其一:使用不可逆加密演算法(如:MD5,注意md5在對中文資料加密時,採用不同字符集轉碼加密出來結果也不一樣),不可逆加密演算法,就必須要雙方都約定一個協議,在傳遞的引數上增加一個加密後的token值,其餘的引數照樣傳遞,加密過程為使用某種key與資料本身進行組合,並且存在一些動態變化性,將加密資料作為一個引數傳遞到接收方,接收方使用相同的方法得到一個密文,兩個密文進行對比,若對比一致,則認證成功,若對比不一致,則認證失敗;這樣做算是比較簡單的方法,有些還用了可逆和不可逆同時來用,先將資料按照可逆演算法加密,然後再計算token,不過比較複雜些了。
其二:可逆加密演算法,但是這種可逆,需要有一個密匙,最好的是非對稱密匙,而且最好是雙方的密匙可以隨著某個值而變化,而不是固定的密匙。非對稱密匙比較複雜,如果要用的話,這個可能會比較麻煩,安全級別極高的可以考慮,如果你的密匙本身可以隨著某種方式得到一個變化,使用對稱也基本夠用,如:Blowfish就還算是不錯的,但是沒有密匙的就別用了,類似Base64就太簡單了;當然你也可以像上面說的,可逆和不可逆混用來提高安全級別。
3、httpclient在併發量較高的呼叫下問題如何去解決
前面有提及到httpclient模擬系統之間的互動,如果系統之間的互動不高,是非常輕鬆的動作,不過httpclient是作為WEB容器的web請求存在,在http協議下,都是無狀態的協議,也就是連線-請求-反饋-斷開幾個基本動作,好在現在WEB容器有了keep-alive的功能,包括很多負載均衡裝置:如:LB、LVS、nginx、apache、jboss、tomcat等等都是支援的,雖然支援,但是看看上面的程式碼,就發現,每次請求都會重新建立連線,如何讓他們不要重複建立連線呢?或者說在伺服器端沒有斷開前不要重複建立連線,一個連線可以被使用多次請求,不至於一次請求就被斷開一次;建立一次連線需要三次握手過程,以及更多的網路開銷,所以你懂的。
道理很簡單,其實和連結資料庫差不多,將上面的請求的client物件以及method物件作為共享變數時,發起多次請求,平均效率會提升2倍左右,注意,這裡是迴圈測試,而不是多執行緒。
但是對於併發較高的,我們不可能將method只用一個,因為它本身不能併發,於是我們就要用多個,在多個共享的物件中,如果控制好徵用,有涉及到連線池的問題,不過這個連線池相對資料庫的連線池要簡單很多,因為,重試等動作,apache已經為你包裝好了,你只需要順序找和分配就可以了,如何降低競爭就是演算法和策略的問題了。
但是,讓客戶端來編寫這麼一段程式碼是不是有點過分,當然你願意寫也是可以的,其實apache又為我們提供了一個後面就是非同步httpclient(其實這裡所知的非同步並非真正的非同步IO模式),也就是將這部分包裝了,對於訪問者來說還是同步的,只是在IO層面是非阻塞的了,這個就配合了伺服器端的keep-alive,就像伺服器端同時向一個站點請求多個資源時,我們希望是一個連線,而不是多個連結,其實在很多瀏覽器(如chrome、FF)都可以監控到它同時請求的伺服器端資源,那麼要用httpclient實現非同步IO應該如何來做呢?其實也蠻簡單的,下面是一個簡單例子:
首先你要增加一個關於非同步IO需要的包:
1、async-http-client包,可以在這裡下載:https://oss.sonatype.org/content/repositories/releases/com/ning/async-http-client/1.6.2/
2、log4j的包,這個不用我說了,都知道在哪裡
3、slf4j-spi 的包,目前用1.5以上的版本比較多。
4、slf4j-log4j 的包,可以看出,slf4j是在log4j基礎上包裝的。
OK,就這幾個了,弄好後再看看下面這段程式碼,通過使用它,效能可以得到明顯改善:
- AsyncHttpClient client = new AsyncHttpClient();
- try {
- Future<Response> f = client.prepareGet("http://www.google.com.hk/").execute();
- System.out.println(f.get().getResponseBody("Big5"));//谷歌的輸出編碼集為Big5,反向解析結果的時候使用
- }catch(...) {....}
這段程式碼是不是超級簡單,可以通過上面描述的三種方式:
1、直接呼叫
2、將GetMethod或PostMethod物件作為共享物件反覆使用。
3、使用AsyncHttpClient
這三種方法,非別使用一次呼叫、迴圈多次呼叫、併發呼叫來測試效能,後面兩者的效能比第一種方法的效能要高很多。
原文:http://blog.csdn.net/xieyuooo/article/details/7182354