httpclient详解

HttpClient详解(一)

HttpClient是一个客户端的HTTP通信实现库。HttpClient的目标是发送和接收HTTP报文。

HTTP请求

HttpClient 支持所有定义在HTTP/1.1版本中的HTTP方法:GET,HEAD,POST,PUT,DELETE,TRACE和OPTIONS。对于每个方法类型都有一个特殊的类:HttpGet,HttpHead,HttpPost,HttpPut,HttpDelete,HttpTrace和 HttpOptions。HttpClient提供很多工具方法来简化创建和修改执行URI:

  • URI也可以编程来拼装:
1
2
3
4
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
"q=httpclient&btnG=Google+Search&aq=f&oq=", null);
HttpGet httpget = new HttpGet(uri);
System.out.println(httpget.getURI());

输出内容为:
http://www.google.com/search?q=httpclient&btnG=Google+Search&aq=f&oq=

  • 查询字符串也可以从独立的参数中来生成:
1
2
3
4
5
6
7
8
List<NameValuePair> qparams = new ArrayList<NameValuePair>();
qparams.add(new BasicNameValuePair("q", "httpclient"));
qparams.add(new BasicNameValuePair("btnG", "Google Search"));
qparams.add(new BasicNameValuePair("aq", "f"));
qparams.add(new BasicNameValuePair("oq", null));
URI uri = URIUtils.createURI("http", "www.google.com", -1, "/search",
URLEncodedUtils.format(qparams, "UTF-8"), null);
HttpGet httpget = new HttpGet(uri);

HTTP响应

HTTP响应是由服务器在接收和解释请求报文之后返回发送给客户端的报文。响应报文的第一行包含了协议版本,之后是数字状态码和相关联的文本段。

1
2
3
4
5
6
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
System.out.println(response.getProtocolVersion());
System.out.println(response.getStatusLine().getStatusCode());
System.out.println(response.getStatusLine().getReasonPhrase());
System.out.println(response.getStatusLine().toString());

输出内容为:
HTTP/1.1
200
OK
HTTP/1.1 200 OK

处理报文头部

一个HTTP报文可以包含很多描述如内容长度,内容类型等信息属性的头部信息。HttpClient提供获取,添加,移除和枚举头部信息的方法。

1
2
3
4
5
6
7
8
9
HttpResponse response = new BasicHttpResponse(HttpVersion.HTTP_1_1,
HttpStatus.SC_OK, "OK");
response.addHeader("Set-Cookie",
"c1=a; path=/; domain=localhost");
response.addHeader("Set-Cookie",
"c2=b; path="/", c3=c; domain="localhost"");
Header h1 = response.getFirstHeader("Set-Cookie");
Header h2 = response.getLastHeader("Set-Cookie");
Header[] hs = response.getHeaders("Set-Cookie");

获得给定类型的所有头部信息最有效的方式是使用HeaderIterator接口。

1
2
3
4
HeaderIterator it = response.headerIterator("Set-Cookie");
while (it.hasNext()) {
System.out.println(it.next());
}

HTTP实体

HTTP 报文可以携带和请求或响应相关的内容实体。
HttpClient根据其内容出自何处区分三种类型的实体:
- streamed流式:内容从流中获得,或者在运行中产生。特别是这种分类包含从HTTP响应中获取的实体。流式实体是不可重复生成的。
- self-contained自我包含式:内容在内存中或通过独立的连接或其它实体中获得。自我包含式的实体是可以重复生成的。这种类型的实体会经常用于封闭HTTP请求的实体。
- wrapping包装式:内容从另外一个实体中获得。

要从实体中读取内容,可以通过HttpEntity#getContent()方法从输入流中获取,这会返回一个java.io.InputStream对 象,或者提供一个输出流到HttpEntity#writeTo(OutputStream)方法中,这会一次返回所有写入到给定流中的内容。当实体通过一个收到的报文获取时,HttpEntity#getContentType()方法和 HttpEntity#getContentLength()方法可以用来读取通用的元数据,如Content-Type和Content-Length 头部信息(如果它们是可用的)。因为头部信息Content-Type可以包含对文本MIME类型的字符编码,比如text/plain或text /html,HttpEntity#getContentEncoding()方法用来读取这个信息。如果头部信息不可用,那么就返回长度-1。

当完成一个响应实体,那么保证所有实体内容已经被完全消耗是很重要的,所以连接可以安全的放回到连接池中,而且可以通过连接管理器对后续的请求重用连接。处 理这个操作的最方便的方法是调用HttpEntity#consumeContent()方法来消耗流中的任意可用内容。HttpClient探测到内容 流尾部已经到达后,会立即会自动释放低层连接,并放回到连接管理器。HttpEntity#consumeContent()方法调用多次也是安全的。

HttpClient为很多公用的数据容器,比如字符串,字节数组,输入流和文件提供了一些类:StringEntity,ByteArrayEntity,InputStreamEntity和FileEntity。

1
2
3
4
File file = new File("somefile.txt");
FileEntity entity = new FileEntity(file, "text/plain; charset="UTF-8"");
HttpPost httppost = new HttpPost("http://localhost/action.do");
httppost.setEntity(entity);

HTML表单

许多应用程序需要频繁模拟提交一个HTML表单的过程,比如,为了来记录一个Web应用程序或提交输出数据。HttpClient提供了特殊的实体类UrlEncodedFormEntity来这个满足过程。

1
2
3
4
5
6
List<NameValuePair> formparams = new ArrayList<NameValuePair>();
formparams.add(new BasicNameValuePair("param1", "value1"));
formparams.add(new BasicNameValuePair("param2", "value2"));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formparams, "UTF-8");
HttpPost httppost = new HttpPost("http://localhost/handler.do");
httppost.setEntity(entity);

内容分块

设置 HttpEntity#setChunked()方法为true是通知HttpClient分块编码的首选。请注意HttpClient将会使用标识作为提示。当使用的HTTP协议版本,如HTTP/1.0版本,不支持分块编码时,这个值会被忽略。

1
2
3
4
5
StringEntity entity = new StringEntity("important message",
"text/plain; charset="UTF-8"");
entity.setChunked(true);
HttpPost httppost = new HttpPost("http://localhost/acrtion.do");
httppost.setEntity(entity);

响应控制器

控制响应的最简便和最方便的方式是使用ResponseHandler接口。这个放完完全减轻了用户关于连接管理的担心。当使用ResponseHandler时,HttpClient将会自动关注并保证释放连接到连接管理器中去,而不管请求执行是否成功或引发了异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://localhost/");
ResponseHandler<byte[]> handler = new ResponseHandler<byte[]>() {
    public byte[] handleResponse(
        HttpResponse response) throws ClientProtocolException, IOException {
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            return EntityUtils.toByteArray(entity);
        } else {
            return null;
        }
    }
};
byte[] response = httpclient.execute(httpget, handler);

HTTP执行的环境

最 初,HTTP是被设计成无状态的,面向请求-响应的协议。然而,应用程序经常需要通过一些逻辑相关的请求-响应交换来持久状态信息。HttpClient允许HTTP请求在一个特定的执行环境中来执行,简称为HTTP上下文。HTTP上下文功能和java.util.Map很相似。 它仅仅是任意命名参数值的集合。
在HTTP请求执行的这一过程中,HttpClient添加了下列属性到执行上下文中:
'http.connection':HttpConnection实例代表了连接到目标服务器的真实连接。
'http.target_host':HttpHost实例代表了连接目标。
'http.proxy_host':如果使用了,HttpHost实例代表了代理连接。
'http.request':HttpRequest实例代表了真实的HTTP请求。
'http.response':HttpResponse实例代表了真实的HTTP响应。
'http.request_sent':java.lang.Boolean对象代表了暗示真实请求是否被完全传送到目标连接的标识。
比如,为了决定最终的重定向目标,在请求执行之后,可以检查http.target_host属性的值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpContext localContext = new BasicHttpContext();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget, localContext);
HttpHost target = (HttpHost) localContext.getAttribute(
ExecutionContext.HTTP_TARGET_HOST);
System.out.println("Final target: " + target);
HttpEntity entity = response.getEntity();
if (entity != null) {
    entity.consumeContent();
}

