HttpClient 教程 (二)

第二章 链接管理

HttpClient有一个对链接初始化和终止,还有在活动链接上I/O操做的完整控制。而链接操做的不少方面可使用一些参数来控制。html

2.1 链接参数

这些参数能够影响链接操做:java

  • '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来设置不限制的数字。若是这个参数没有被设置那就假设是不限制的。

2.2 持久链接

从一个主机向另一个创建链接的过程是至关复杂的,并且包含了两个终端之间的不少包的交换,它是至关费时的。链接握手的开销是很重要的,特别是对小量的HTTP报文。若是打开的链接能够被重用来执行屡次请求,那么就能够达到很高的数据吞吐量。node

HTTP/1.1强调HTTP链接默认状况能够被重用于屡次请求。HTTP/1.0兼容的终端也可使用类似的机制来明确地交流它们的偏好来保证链接处于活动状态,也使用它来处理多个请求。HTTP代理也能够保持空闲链接处于一段时间的活动状态,防止对相同目标主机的一个链接也许对随后的请求须要。保持链接活动的能力一般被称做持久性链接。HttpClient彻底支持持久性链接。算法

2.3 HTTP链接路由

HttpClient可以直接或经过路由创建链接到目标主机,这会涉及多个中间链接,也被称为跳。HttpClient区分路由和普通链接,通道和分层。通道链接到目标主机的多个中间代理的使用也称做是代理链。浏览器

普通路由由链接到目标或仅第一次的代理来建立。通道路由经过代理链到目标链接到第一通道来创建。没有代理的路由不是通道的,分层路由经过已存在链接的分层协议来创建。协议仅仅能够在到目标的通道上或在没有代理的直接链接上分层。缓存

2.3.1 路由计算

RouteInfo接口表明关于最终涉及一个或多个中间步骤或跳的目标主机路由的信息。HttpRoute是RouteInfo的具体实现,这是不能改变的(是不变的)。HttpTracker是可变的RouteInfo实现,由HttpClient在内部使用来跟踪到最大路由目标的剩余跳数。HttpTracker能够在成功执行向路由目标的下一跳以后更新。HttpRouteDirector是一个帮助类,能够用来计算路由中的下一跳。这个类由HttpClient在内部使用。安全

HttpRoutePlanner是一个表明计算到基于执行上下文到给定目标完整路由策略的接口。HttpClient附带两个默认的HttpRoutePlanner实现。ProxySelectorRoutePlanner是基于java.net.ProxySelector的。默认状况下,它会从系统属性中或从运行应用程序的浏览器中选取JVM的代理设置。DefaultHttpRoutePlanner实现既不使用任何Java系统属性,也不使用系统或浏览器的代理设置。它只基于HTTP以下面描述的参数计算路由。服务器

2.3.2 安全HTTP链接

若是信息在两个不能由非认证的第三方进行读取或修改的终端之间传输,HTTP链接能够被认为是安全的。SSL/TLS协议是用来保证HTTP传输安全使用最普遍的技术。而其它加密技术也能够被使用。一般来讲,HTTP传输是在SSL/TLS加密链接之上分层的。网络

2.4 HTTP路由参数

这些参数能够影响路由计算:
  • 'http.route.default-proxy':定义能够被不使用JRE设置的默认路由规划者使用的代理主机。这个参数指望获得一个HttpHost类型的值。若是这个参数没有被设置,那么就会尝试直接链接到目标。
  • 'http.route.local-address':定义一个本地地址由全部默认路由规划者来使用。有多个网络接口的机器中,这个参数能够被用于从链接源中选择网络接口。这个参数指望获得一个java.net.InetAddress类型的值。若是这个参数没有被设置,将会自动使用本地地址。
  • 'http.route.forced-route':定义一个由全部默认路由规划者使用的强制路由。代替了计算路由,给定的强制路由将会被返回,尽管它指向一个彻底不一样的目标主机。这个参数指望获得一个HttpRoute类型的值。若是这个参数没有被设置,那么就使用默认的规则创建链接到目标服务器。

2.5 套接字工厂

LayeredSocketFactory是SocketFactory接口的扩展。分层的套接字工厂可HTTP链接内部使用java.net.Socket对象来处理数据在线路上的传输。它们依赖SocketFactory接口来建立,初始化和链接套接字。这会使得HttpClient的用户能够提供在运行时指定套接字初始化代码的应用程序。PlainSocketFactory是建立和初始化普通的(不加密的)套接字的默认工厂。多线程

