HttpClient使用過程中的安全隱患
HttpClient使用過程中的安全隱患,這個有些標題黨。因為這本身不是HttpClient的問題,而是使用者的問題。
安全隱患場景說明:
一旦請求大數據資源,則HttpClient線程會被長時間占有。即便調用了org.apache.commons.httpclient.HttpMethod#releaseConnection()方法,也無濟于事。
如果請求的資源是應用可控的,那么不存在任何問題。可是恰恰我們應用的使用場景是,請求資源由用戶自行輸入,于是乎,我們不得不重視這個問題。
我們跟蹤releaseConnection代碼發現:
org.apache.commons.httpclient.HttpMethodBase#releaseConnection()
- public void releaseConnection() {
- try {
- if ( this .responseStream != null ) {
- try {
- // FYI - this may indirectly invoke responseBodyConsumed.
- this .responseStream.close();
- } catch (IOException ignore) {
- }
- }
- } finally {
- ensureConnectionRelease();
- }
- }
org.apache.commons.httpclient.ChunkedInputStream#close()
- public void close() throws IOException {
- if ( ! closed) {
- try {
- if ( ! eof) {
- exhaustInputStream( this );
- }
- } finally {
- eof = true ;
- closed = true ;
- }
- }
- }
org.apache.commons.httpclient.ChunkedInputStream#exhaustInputStream(InputStream inStream)
- static void exhaustInputStream(InputStream inStream) throws IOException {
- // read and discard the remainder of the message
- byte buffer[] = new byte [ 1024 ];
- while (inStream.read(buffer) >= 0 ) {
- ;
- }
- }
看到了吧,所謂的丟棄response,其實是讀完了一次請求的response,只是不做任何處理罷了。
想想也是,HttpClient的設計理念是重復使用HttpConnection,豈能輕易被強制close呢。
怎么辦?有朋友說,不是有time out設置嘛,設置下就可以下。
我先來解釋下Httpclient中兩個time out的概念:
1.public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
即創建socket連接的超時時間:java.net.Socket#connect(SocketAddress endpoint, int timeout)中的timeout
2.public static final String SO_TIMEOUT = "http.socket.timeout";
即read data過程中,等待數據的timeout:java.net.Socket#setSoTimeout(int timeout)中的timeout
而在我上面場景中,這兩個timeout都不滿足,確實是由于資源過大,而占用了大量的請求時間。
問題總是要解決的,解決思路如下:
1.利用DelayQueue,管理所有請求
2.利用一個異步線程監控,關閉超長時間的請求
演示代碼如下:
- public class Misc2 {
- private static final DelayQueue < Timeout > TIMEOUT_QUEUE = new DelayQueue < Timeout > ();
- public static void main(String[] args) throws Exception {
- new Monitor().start(); // 超時監控線程
- new Request( 4 ).start(); // 模擬***個下載
- new Request( 3 ).start(); // 模擬第二個下載
- new Request( 2 ).start(); // 模擬第三個下載
- }
- /**
- * 模擬一次HttpClient請求
- *
- * @author Stone.J 2011-4-9
- */
- public static class Request extends Thread {
- private long delay;
- public Request( long delay){
- this .delay = delay;
- }
- public void run() {
- HttpClient hc = new HttpClient();
- GetMethod req = new GetMethod( " http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz " );
- try {
- TIMEOUT_QUEUE.offer( new Timeout(delay * 1000 , hc.getHttpConnectionManager()));
- hc.executeMethod(req);
- } catch (Exception e) {
- System.out.println(e);
- }
- req.releaseConnection();
- }
- }
- /**
- * 監工:監控線程,通過DelayQueue,阻塞得到最近超時的對象,強制關閉
- *
- * @author Stone.J 2011-4-9
- */
- public static class Monitor extends Thread {
- @Override
- public void run() {
- while ( true ) {
- try {
- Timeout timeout = TIMEOUT_QUEUE.take();
- timeout.forceClose();
- } catch (InterruptedException e) {
- System.out.println(e);
- }
- }
- }
- }
- /**
- * 使用delay queue,對Delayed接口的實現 根據請求當前時間+該請求允許timeout時間,和當前時間比較,判斷是否已經超時
- *
- * @author Stone.J 2011-4-9
- */
- public static class Timeout implements Delayed {
- private long debut;
- private long delay;
- private HttpConnectionManager manager;
- public Timeout( long delay, HttpConnectionManager manager){
- this .debut = System.currentTimeMillis();
- this .delay = delay;
- this .manager = manager;
- }
- public void forceClose() {
- System.out.println( this .debut + " : " + this .delay);
- if (manager instanceof SimpleHttpConnectionManager) {
- ((SimpleHttpConnectionManager) manager).shutdown();
- }
- if (manager instanceof MultiThreadedHttpConnectionManager) {
- ((MultiThreadedHttpConnectionManager) manager).shutdown();
- }
- }
- @Override
- public int compareTo(Delayed o) {
- if (o instanceof Timeout) {
- Timeout timeout = (Timeout) o;
- if ( this .debut + this .delay == timeout.debut + timeout.delay) {
- return 0 ;
- } else if ( this .debut + this .delay > timeout.debut + timeout.delay) {
- return 1 ;
- } else {
- return - 1 ;
- }
- }
- return 0 ;
- }
- @Override
- public long getDelay(TimeUnit unit) {
- return debut + delay - System.currentTimeMillis();
- }
- }
- }
【編輯推薦】