HttpClient使用過程中的安全隱患,這個有些標(biāo)題黨。因為這本身不是HttpClient的問題,而是使用者的問題。
安全隱患場景說明:
一旦請求大數(shù)據(jù)資源,則HttpClient線程會被長時間占有。即便調(diào)用了org.apache.commons.httpclient.HttpMethod#releaseConnection()方法,也無濟于事。
如果請求的資源是應(yīng)用可控的,那么不存在任何問題。可是恰恰我們應(yīng)用的使用場景是,請求資源由用戶自行輸入,于是乎,我們不得不重視這個問題。
我們跟蹤releaseConnection代碼發(fā)現(xiàn):
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的設(shè)計理念是重復(fù)使用HttpConnection,豈能輕易被強制close呢。
怎么辦?有朋友說,不是有time out設(shè)置嘛,設(shè)置下就可以下。
我先來解釋下Httpclient中兩個time out的概念:
1.public static final String CONNECTION_TIMEOUT = "http.connection.timeout";
即創(chuàng)建socket連接的超時時間:java.net.Socket#connect(SocketAddress endpoint, int timeout)中的timeout
2.public static final String SO_TIMEOUT = "http.socket.timeout";
即read data過程中,等待數(shù)據(jù)的timeout:java.net.Socket#setSoTimeout(int timeout)中的timeout
而在我上面場景中,這兩個timeout都不滿足,確實是由于資源過大,而占用了大量的請求時間。
問題總是要解決的,解決思路如下:
1.利用DelayQueue,管理所有請求
2.利用一個異步線程監(jiān)控,關(guān)閉超長時間的請求
演示代碼如下:
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(); // 超時監(jiān)控線程
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 * 監(jiān)工:監(jiān)控線程,通過DelayQueue,阻塞得到最近超時的對象,強制關(guān)閉
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接口的實現(xiàn) 根據(jù)請求當(dāng)前時間+該請求允許timeout時間,和當(dāng)前時間比較,判斷是否已經(jīng)超時
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,但是發(fā)現(xiàn)同事已經(jīng)有比較纖細的描述,就加個鏈接吧 (人懶,沒辦法)
http://agapple.iteye.com/blog/916837
http://agapple.iteye.com/blog/947133
備注:
HttpClient3.1中,SimpleHttpConnectionManager才有shutdown方法,3.0.1中還存在 :)