建立套接字的过程和链接到主机的过程是不成对的,因此套接字在链接操做封锁时能够被关闭。

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);

2.5.1 安全套接字分层

LayeredSocketFactory是SocketFactory接口的扩展。分层的套接字工厂能够建立在已经存在的普通套接字之上的分层套接字。套接字分层主要经过代理来建立安全的套接字。HttpClient附带实现了SSL/TLS分层的SSLSocketFactory。请注意HttpClient不使用任何自定义加密功能。它彻底依赖于标准的Java密码学(JCE)和安全套接字(JSEE)扩展。

2.5.2 SSL/TLS的定制

HttpClient使用SSLSocketFactory来建立SSL链接。SSLSocketFactory容许高度定制。它可使用javax.net.ssl.SSLContext的实例做为参数,并使用它来建立定制SSL链接。

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);
SSLSocketFactory的定制暗示出必定程度SSL/TLS协议概念的熟悉,这个详细的解释超出了本文档的范围。请参考Java的安全套接字扩展[http://java.sun.com/j2se/1.5.0/docs/guide/
security/jsse/JSSERefGuide.html],这是javax.net.ssl.SSLContext和相关工具的详细描述。

2.5.3 主机名验证

除了信任验证和客户端认证在SSL/TLS协议级上进行,一旦链接创建以后,HttpClient能可选地验证目标主机名匹配存储在服务器的X.509认证中的名字。这个认证能够提供额外的服务器信任材料的真实保证。X509主机名验证接口表明了主机名验证的策略。HttpClient附带了3个X509主机名验证器。很重要的一点是:主机名验证不该该混淆SSL信任验证。
  • StrictHostnameVerifier:严格的主机名验证在Sun Java 1.4,Sun Java 5和Sun Java 6中是相同的。并且也很是接近IE6。这个实现彷佛是兼容RFC 2818处理通配符的。主机名必须匹配第一个CN或任意的subject-alt。在CN和其它任意的subject-alt中可能会出现通配符。
  • BrowserCompatHostnameVerifier:主机名验证器和Curl和Firefox的工做方式是相同的。主机名必须匹配第一个CN或任意的subject-alt。在CN和其它任意的subject-alt中可能会出现通配符。BrowserCompatHostnameVerifier和StrictHostnameVerifier的惟一不一样是使用BrowserCompatHostnameVerifier匹配全部子域的通配符(好比”*.foo.com”),包括”a.b.foo.com”。
  • AllowAllHostnameVerifier:这个主机名验证器基本上是关闭主机名验证的。这个实现是一个空操做,并且不会抛出javax.net.ssl.SSLException异常。

每个默认的HttpClient使用BrowserCompatHostnameVerifier的实现。若是须要的话,它能够指定不一样的主机名验证器实现。

SSLSocketFactory sf = new SSLSocketFactory(SSLContext.getInstance("TLS"));
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

2.6 协议模式

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

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);

2.7 HttpClient代理配置

尽管HttpClient了解复杂的路由模式和代理链,它仅支持简单直接的或开箱的跳式代理链接。

告诉HttpClient经过代理去链接到目标主机的最简单方式是经过设置默认的代理参数:

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

也能够构建HttpClient使用标准的JRE代理选择器来得到代理信息:

DefaultHttpClient httpclient = new DefaultHttpClient();
ProxySelectorRoutePlanner routePlanner = new ProxySelectorRoutePlanner(
httpclient.getConnectionManager().getSchemeRegistry(),
ProxySelector.getDefault());
httpclient.setRoutePlanner(routePlanner);

另一种选择,能够提供一个定制的RoutePlanner实现来得到HTTP路由计算处理上的复杂的控制:

DefaultHttpClient httpclient = new DefaultHttpClient();
httpclient.setRoutePlanner(new HttpRoutePlanner() {
public HttpRoute determineRoute(HttpHost target,
HttpRequest request,
HttpContext context) throws HttpException {
return new HttpRoute(target, null, new HttpHost("someproxy", 8080),
"https".equalsIgnoreCase(target.getSchemeName()));
}
});

2.8 HTTP链接管理器

2.8.1 链接操做器

链接操做是客户端的低层套接字或能够经过外部实体,一般称为链接操做的被操做的状态的链接。OperatedClientConnection接口扩展了HttpClientConnection接口并且定义了额外的控制链接套接字的方法。ClientConnectionOperator接口表明了建立实例和更新那些对象低层套接字的策略。实现类最有可能利用SocketFactory来建立java.net.Socket实例。ClientConnectionOperator接口可让HttpClient的用户提供一个链接操做的定制策略和提供可选实现OperatedClientConnection接口的能力。

2.8.2 管理链接和链接管理器

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

内部的HTTP链接管理器和OperatedClientConnection实例一块儿工做,可是它们为服务消耗器ManagedClientConnection提供实例。ManagedClientConnection扮演链接之上管理状态控制全部I/O操做的OperatedClientConnection实例的包装器。它也抽象套接字操做,提供打开和更新去建立路由套接字便利的方法。ManagedClientConnection实例了解产生它们到链接管理器的连接,并且基于这个事实,当再也不被使用时,它们必须返回到管理器。ManagedClientConnection类也实现了ConnectionReleaseTrigger接口,能够被用来触发释放链接返回给管理器。一旦释放链接操做被触发了,被包装的链接从ManagedClientConnection包装器中脱离,OperatedClientConnection实例被返回给管理器。尽管服务消耗器仍然持有ManagedClientConnection实例的引用,它也再也不去执行任何I/O操做或有意无心地改变的OperatedClientConnection状态。

这里有一个从链接管理器中获取链接的示例:

HttpParams params = new BasicHttpParams();
Scheme http = new Scheme("http", PlainSocketFactory.getSocketFactory(), 80);
SchemeRegistry sr = new SchemeRegistry();
sr.register(http);
ClientConnectionManager connMrg = new SingleClientConnManager(params, sr);
// 请求新链接。这多是一个很长的过程。
ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
// 等待链接10秒
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
// 用链接在作有用的事情。当完成时释放链接。
conn.releaseConnection();
} catch (IOException ex) {
// 在I/O error之上终止链接。
conn.abortConnection();
throw ex;
}

若是须要,链接请求能够经过调用来ClientConnectionRequest#abortRequest()方法过早地中断。这会解锁在ClientConnectionRequest#getConnection()方法中被阻止的线程。

一旦响应内容被彻底消耗后,BasicManagedEntity包装器类能够用来保证自动释放低层的链接。HttpClient内部使用这个机制来实现透明地对全部从HttpClient#execute()方法中得到响应释放链接:

ClientConnectionRequest connRequest = connMrg.requestConnection(
new HttpRoute(new HttpHost("localhost", 80)), null);
ManagedClientConnection conn = connRequest.getConnection(10, TimeUnit.SECONDS);
try {
BasicHttpRequest request = new BasicHttpRequest("GET", "/");
conn.sendRequestHeader(request);
HttpResponse response = conn.receiveResponseHeader();
conn.receiveResponseEntity(response);
HttpEntity entity = response.getEntity();
if (entity != null) {
BasicManagedEntity managedEntity = new BasicManagedEntity(entity, conn, true);
// 替换实体
response.setEntity(managedEntity);
}
// 使用响应对象作有用的事情。当响应内容被消耗后这个链接将会自动释放。
} catch (IOException ex) {
//在I/O error之上终止链接。
conn.abortConnection();
throw ex;
}

2.8.3 简单链接管理器

SingleClientConnManager是一个简单的链接管理器,在同一时间它仅仅维护一个链接。尽管这个类是线程安全的,但它应该被用于一个执行线程。SingleClientConnManager对于同一路由的后续请求会尽可能重用链接。而若是持久链接的路由不匹配链接请求的话,它也会关闭存在的链接以后对给定路由再打开一个新的。若是链接已经被分配,将会抛出java.lang.IllegalStateException异常。

对于每一个默认链接,HttpClient使用SingleClientConnManager。

2.8.4 链接池管理器

ThreadSafeClientConnManager是一个复杂的实现来管理客户端链接池,它也能够从多个执行线程中服务链接请求。对每一个基本的路由,链接都是池管理的。对于路由的请求,管理器在池中有可用的持久性链接,将被从池中租赁链接服务,而不是建立一个新的链接。

