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,但是发现同事已经有比较纤细的描述,就加个链接吧 (人懒,没办法)