stone2083

          HttpClient使用過程中的安全隱患

          HttpClient使用過程中的安全隱患,這個有些標題黨。因為這本身不是HttpClient的問題,而是使用者的問題。

          安全隱患場景說明:
          一旦請求大數據資源,則HttpClient線程會被長時間占有。即便調用了org.apache.commons.httpclient.HttpMethod#releaseConnection()方法,也無濟于事。
          如果請求的資源是應用可控的,那么不存在任何問題。可是恰恰我們應用的使用場景是,請求資源由用戶自行輸入,于是乎,我們不得不重視這個問題。

          我們跟蹤releaseConnection代碼發現:
          org.apache.commons.httpclient.HttpMethodBase#releaseConnection()
           1 public void releaseConnection() {
           2     try {
           3         if (this.responseStream != null) {
           4             try {
           5                 // FYI - this may indirectly invoke responseBodyConsumed.
           6                 this.responseStream.close();
           7             } catch (IOException ignore) {
           8             }
           9         }
          10     } finally {
          11         ensureConnectionRelease();
          12     }
          13 }
          org.apache.commons.httpclient.ChunkedInputStream#close()
           1 public void close() throws IOException {
           2     if (!closed) {
           3         try {
           4             if (!eof) {
           5                 exhaustInputStream(this);
           6             }
           7         } finally {
           8             eof = true;
           9             closed = true;
          10         }
          11     }
          12 }
          org.apache.commons.httpclient.ChunkedInputStream#exhaustInputStream(InputStream inStream)
          1 static void exhaustInputStream(InputStream inStream) throws IOException {
          2     // read and discard the remainder of the message
          3     byte buffer[] = new byte[1024];
          4     while (inStream.read(buffer) >= 0) {
          5         ;
          6     }
          7 }
          看到了吧,所謂的丟棄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.利用一個異步線程監控,關閉超長時間的請求

          演示代碼如下:
            1 public class Misc2 {
            2 
            3     private static final DelayQueue<Timeout> TIMEOUT_QUEUE = new DelayQueue<Timeout>();
            4 
            5     public static void main(String[] args) throws Exception {
            6         new Monitor().start(); // 超時監控線程
            7 
            8         new Request(4).start();// 模擬第一個下載
            9         new Request(3).start();// 模擬第二個下載
           10         new Request(2).start();// 模擬第三個下載
           11     }
           12 
           13     /**
           14      * 模擬一次HttpClient請求
           15      * 
           16      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
           17      */
           18     public static class Request extends Thread {
           19 
           20         private long delay;
           21 
           22         public Request(long delay){
           23             this.delay = delay;
           24         }
           25 
           26         public void run() {
           27             HttpClient hc = new HttpClient();
           28             GetMethod req = new GetMethod("http://www.python.org/ftp/python/2.7.1/Python-2.7.1.tgz");
           29             try {
           30                 TIMEOUT_QUEUE.offer(new Timeout(delay * 1000, hc.getHttpConnectionManager()));
           31                 hc.executeMethod(req);
           32             } catch (Exception e) {
           33                 System.out.println(e);
           34             }
           35             req.releaseConnection();
           36         }
           37 
           38     }
           39 
           40     /**
           41      * 監工:監控線程,通過DelayQueue,阻塞得到最近超時的對象,強制關閉
           42      * 
           43      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
           44      */
           45     public static class Monitor extends Thread {
           46 
           47         @Override
           48         public void run() {
           49             while (true) {
           50                 try {
           51                     Timeout timeout = TIMEOUT_QUEUE.take();
           52                     timeout.forceClose();
           53                 } catch (InterruptedException e) {
           54                     System.out.println(e);
           55                 }
           56             }
           57         }
           58 
           59     }
           60 
           61     /**
           62      * 使用delay queue,對Delayed接口的實現 根據請求當前時間+該請求允許timeout時間,和當前時間比較,判斷是否已經超時
           63      * 
           64      * @author <a href="mailto:li.jinl@alibaba-inc.com">Stone.J</a> 2011-4-9
           65      */
           66     public static class Timeout implements Delayed {
           67 
           68         private long                  debut;
           69         private long                  delay;
           70         private HttpConnectionManager manager;
           71 
           72         public Timeout(long delay, HttpConnectionManager manager){
           73             this.debut = System.currentTimeMillis();
           74             this.delay = delay;
           75             this.manager = manager;
           76         }
           77 
           78         public void forceClose() {
           79             System.out.println(this.debut + ":" + this.delay);
           80             if (manager instanceof SimpleHttpConnectionManager) {
           81                 ((SimpleHttpConnectionManager) manager).shutdown();
           82             }
           83             if (manager instanceof MultiThreadedHttpConnectionManager) {
           84                 ((MultiThreadedHttpConnectionManager) manager).shutdown();
           85             }
           86         }
           87 
           88         @Override
           89         public int compareTo(Delayed o) {
           90             if (o instanceof Timeout) {
           91                 Timeout timeout = (Timeout) o;
           92                 if (this.debut + this.delay == timeout.debut + timeout.delay) {
           93                     return 0;
           94                 } else if (this.debut + this.delay > timeout.debut + timeout.delay) {
           95                     return 1;
           96                 } else {
           97                     return -1;
           98                 }
           99             }
          100             return 0;
          101         }
          102 
          103         @Override
          104         public long getDelay(TimeUnit unit) {
          105             return debut + delay - System.currentTimeMillis();
          106         }
          107 
          108     }
          109 
          110 }


          本來還想詳細講下DelayQueue,但是發現同事已經有比較纖細的描述,就加個鏈接吧 (人懶,沒辦法)
          http://agapple.iteye.com/blog/916837
          http://agapple.iteye.com/blog/947133

          備注:
          HttpClient3.1中,SimpleHttpConnectionManager才有shutdown方法,3.0.1中還存在 :)

          posted on 2011-04-09 20:46 stone2083 閱讀(4765) 評論(0)  編輯  收藏 所屬分類: java

          主站蜘蛛池模板: 江陵县| 鄂托克旗| 晋江市| 团风县| 泌阳县| 霍林郭勒市| 车致| 台中县| 改则县| 宁晋县| 三都| 神木县| 霞浦县| 丁青县| 肥东县| 平谷区| 庆元县| 镇远县| 阿图什市| 莱芜市| 通江县| 阜宁县| 新龙县| 贡山| 鹿泉市| 扶沟县| 茶陵县| 保山市| 额敏县| 淳化县| 紫阳县| 苍溪县| 德州市| 习水县| 蕲春县| 绥化市| 唐山市| 三门峡市| 涟水县| 莫力| 博兴县|