ThreadSafeClientConnManager维护每一个基本路由的最大链接限制。每一个默认的实现对每一个给定路由将会建立不超过两个的并发链接,而总共也不会超过20个链接。对于不少真实的应用程序,这个限制也证实很大的制约,特别是他们在服务中使用HTTP做为传输协议。链接限制,也可使用HTTP参数来进行调整。

这个示例展现了链接池参数是如何来调整的:

HttpParams params = new BasicHttpParams();
// 增长最大链接到200
ConnManagerParams.setMaxTotalConnections(params, 200);
// 增长每一个路由的默认最大链接到20
ConnPerRouteBean connPerRoute = new ConnPerRouteBean(20);
// 对localhost:80增长最大链接到50
HttpHost localhost = new HttpHost("locahost", 80);
connPerRoute.setMaxForRoute(new HttpRoute(localhost), 50);
ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
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);

2.8.5 链接管理器关闭

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

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();

2.9 链接管理参数

这些是能够用于定制标准HTTP链接管理器实现的参数:
  • 'http.conn-manager.timeout':定义了当从ClientConnectionManager中检索ManagedClientConnection实例时使用的毫秒级的超时时间。这个参数指望获得一个java.lang.Long类型的值。若是这个参数没有被设置,链接请求就不会超时(无限大的超时时间)。
  • 'http.conn-manager.max-per-route':定义了每一个路由链接的最大数量。这个限制由客户端链接管理器来解释,并且应用于独立的管理器实例。这个参数指望获得一个ConnPerRoute类型的值。
  • 'http.conn-manager.max-total':定义了总共链接的最大数目。这个限制由客户端链接管理器来解释,并且应用于独立的管理器实例。这个参数指望获得一个java.lang.Integer类型的值。

2.10 多线程执行请求

当配备链接池管理器时,好比ThreadSafeClientConnManager,HttpClient能够同时被用来执行多个请求,使用多线程执行。

ThreadSafeClientConnManager将会分配基于它的配置的链接。若是对于给定路由的全部链接都被租出了,那么链接的请求将会阻塞,直到一个链接被释放回链接池。它能够经过设置'http.conn-manager.timeout'为一个正数来保证链接管理器不会在链接请求执行时无限期的被阻塞。若是链接请求不能在给定的时间周期内被响应,将会抛出ConnectionPoolTimeoutException异常。

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();
}
}
}

2.11 链接收回策略

一个经典的阻塞I/O模型的主要缺点是网络套接字仅当I/O操做阻塞时才能够响应I/O事件。当一个链接被释放返回管理器时,它能够被保持活动状态而却不能监控套接字的状态和响应任何I/O事件。若是链接在服务器端关闭,那么客户端链接也不能去侦测链接状态中的变化和关闭本端的套接字去做出适当响应。

HttpClient经过测试链接是不是过期的来尝试去减轻这个问题,这已经再也不有效了,由于它已经在服务器端关闭了,以前使用执行HTTP请求的链接。过期的链接检查也并非100%的稳定,反而对每次请求执行还要增长10到30毫秒的开销。惟一可行的而不涉及到每一个对空闲链接的套接字模型线程解决方案,是使用专用的监控线程来收回由于长时间不活动而被认为是过时的链接。监控线程能够周期地调用ClientConnectionManager#closeExpiredConnections()方法来关闭全部过时的链接,从链接池中收回关闭的链接。它也能够选择性调用ClientConnectionManager#closeIdleConnections()方法来关闭全部已经空闲超过给定时间周期的链接。

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();
}
}
}

2.12 链接保持活动的策略

HTTP规范没有肯定一个持久链接可能或应该保持活动多长时间。一些HTTP服务器使用非标准的头部信息Keep-Alive来告诉客户端它们想在服务器端保持链接活动的周期秒数。若是这个信息可用,HttClient就会利用这个它。若是头部信息Keep-Alive在响应中不存在,HttpClient假设链接无限期的保持活动。然而许多现实中的HTTP服务器配置了在特定不活动周期以后丢掉持久链接来保存系统资源,每每这是不通知客户端的。若是默认的策略证实是过于乐观的,那么就会有人想提供一个定制的保持活动策略。

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;
}
}
}); 
相关文章
相关标签/搜索