幂等的方法

HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等操作对于代理和缓存来说具有“友好性”,因为幂等操作的额外执行不会对二者产生危害性后果(除了带宽浪费)。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变。

异常自动恢复

默认情况下,HttpClient会试图自动从I/O异常中恢复。默认的自动恢复机制是受很少一部分已知的异常是安全的这个限制。
HttpClient不会从任意逻辑或HTTP协议错误(那些是从HttpException类中派生出的)中恢复的。
HttpClient将会自动重新执行那么假设是幂等的方法。
HttpClient将会自动重新执行那些由于运输异常失败,而HTTP请求仍然被传送到目标服务器(也就是请求没有完全被送到服务器)失败的方法。

请求重试处理

为了开启自定义异常恢复机制,应该提供一个HttpRequestRetryHandler接口的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpRequestRetryHandler myRetryHandler = new HttpRequestRetryHandler() {
public boolean retryRequest(IOException exception,
    int executionCount,HttpContext context) {
        if (executionCount >= 5) {
            // 如果超过最大重试次数,那么就不要继续了
            return false;
        }
        if (exception instanceof NoHttpResponseException) {
            // 如果服务器丢掉了连接,那么就重试
            return true;
        }
        if (exception instanceof SSLHandshakeException) {
            // 不要重试SSL握手异常
            return false;
        }
        HttpRequest request = (HttpRequest) context.getAttribute(
            ExecutionContext.HTTP_REQUEST);
        boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
        if (idempotent) {
            // 如果请求被认为是幂等的,那么就重试
            return true;
        }
        return false;
    }
};
httpclient.setHttpRequestRetryHandler(myRetryHandler);

中止请求

被HttpClient执行的HTTP请求可以在执行的任意阶段通过调用HttpUriRequest#abort()方 法而中止。这个方法是线程安全的,而且可以从任意线程中调用。

HTTP参数

在 HTTP请求执行过程中,HttpRequest对象的HttpParams是和用于执行请求的HttpClient实例的HttpParams联系在一起的。这使得设置在HTTP请求级别的参数优先于设置在HTTP客户端级别的HttpParams。推荐的做法是设置普通参数对所有的在HTTP客户端级别的HTTP请求共享,而且可以选择性重写具体在HTTP请求级别的参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,HttpVersion.HTTP_1_0);
httpclient.getParams().setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET,"UTF-8");
HttpGet httpget = new HttpGet("http://www.google.com/");
httpget.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION,HttpVersion.HTTP_1_1);
httpget.getParams().setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE,Boolean.FALSE);
httpclient.addRequestInterceptor(new HttpRequestInterceptor() {
    public void process(final HttpRequest request,
        final HttpContext context) throws HttpException, IOException {
        System.out.println(request.getParams().getParameter(
            CoreProtocolPNames.PROTOCOL_VERSION));
        System.out.println(request.getParams().getParameter(
            CoreProtocolPNames.HTTP_CONTENT_CHARSET));
        System.out.println(request.getParams().getParameter(
            CoreProtocolPNames.USE_EXPECT_CONTINUE));
        System.out.println(request.getParams().getParameter(
            CoreProtocolPNames.STRICT_TRANSFER_ENCODING));
    }
});

HttpClient详解(二)

HttpClient有一个对连接初始化和终止,还有在活动连接上I/O操作的完整控制。而连接操作的很多方面可以使用一些参数来控制。

