HttpClient Component是Apache的一个非常实用的项目,让http不是很熟练/精通的开发者也很好上手开发。在请求频繁和高并发的情况下,建议使用连接管理。本文记录HttpClient连接管理的简单配置。
1. 连接管理器
连接管理器可以看做是生成和管理http连接的工厂(factory),像一个总代理,为线程获取连接和管理连接的生命周期,并且保证线程安全。官方给的sample代码:
HttpClientContext context = HttpClientContext.create();
HttpClientConnectionManager connMrg = new BasicHttpClientConnectionManager();
HttpRoute route = new HttpRoute(new HttpHost("localhost", 80));
// Request new connection. This can be a long process
ConnectionRequest connRequest = connMrg.requestConnection(route, null);
// Wait for connection up to 10 sec
HttpClientConnection conn = connRequest.get(10, TimeUnit.SECONDS);
try {
// If not open
if (!conn.isOpen()) {
// establish connection based on its route info
connMrg.connect(conn, route, 1000, context);
// and mark it as route complete
connMrg.routeComplete(conn, route, context);
}
// Do useful things with the connection.
} finally {
connMrg.releaseConnection(conn, null, 1, TimeUnit.MINUTES);
}
这里用的是BasicHttpClientConnectionManager,应该不是很常用,因为一次只能持有一个连接。当线程请求新的连接时,manager会检查请求的路由和当前持有的连接路由是否一致,如果正好一致,那可以做为重用的连接,如果不一致,将当前的连接关闭并且按照请求的路由建立一个新的连接提供给线程。
2.连接池
为了节省频繁建链和拆链的开销,可以使用连接池,用PoolingHttpClientConnectionManager代替BasicHttpClientConnectionManager。可以保持有多个连接,并且按照路由进行区分。官方sample代码:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
// Increase max total connection to 200
cm.setMaxTotal(200);
// Increase default max connection per route to 20
cm.setDefaultMaxPerRoute(20);
// Increase max connections for localhost:80 to 50
HttpHost localhost = new HttpHost("locahost", 80);
cm.setMaxPerRoute(new HttpRoute(localhost), 50);
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(cm)
.build();
这份sample代码用的是最简单的PoolingHttpClientConnectionManager,在构造时没有指定任何参数和配置。maxTotal是总共的最大连接数,defaultMaxPerRoute是单个路由的最大连接数(也可以单独设置其中某个路由的最大连接数maxPerRoute)。
使用完必须关闭连接池,释放资源
CloseableHttpClient httpClient = <...>
httpClient.close();
3. 连接的释放和回收策略
同步的HTTP请求有一个弊端,就是客户端并不知道连接池中的哪些连接已经“过时”(比如在服务器端被关闭),需要采用策略来对此种情况进行优化。
PoolingHttpClientConnectionManager提供了一个方法closeIdleConnections()来关闭释放长时间没有活动的连接,不过并不是100%准确。
4. Keep-Alive 策略
有些服务器会在返回的报文头添加Keep Alive值来告诉客户端当前连接打算保持多长时间,客户端可用利用这一值。但是有些服务器没有在返回的报文头中加入这一值,这种情况下客户端会当做此连接是无限期的,影响连接的释放和回收。所以可以在客户端自己定义Keey-Alive策略。我写了一个比官网还简单的。
CloseableHttpClient client = HttpClients.custom().setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy() {
@Override
public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
long keepAlive = super.getKeepAliveDuration(response, context);
if (keepAlive == -1) {
// 如果keep-alive值没有由服务器明确设置,那么保持连接持续n秒。
keepAlive = httpKeepAliveTime;
}
return keepAlive;
}
})
.build();
5. SocketFactory
HttpClient还提供了可以定制化的socketFactory结合连接管理一起使用,就可以支持不同的协议。
ConnetionSocketFactory接口有一个默认实现PlainConnectionSocketFactory,不附加分层协议,创建普通(未加密)的Socket。LayeredConnectionSocketFactory是ConnetionSocketFactory的子接口,可以在现有的普通Socket上创建分层Socket,比如HttpClient提供的实现了SSL/TSL分层的SSLConnectionSocketFactory工厂类(实现了LayeredConnectionSocketFactory接口),可以关联https协议。sample代码展示了socketFactory和连接管理结合使用。
ConnectionSocketFactory plainsf = new PlainConnectionSocketFactory();
LayeredConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(SSLContext.getDefault());
Registry<ConnectionSocketFactory> r = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", plainsf)
.register("https", sslsf)
.build();
HttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(r);
HttpClients.custom()
.setConnectionManager(cm)
.build();
6. 主机验证
除了在SSL/TLS协议级别上执行的信任验证和客户端身份验证之外,HttpClient还可以在建立连接后选择验证目标主机名是否与服务器的X.509证书中存储的名称匹配。这种验证可以为服务器信任材料的真实性提供额外的保证。javax.net.ssl.HostnameVerifier接口表示用于主机名验证的策略。HttpClient附带两个javax.net.ssl.HostnameVerifier实现。
DefaultHostnameVerifier: HttpClient使用的默认实现(符合RFC 2818)。主机名必须与证书指定的主机名称匹配。
NoopHostnameVerifier:这个主机名验证器本质上关闭了主机名验证。它接受任何有效并匹配目标主机的SSL会话。
HttpClient默认使用DefaultHostnameVerifier实现。如果需要,可以指定不同验证策略。
sample代码参考:
SSLContext sslContext = SSLContexts.createSystemDefault();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslContext,
NoopHostnameVerifier.INSTANCE);