前言
作为一个java后端开发,调用http协议的请求一直使用的httpclient,但是对他内部原理却一概不知,由于它使用了连接池来解决连接资源消耗所带来的问题,同时我们在使用时他也隐匿了连接池操作的很多细节,所以若是使用不当势必会带来很多隐藏的坑(诸如CLOSE_WAIT过多造成服务假死之类)所以最近有时间看了他内部源码,特此记录下来。
httpclient当前版本总览:
httpclient是apache下的一个子项目,commons-httpclient是最老的版本,现在官方已经不再开发和维护同时也不被建议使用,已经是一个遗留项目,取而代之的是httpclientcommons项目。目前httpclientcommons项目最新的release版本为4.5.9, 5.0的beta版本也已经发布。而httpclient3.x 4.x和4,.3之后的版本之前的API也是有所不同。
源码解读
先看一点httpclientcommons官方使用示例代码
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://targethost/homepage");
CloseableHttpResponse response1 = httpclient.execute(httpGet);
// The underlying HTTP connection is still held by the response object
// to allow the response content to be streamed directly from the network socket.
// In order to ensure correct deallocation of system resources
// the user MUST call CloseableHttpResponse#close() from a finally clause.
// Please note that if response content is not fully consumed the underlying
// connection cannot be safely re-used and will be shut down and discarded
// by the connection manager.
try {
System.out.println(response1.getStatusLine());
HttpEntity entity1 = response1.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity1);
} finally {
response1.close();
}
HttpPost httpPost = new HttpPost("http://targethost/login");
List <NameValuePair> nvps = new ArrayList <NameValuePair>();
nvps.add(new BasicNameValuePair("username", "vip"));
nvps.add(new BasicNameValuePair("password", "secret"));
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
CloseableHttpResponse response2 = httpclient.execute(httpPost);
try {
System.out.println(response2.getStatusLine());
HttpEntity entity2 = response2.getEntity();
// do something useful with the response body
// and ensure it is fully consumed
EntityUtils.consume(entity2);
} finally {
response2.close();
}
使用示例中关键代码中是使用HttpClients.createDefault()创建了一个CloseableHttpClient对象(线程安全对象,可共享),接下来就去看看HttpClients.createDefault()方法。
/**
* Creates {@link CloseableHttpClient} instance with default
* configuration.
*/
public static CloseableHttpClient createDefault() {
return HttpClientBuilder.create().build();
}
通过HttpClientBuilder#build返回一个CloseableHttpClient对象,接下来看下HttpClientBuilder#build,此方法篇幅较长,只看关键代码即可,并不影响对我们引用层面的理解。
public CloseableHttpClient build() {
// Create main request executor
// We copy the instance fields to avoid changing them, and rename to avoid accidental use of the wrong version
PublicSuffixMatcher publicSuffixMatcherCopy = this.publicSuffixMatcher;
if (publicSuffixMatcherCopy == null) {
publicSuffixMatcherCopy = PublicSuffixMatcherLoader.getDefault();
}
//HttpRequestExecutor通过preProcess,execute,postProcess方法拦截请求,发送请求,接收
//请求,这个后面会再次看到这个对象的使用
HttpRequestExecutor requestExecCopy = this.requestExec;
if (requestExecCopy == null) {
requestExecCopy = new HttpRequestExecutor();
}
//初始化连接池对象,若是应用层不初始化一个则会默认初始化一个默认defaultMaxPerRoute=2,
//maxTotal=20 ValidateAfterInactivity=2000(从池中获取连接时校验连接是否可用的间隔时
//间), validityDeadline = Long.MAX_VALUE,this.expiry = this.validityDeadline;
//连接最终失效时间和连接过期时间都是Long.MAX_VALUE,具体后面分析httplcientConnection
//如何从连接池获取时会查看HttpClientConnectionManager的构造方法
HttpClientConnectionManager connManagerCopy = this.connManager;
if (connManagerCopy == null) {
LayeredConnectionSocketFactory sslSocketFactoryCopy = this.sslSocketFactory;
if (sslSocketFactoryCopy == null) {
final String[] supportedProtocols = systemProperties ? split(
System.getProperty("https.protocols")) : null;
final String[] supportedCipherSuites = systemProperties ? split(
System.getProperty("https.cipherSuites")) : null;
HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier;
if (hostnameVerifierCopy == null) {
hostnameVerifierCopy = new DefaultHostnameVerifier(publicSuffixMatcherCopy);
}
if (sslContext != null) {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
sslContext, supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
} else {
if (systemProperties) {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
(SSLSocketFactory) SSLSocketFactory.getDefault(),
supportedProtocols, supportedCipherSuites, hostnameVerifierCopy);
} else {
sslSocketFactoryCopy = new SSLConnectionSocketFactory(
SSLContexts.createDefault(),
hostnameVerifierCopy);
}
}
}
@SuppressWarnings("resource")
final PoolingHttpClientConnection