连接参数

  • 'http.socket.timeout': 定义了套接字的毫秒级超时时间(SO_TIMEOUT),换句话说,在两个连续的数据包之间最大的闲置时间。如果超时时间是0就解释为是一个无限大的超时时间。这个参数期望得到一个java.lang.Integer类型的值。如果这个参数没有被设置,那么读取操作就不会超时(无限大的超 时时间)。
  • 'http.tcp.nodelay': 决定了是否使用Nagle算法。Nagle算法视图通过最小化发送的分组数量来节省带宽。当应用程序希望降低网络延迟并提高性能时,它们可以关闭 Nagle算法(也就是开启TCP_NODELAY)。数据将会更早发送,增加了带宽消耗的成文。这个参数期望得到一个 java.lang.Boolean类型的值。如果这个参数没有被设置,那么TCP_NODELAY就会开启(无延迟)。
  • 'http.socket.buffer- size':决定了内部套接字缓冲使用的大小,来缓冲数据同时接收/传输HTTP报文。这个参数期望得到一个java.lang.Integer类型的 值。如果这个参数没有被设置,那么HttpClient将会分配8192字节的套接字缓存。
  • 'http.socket.linger': 使用指定的秒数拖延时间来设置SO_LINGER。最大的连接超时值是平台指定的。值0暗示了这个选项是关闭的。值-1暗示了使用了JRE默认的。这个设 置仅仅影响套接字关闭操作。如果这个参数没有被设置,那么就假设值为-1(JRE默认)。
  • 'http.connection.timeout': 决定了直到连接建立时的毫秒级超时时间。超时时间的值为0解释为一个无限大的时间。这个参数期望得到一个java.lang.Integer类型的值。如 果这个参数没有被设置,连接操作将不会超时(无限大的超时时间)。
  • 'http.connection.stalecheck': 决定了是否使用旧的连接检查。当在一个连接之上执行一个请求而服务器端的连接已经关闭时,关闭旧的连接检查可能导致在获得一个I/O错误风险时显著的性能 提升(对于每一个请求,检查时间可以达到30毫秒)。这个参数期望得到一个java.lang.Boolean类型的值。出于性能的关键操作,检查应该被 关闭。如果这个参数没有被设置,那么旧的连接将会在每个请求执行之前执行。
  • 'http.connection.max- line-length':决定了最大请求行长度的限制。如果设置为一个正数,任何HTTP请求行超过这个限制将会引发 java.io.IOException异常。负数或零将会关闭这个检查。这个参数期望得到一个java.lang.Integer类型的值。如果这个参 数没有被设置,那么就不强制进行限制了。
  • 'http.connection.max- header-count':决定了允许的最大HTTP头部信息数量。如果设置为一个正数,从数据流中获得的HTTP头部信息数量超过这个限制就会引发 java.io.IOException异常。负数或零将会关闭这个检查。这个参数期望得到一个java.lang.Integer类型的值。如果这个参 数没有被设置,那么就不 强制进行限制了。
  • 'http.connection.max- status-line-garbage':决定了在期望得到HTTP响应状态行之前可忽略请求行的最大数量。使用HTTP/1.1持久性连接,这个问题 产生的破碎的脚本将会返回一个错误的Content-Length(有比指定的字节更多的发送)。不幸的是,在某些情况下,这个不能在错误响应后来侦测, 只能在下一次之前。所以HttpClient必须以这种方式跳过那些多余的行。这个参数期望得到一个java.lang.Integer类型的值。0是不 允许在状态行之前的所有垃圾/空行。使用java.lang.Integer#MAX_VALUE来设置不限制的数字。如果这个参数没有被设置那就假设是 不限制的。

安全HTTP连接

如果信息在两个不能由非认证的第三方进行读取或修改的终端之间传输,HTTP连接可以被认为是安全的。SSL/TLS协议是用来保证HTTP传输安全使用最广泛的技术。而其它加密技术也可以被使用。通常来说,HTTP传输是在SSL/TLS加密连接之上分层的。

套接字工厂

LayeredSocketFactory 是SocketFactory接口的扩展。分层的套接字工厂可HTTP连接内部使用java.net.Socket对象来处理数据在线路上的传输。它们依赖SocketFactory接口来创建,初始化和连接套接字。这会使得HttpClient的用户可以提供在运行时指定套接字初始化代码的应用程序。
PlainSocketFactory是创建和初始化普通的(不加密的)套接字的默认工厂。
创建套接字的过程和连接到主机的过程是不成对的,所以套接字在连接操作封锁时可以被关闭。

