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 阅读(4756) 评论(0)  编辑  收藏 所属分类: java


只有注册用户登录后才能发表评论。


网站导航: