httpclient功能十分丰富,虽然我们一般使用的是它最基本的功能。本文就说两方面,一是http调用流程,就是基本的功能,二是连接池的管理。
一 http调用流程
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); // 将最大连接数 cm.setMaxTotal(maxTotal); // 每个路由基础的连接 cm.setDefaultMaxPerRoute(defaultMaxPerRoute); // 默认设置 RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(defaultSocketTimeout).setConnectTimeout(defaultConnectTimeout) .setConnectionRequestTimeout(defaultConnectionRequestTimeout).build(); //创建httpclient CloseableHttpClient defaultClient = (HttpClients.custom().setDefaultRequestConfig(requestConfig).setConnectionManager(cm) .setSSLSocketFactory(createSSL()).build());
1,HttpClientBuilder创建httpclient
在HttpClientBuilder中,有很多准备操作,比如准备连接池、代理策略、重试策略、认证策略、长连接策略,这些我们暂时不细看。这里生成的httpclient是InternalHttpClient
2, httpClient.execute
此处调用逻辑是InternalHttpClient》MainClientExec》HttpRequestExecutor。在MainClientExec中向连接池租用连接。得到socket连接后执行请求。
protected HttpResponse doSendRequest(HttpRequest request, HttpClientConnection conn, HttpContext context) throws IOException, HttpException { Args.notNull(request, "HTTP request"); Args.notNull(conn, "Client connection"); Args.notNull(context, "HTTP context"); HttpResponse response = null; context.setAttribute("http.connection", conn); context.setAttribute("http.request_sent", Boolean.FALSE); //sengheader conn.sendRequestHeader(request); if(request instanceof HttpEntityEnclosingRequest) { boolean sendentity = true; ProtocolVersion ver = request.getRequestLine().getProtocolVersion(); if(((HttpEntityEnclosingRequest)request).expectContinue() && !ver.lessEquals(HttpVersion.HTTP_1_0)) { conn.flush(); if(conn.isResponseAvailable(this.waitForContinue)) { response = conn.receiveResponseHeader(); if(this.canResponseHaveBody(request, response)) { conn.receiveResponseEntity(response); } int status = response.getStatusLine().getStatusCode(); if(status < 200) { if(status != 100) { throw new ProtocolException("Unexpected response: " + response.getStatusLine()); } response = null; } else { sendentity = false; } } } if(sendentity) { conn.sendRequestEntity((HttpEntityEnclosingRequest)request); } } conn.flush(); context.setAttribute("http.request_sent", Boolean.TRUE); return response; }
二 连接池的管理
我们在发起http请求时,是如何向连接池租用连接的呢?
1,在PoolingHttpClientConnectionManager中有一个CPool。缓存的连接是在他的父类AbstractConnPool中管理的。
2,当向连接池申请时,连接池会根据域名route来判断是否已存在soctket连接,如何来判断呢,就是AbstractConnPool中的Map routeToPool. 根据这个map,获取到一个RouteSpecificPool。这个池子中缓存的是同一个路由的socket连接。缓存多少个就是我们一开始设置的setDefaultMaxPerRoute。
private final Lock lock; private final Condition condition; private final ConnFactory<T, C> connFactory; private final Map<T, RouteSpecificPool<T, C, E>> routeToPool; private final Set<E> leased; private final LinkedList<E> available; private final LinkedList<Future<E>> pending; private final Map<T, Integer> maxPerRoute; private volatile boolean isShutDown; private volatile int defaultMaxPerRoute; private volatile int maxTotal; private volatile int validateAfterInactivity;
3,如果RouteSpecificPool存在,则向其内部租赁。有三个集合,租用的逻辑就围绕其展开。等待的,是指,当连接不够用了,而缓存的个数已达上限,则等待,到了一定的时间还没有拿到,就放弃了。
private final T route; //使用中的 private final Set<E> leased; //空闲的 private final LinkedList<E> available; //等待的 private final LinkedList<Future<E>> pending;
4,拿到连接CPoolEntry后,会校验起是否有效,如果校验不通过,则销毁,重新获取。
if(AbstractConnPool.this.validateAfterInactivity <= 0 || ex.getUpdated() + (long)AbstractConnPool.this.validateAfterInactivity > System.currentTimeMillis() || AbstractConnPool.this.validate(ex)) { this.entry = ex; this.done = true; AbstractConnPool.this.onLease(this.entry); if(callback != null) { callback.completed(this.entry); } PoolEntry var10000 = this.entry; return var10000; }