1
2
3
4
5
PlainSocketFactory sf = PlainSocketFactory.getSocketFactory();
Socket socket = sf.createSocket();
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 8080, null, -1, params)

SSL/TLS的定制

HttpClient使用SSLSocketFactory来创建SSL连接。SSLSocketFactory允许高度定制。它可以使用javax.net.ssl.SSLContext的实例作为参数,并使用它来创建定制SSL连接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
TrustManager easyTrustManager = new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain,
        String authType) throws CertificateException {
        // 哦,这很简单!
    }
    @Override
    public void checkServerTrusted(X509Certificate[] chain,
        String authType) throws CertificateException {
        //哦,这很简单!
    }
    @Override
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
};
SSLContext sslcontext = SSLContext.getInstance("TLS");
sslcontext.init(null, new TrustManager[] { easyTrustManager }, null);
SSLSocketFactory sf = new SSLSocketFactory(sslcontext);
SSLSocket socket = (SSLSocket) sf.createSocket();
socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5" });
HttpParams params = new BasicHttpParams();
params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 1000L);
sf.connectSocket(socket, "locahost", 443, null, -1, params);

协议模式

Scheme 类代表了一个协议模式,比如“http”或“https”同时包含一些协议属性,比如默认端口,用来为给定协议创建java.net.Socket实例的 套接字工厂。SchemeRegistry类用来维持一组Scheme,当去通过请求URI建立连接时,HttpClient可以从中选择:

1
2
3
4
5
6
7
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
Scheme https = new Scheme("https", sf, 443);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
sr.register(https);

HttpClient代理配置

1
2
3
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpHost proxy = new HttpHost("someproxy", 8080);
httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);

HTTP连接管理器

HTTP 连接是复杂的,有状态的,线程不安全的对象需要正确的管理以便正确地执行功能。HTTP连接在同一时间仅仅只能由一个执行线程来使用。 HttpClient采用一个特殊实体来管理访问HTTP连接,这被称为HTTP连接管理器,代表了ClientConnectionManager接口。一个HTTP连接管理器的目的是作为工厂服务于新的HTTP连接,管理持久连接和同步访问持久连接来确保同一时间仅有一个线程可以访问一个连接。

SingleClientConnManager 是一个简单的连接管理器,在同一时间它仅仅维护一个连接。尽管这个类是线程安全的,但它应该被用于一个执行线程。ThreadSafeClientConnManager 是一个复杂的实现来管理客户端连接池,它也可以从多个执行线程中服务连接请求。对每个基本的路由,连接都是池管理的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
HttpParams params = new BasicHttpParams();
ConnManagerParams.setTimeout(params, 2000);
HttpConnectionParams.setConnectionTimeout(params, 10000);
HttpConnectionParams.setSoTimeout(params, 20000);

SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(
new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(
new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);

连接管理器关闭

当一个HttpClient实例不再需要时,而且即将走出使用范围,那么关闭连接管理器来保证由管理器保持活动的所有连接被关闭,由连接分配的系统资源被释放是很重要的。

1
2
3
4
5
6
7
8
9
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet("http://www.google.com/");
HttpResponse response = httpclient.execute(httpget);
HttpEntity entity = response.getEntity();
System.out.println(response.getStatusLine());
if (entity != null) {
entity.consumeContent();
}
httpclient.getConnectionManager().shutdown();

多线程执行请求

当配备连接池管理器时,比如ThreadSafeClientConnManager,HttpClient可以同时被用来执行多个请求,使用多线程执行。ThreadSafeClientConnManager 将会分配基于它的配置的连接。如果对于给定路由的所有连接都被租出了,那么连接的请求将会阻塞,直到一个连接被释放回连接池。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
HttpParams params = new BasicHttpParams();
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http",                                                    PlainSocketFactory.getSocketFactory(), 80));
ClientConnectionManager cm = new ThreadSafeClientConnManager(params, schemeRegistry);
HttpClient httpClient = new DefaultHttpClient(cm, params);
// 执行GET方法的URI
String[] urisToGet = {
"http://www.domain1.com/",
"http://www.domain2.com/",
"http://www.domain3.com/",
"http://www.domain4.com/"
};
// 为每个URI创建一个线程
GetThread[] threads = new GetThread[urisToGet.length];
for (int i = 0; i < threads.length; i++) {
    HttpGet httpget = new HttpGet(urisToGet[i]);
    threads[i] = new GetThread(httpClient, httpget);
}
// 开始执行线程
for (int j = 0; j < threads.length; j++) {
    threads[j].start();
}
// 合并线程
for (int j = 0; j < threads.length; j++) {
    threads[j].join();
}
static class GetThread extends Thread {
    private final HttpClient httpClient;
    private final HttpContext context;
    private final HttpGet httpget;
    public GetThread(HttpClient httpClient, HttpGet httpget) {
        this.httpClient = httpClient;
        this.context = new BasicHttpContext();
        this.httpget = httpget;
    }
    @Override
    public void run() {
        try {
            HttpResponse response = this.httpClient.execute(this.httpget, this.context);
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                // 对实体做些有用的事情...
                // 保证连接能释放回管理器
                entity.consumeContent();
            }
        } catch (Exception ex) {
            this.httpget.abort();
        }
    }
}

连接收回策略

使用专用的监控线 程来收回因为长时间不活动而被认为是过期的连接。监控线程可以周期地调用 ClientConnectionManager#closeExpiredConnections()方法来关闭所有过期的连接,从连接池中收回关闭的 连接。它也可以选择性调用ClientConnectionManager#closeIdleConnections()方法来关闭所有已经空闲超过给 定时间周期的连接。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static class IdleConnectionMonitorThread extends Thread {
    private final ClientConnectionManager connMgr;
    private volatile boolean shutdown;
    public IdleConnectionMonitorThread(ClientConnectionManager connMgr) {
        super();
        this.connMgr = connMgr;
    }
    @Override
    public void run() {
        try {
            while (!shutdown) {
                synchronized (this) {
                    wait(5000);
                    // 关闭过期连接
                    connMgr.closeExpiredConnections();
                    // 可选地,关闭空闲超过30秒的连接
                    connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                }
            }
        } catch (InterruptedException ex) {
            // 终止
        }
    }
    public void shutdown() {
        shutdown = true;
        synchronized (this) {
            notifyAll();
        }
    }
}

连接保持活动的策略

HTTP 规范没有确定一个持久连接可能或应该保持活动多长时间。一些HTTP服务器使用非标准的头部信息Keep-Alive来告诉客户端它们想在服务器端保持连接活动的周期秒数。如果这个信息可用,HttClient就会利用这个。如果头部信息Keep-Alive在响应中不存在,HttpClient假设连接无限期的保持活动。然而许多现实中的HTTP服务器配置了在特定不活动周期之后丢掉持久连接来保存系统资源,往往这是不通知客户端的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
        // 兑现'keep-alive'头部信息
        HeaderElementIterator it = new BasicHeaderElementIterator(
            response.headerIterator(HTTP.CONN_KEEP_ALIVE));
        while (it.hasNext()) {
            HeaderElement he = it.nextElement();
            String param = he.getName();
            String value = he.getValue();
            if (value != null && param.equalsIgnoreCase("timeout")) {
                try {
                    return Long.parseLong(value) * 1000;
                } catch(NumberFormatException ignore) {
                }
            }
        }
        HttpHost target = (HttpHost) context.getAttribute(
            ExecutionContext.HTTP_TARGET_HOST);
        if ("www.naughty-server.com".equalsIgnoreCase(target.getHostName())) {
            // 只保持活动5秒
            return 5 * 1000;
        } else {
            // 否则保持活动30秒
            return 30 * 1000;
        }
    }
});
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值