Java HttpComponents源码阅读1
Java HttpComponents源码阅读2
MainClientExec#exec
MainClientExec
作为执行链中的最后一个请求执行器,负责执行实际的请求操作。所以这个方法汇总了大部分连接的处理,下面一步步进行拆解;
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
...
Object userToken = context.getUserToken();
// 从连接池中获取http请求句柄
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
connManager
为PoolingHttpClientConnectionManager
对象,每一个HTTP请求都会调用requestConnection
方法来获取一个请求的句柄;
PoolingHttpClientConnectionManager#requestConnection
private final CPool pool;
public ConnectionRequest requestConnection(
final HttpRoute route,
final Object state) {
// pool对象为CPool类型,根据路由从池中租用连接句柄
final Future future = this.pool.lease(route, state, null);
return new ConnectionRequest() { ... };
}
根据路由从连接池中租用连接的句柄,从该句柄可以拿到HttpClientConnection
对象,注意这里新分配的连接可以以关闭状态返回;state
代表着期望的连接状态,如果为null表示新的连接不期望携带任何状态,否则从可用连接池中获取请求时会去寻找连接状态为state
的连接;
CPool#lease
public Future lease(final T route, final Object state,
final FutureCallback callback) {
return new Future() { ... }
}
lease
方法返回一个Future
句柄,该句柄定义了租用连接的具体细节,这里该句柄已经和路由绑定起来;
继续接MainClientExec#exec
...
if (execAware != null) {
if (execAware.isAborted()) {
// 如果请求被中断,取消请求
connRequest.cancel();
throw new RequestAbortedException("Request aborted");
}
// 通过cas设置cancellable方法
execAware.setCancellable(connRequest);
}
...
所有的方法类如HttpGet
都是继承HttpExecutionAware
,这里把请求取消的处理和请求绑定起来;
...
final RequestConfig config = context.getRequestConfig();
final HttpClientConnection managedConn;
try {
// 获取配置中的超时时间
final int timeout = config.getConnectionRequestTimeout();
// 在一定时间内从句柄接口中获取HttpClientConnection连接
// 见下面
managedConn = connRequest.get(timeout > 0 ? timeout : 0, TimeUnit.MILLISECONDS);
} catch(final InterruptedException interrupted) {
// 如果遇到线程中断直接抛异常
Thread.currentThread().interrupt();
throw new RequestAbortedException("Request aborted", interrupted);
} catch(final ExecutionException ex) {
// 试图检索因抛出异常而中止的任务的结果时引发的异常,
// 主要是和Future有关的,在后面可以看到为什么会发生这个异常
Throwable cause = ex.getCause();
if (cause == null) {
cause = ex;
}
throw new RequestAbortedException("Request execution failed", cause);
}
...
根据配置来的超时时间请求HttpClientConnection
连接对象;
ConnectionRequest#get
public HttpClientConnection get(
final long timeout,
final TimeUnit timeUnit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
// 根据route从连接池中获取connection
// 见下面
final HttpClientConnection conn = leaseConnection(future, timeout, timeUnit);
if (conn.isOpen()) {
// 获取到重用连接,socket还未被关闭,重新刷新socket配置
final HttpHost host;
if (route.getProxyHost() != null) {
host = route.getProxyHost();
} else {
host = route.getTargetHost();
}
final SocketConfig socketConfig = resolveSocketConfig(host);
// 重新刷新timeout
conn.setSocketTimeout(socketConfig.getSoTimeout());
}
return conn;
}
在给定时间内在连接池中获取连接,调用此方法时将阻塞,直到连接变为可用、超时过期或连接管理器关闭为止。如果在阻塞时或开始之前调用了cancel(),将抛出一个InterruptedException
。
PoolingHttpClientConnectionManager#leaseConnection
protected HttpClientConnection leaseConnection(
final Future future,
final long timeout,
final TimeUnit timeUnit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
final CPoolEntry entry;
try {
// future里面含有路由信息,从future中获取CPoolEntry对象
// 见下面
entry = future.get(timeout, timeUnit);
if (entry == null || future.isCancelled()) {
throw new ExecutionException(new CancellationException("Operation cancelled"));
}
return CPoolProxy.newProxy(entry); // 返回的是CPoolEntry的代理对象
} catch (final TimeoutException ex) {
throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
}
}
leaseConnection
做的事情就是执行future
句柄定义好的获取连接操作,成功获取到CPoolEntry
后会返回一个代理对象CPoolProxy
,该代理的核心功能就是校验连接的可用性,一旦发现连接不可用了,就会抛出ConnectionShutdownException
异常;
AbstractConnPool$future#get
private final AtomicBoolean cancelled = new AtomicBoolean(false);
// 代表完成获取连接
private final AtomicBoolean done = new AtomicBoolean(false);
// 储存的是CPoolEntry对象
private final AtomicReference entryRef = new AtomicReference(null);
public E get(final long timeout, final TimeUnit timeUnit) throws InterruptedException, ExecutionException, TimeoutException {
for (;;) {
// 这里开始阻塞
synchronized (this) { // this对象是指future对象
try {
// 第一次获取entry为null
final E entry = entryRef.get();
if (entry != null) {
return entry;
}
if (done.get()) {
// 如果已经成功获取到了连接
throw new ExecutionException(operationAborted());
}
// 租用entry,这里获取到的entry一定是非空
final E leasedEntry = getPoolEntryBlocking(route, state, timeout, timeUnit, this);
if (validateAfterInactivity > 0) {
// 如果开启校验连接可用性
if (leasedEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis()) {
// 检查连接是否可用
if (!validate(leasedEntry)) {
// 连接不可用,释放连接,并重新获取连接
leasedEntry.close();
release(leasedEntry, false);
continue;
}
}
}
// 防止重复租用连接
if (done.compareAndSet(false, true)) {
// 更新状态
entryRef.set(leasedEntry);
done.set(true);
// 成功租用连接的钩子
onLease(leasedEntry);
if (callback != null) {
callback.completed(leasedEntry);
}
return leasedEntry;
} else {
// cas失败,该future已被其他线程用来租用连接
release(leasedEntry, true);
throw new ExecutionException(operationAborted());
}
} catch (final IOException ex) {
// getPoolEntryBlocking会抛IOException,通常是socket的IO异常
if (done.compareAndSet(false, true)) {
if (callback != null) {
callback.failed(ex);
}
}
throw new ExecutionException(ex);
}
}
}
}
自旋的获取CPoolEntry
对象,无论是从空闲的连接池或是新创建一个新的连接,总之从这个方法获取到的CPoolEntry
一定是可用的连接对象;
AbstractConnPool#getPoolEntryBlocking
private E getPoolEntryBlocking(
final T route, final Object state,
final long timeout, final TimeUnit timeUnit,
final Future future) throws IOException, InterruptedException, ExecutionException, TimeoutException {
Date deadline = null;
if (timeout > 0) {
deadline = new Date (System.currentTimeMillis() + timeUnit.toMillis(timeout));
}
this.lock.lock();
try {
// 根据路由返回特定的连接池
final RouteSpecificPool pool = getPool(route);
E entry;
for (;;) {
if (future.isCancelled()) {
throw new ExecutionException(operationAborted());
}
for (;;) {
// 根据state去available连接池中获取连接
entry = pool.getFree(state);
if (entry == null) {
// 连接池中没有空闲的连接
break;
}
// 获取到连接
if (entry.isExpired(System.currentTimeMillis())) {
// 连接已经过期,将连接关闭
entry.close();
}
if (entry.isClosed()) {
// 如果连接已被关闭,将其从available移除
this.available.remove(entry);
pool.free(entry, false);
} else {
break;
}
}
if (entry != null) {
// 如果从连接池中获取到连接,将连接从available中拿出来放置到租用池中
this.available.remove(entry);
this.leased.add(entry);
onReuse(entry); // 重用连接的钩子
return entry;
}
// 连接池中没有可用连接,将创建新的连接
final int maxPerRoute = getMax(route);
// 在分配新连接之前缩小连接池大小
final int excess = Math.max(0, pool.getAllocatedCount() + 1 - maxPerRoute);
if (excess > 0) {
for (int i = 0; i < excess; i++) {
final E lastUsed = pool.getLastUsed();
if (lastUsed == null) {
break;
}
lastUsed.close();
this.available.remove(lastUsed);
pool.remove(lastUsed);
}
}
if (pool.getAllocatedCount() < maxPerRoute) {
// 每个路由的最大容量是有限的
final int totalUsed = this.leased.size();
final int freeCapacity = Math.max(this.maxTotal - totalUsed, 0);
if (freeCapacity > 0) {
final int totalAvailable = this.available.size();
if (totalAvailable > freeCapacity - 1) {
// 空闲连接池容量满了,将最近一个空闲连接移出空闲池
if (!this.available.isEmpty()) {
final E lastUsed = this.available.removeLast();
lastUsed.close();
final RouteSpecificPool otherpool = getPool(lastUsed.getRoute());
otherpool.remove(lastUsed);
}
}
// 创建出来的对象是LoggingManagedHttpClientConnection
final C conn = this.connFactory.create(route);
// 同一个连接会在RouteSpecificPool中和AbstractConnPool保存两份
entry = pool.add(conn);
this.leased.add(entry);
return entry;
}
}
// 创建连接失败,因为该路由的连接已经达到最大上限
boolean success = false;
try {
// 将该创建连接的任务缓存起来
pool.queue(future);
this.pending.add(future);
// 等待直到有连接被释放
if (deadline != null) {
success = this.condition.awaitUntil(deadline);
} else {
this.condition.await();
success = true;
}
if (future.isCancelled()) {
throw new ExecutionException(operationAborted());
}
} finally {
// 一般情况下等待线程被连接池唤醒,现在应该有一个空闲连接可用
// 但是也有可能等待超时了,无论如何只要继续循环,这两种情况都会被检查。
pool.unqueue(future);
this.pending.remove(future);
}
// check for spurious wakeup vs. timeout
if (!success && (deadline != null && deadline.getTime() <= System.currentTimeMillis())) {
break;
}
}
throw new TimeoutException("Timeout waiting for connection");
} finally {
this.lock.unlock();
}
}
// 在连接池内部每一个route对应一个连接池
private RouteSpecificPool getPool(final T route) {
RouteSpecificPool pool = this.routeToPool.get(route);
if (pool == null) {
pool = new RouteSpecificPool(route) {
@Override
protected E createEntry(final C conn) {
return AbstractConnPool.this.createEntry(route, conn);
}
};
this.routeToPool.put(route, pool);
}
return pool;
}
该方法的主要处理逻辑都是发生在下面两个结构中
CPool
作为连接池的核心类,承担了连接池的管理工作,内部每个路由
都对应一个RouteSpecificPool
,所以同一份连接会在两个地方都保存起来;
这个方法最主要做的事情就是:
- 根据路由在
RouteSpecificPool
池中查找一个空闲连接,如果有则判断连接是否过期超时,判断连接可用后直接返回; - 如果在空闲连接池中找不到可用连接,则判断当前条件下是否允许创建连接,如果成功创建连接则直接返回;
- 如果连接池已经满了,将请求加入等待队列,然后将自己阻塞等待其他连接释放;
继续回到MainClientExec#exec
...
// 创建连接成功后,将连接保存到上下文中
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
// 如果开启老化连接检查
if (config.isStaleConnectionCheckEnabled()) {
if (managedConn.isOpen()) { // 验证连接
if (managedConn.isStale()) {
managedConn.close(); // 发现陈旧的连接,将连接断开
}
}
}
// 创建holder,相当于一个请求包装类
final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
try {
if (execAware != null) {
// 如果前面已经设置了cancellable,这行代码就没有效果了
execAware.setCancellable(connHolder);
}
HttpResponse response;
for (int execCount = 1;; execCount++) {
// 开始执行具体请求
if (!managedConn.isOpen()) {
// 如果是新的连接,则需要建立请求的路由
try {
// 建立请求路由
establishRoute(proxyAuthState, managedConn, route, request, context);
} catch (final TunnelRefusedException ex) {
response = ex.getResponse();
break;
}
}
final int timeout = config.getSocketTimeout();
if (timeout >= 0) {
managedConn.setSocketTimeout(timeout);
}
...
成功拿到可用连接后,就是开始建立请求路由,会根据当前请求目标选择不同的路径,但是最终都会调用下面这个方法;
PoolingHttpClientConnectionManager#connect
public void connect(
final HttpClientConnection managedConn,
final HttpRoute route,
final int connectTimeout,
final HttpContext context) throws IOException {
final ManagedHttpClientConnection conn;
synchronized (managedConn) {
final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
conn = entry.getConnection();
}
final HttpHost host;
if (route.getProxyHost() != null) { // 有代理,建立代理路由
host = route.getProxyHost();
} else {
host = route.getTargetHost(); // 没有代理,直接路由
}
// 实际建立socket并连接的操作
this.connectionOperator.connect(
conn, host, route.getLocalSocketAddress(), connectTimeout, resolveSocketConfig(host), context);
}
如果是直接路由,则将底层连接套接字连接到目标地址,如果是通过代理(或多个代理)的路由,则连接到第一个代理跳转。
结合第一张图片看,将LoggingManagedHttpClientConnection
对象与实际负责通信的socket
绑定;
继续回到MainClientExec#exec
...
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
response = requestExecutor.execute(request, managedConn, context);
...
实际发送请求消息并拿到响应的方法;
HttpRequestExecutor#execute
public HttpResponse execute(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
try {
// 发送请求,有些情况下response会随着请求返回
HttpResponse response = doSendRequest(request, conn, context);
if (response == null) {
// 接受请求
response = doReceiveResponse(request, conn, context);
}
return response;
} catch (final IOException ex) {
closeConnection(conn);
throw ex;
} catch (final HttpException ex) {
closeConnection(conn);
throw ex;
} catch (final RuntimeException ex) {
closeConnection(conn);
throw ex;
}
}
该方法主要就是处理发送请求和接受请求;
HttpRequestExecutor#doSendRequest
protected HttpResponse doSendRequest(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
HttpResponse response = null;
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, conn);
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.FALSE);
// 发送header
conn.sendRequestHeader(request);
if (request instanceof HttpEntityEnclosingRequest) {
// 检查expect-continue握手。我们必须刷新头并等待100-continue响应来处理它。
// 如果我们得到不同的响应,我们一定不能发送实体。
boolean sendentity = true;
final ProtocolVersion ver =
request.getRequestLine().getProtocolVersion();
if (((HttpEntityEnclosingRequest) request).expectContinue() &&
!ver.lessEquals(HttpVersion.HTTP_1_0)) {
// 需要处理100-continue逻辑
conn.flush();
// 在指定时间内等到对端回复,不需要永远等待100-continue响应,如果发生超时则发送实体请求
if (conn.isResponseAvailable(this.waitForContinue)) {
// 意味着在指定时间内获取到了100-continue应答
response = conn.receiveResponseHeader();
if (canResponseHaveBody(request, response)) {
// 意味着response存在响应包,解析response
conn.receiveResponseEntity(response);
}
final int status = response.getStatusLine().getStatusCode();
if (status < 200) {
if (status != HttpStatus.SC_CONTINUE) {
throw new ProtocolException(
"Unexpected response: " + response.getStatusLine());
}
// 非正常的状态码,重置response
response = null;
} else {
sendentity = false;
}
}
}
if (sendentity) {
// 发送请求实体
conn.sendRequestEntity((HttpEntityEnclosingRequest) request);
}
}
conn.flush();
context.setAttribute(HttpCoreContext.HTTP_REQ_SENT, Boolean.TRUE);
return response;
}
通过给定的连接发送请求,此方法还处理expect-continue
握手;
HTTP/1.1 协议里的Expect: 100-continue 意思是,在客户端发送 Request Message 之前,HTTP/1.1 协议允许客户端先判定服务器是否愿意接受客户端发来的消息主体(基于 Request Headers)。
目的:它可以让客户端在发送请求数据之前去判断服务器是否愿意接收该数据,如果服务器愿意接收,客户端才会真正发送数据,如果客户端直接发送请求数据,但是服务器又将该请求拒绝的话,这种行为将带来很大的资源开销。
发送了100-continue的客户端不应该永远等待服务器端做出回应,超时一段时间之后,客户端应该直接将实体发送出去。
比如在POST方法时HttpPost
就会去处理expect-continue
的逻辑,如果在这里有判断到返回了响应,则处理响应实体;
HttpRequestExecutor#doReceiveResponse
protected HttpResponse doReceiveResponse(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws HttpException, IOException {
HttpResponse response = null;
int statusCode = 0;
while (response == null || statusCode < HttpStatus.SC_OK) {
response = conn.receiveResponseHeader();
statusCode = response.getStatusLine().getStatusCode();
if (statusCode < HttpStatus.SC_CONTINUE) {
throw new ProtocolException("Invalid response: " + response.getStatusLine());
}
// 解析response entity
if (canResponseHaveBody(request, response)) {
conn.receiveResponseEntity(response);
}
}
return response;
}
doSendRequest
中如果是Get请求,会将HTTP报文的每一行逐一写入socket缓冲区中,当缓冲区满了或是数据全部写入后会发送出去;
如果是Post请求,除了发送请求头、请求行、header等信息,还会发送请求体;
doReceiveResponse
方法用来接受响应,但是不负责解析具体的entity,只是将对应的socket句柄封装在响应实体HttpResponse
里面等待读取;
继续回到MainClientExec#exec,到这里已经拿到请求响应了;
...
// 判断连接是否是可重用状态
if (reuseStrategy.keepAlive(response, context)) {
final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
// 返回的响应头中含有Keep-Alive
connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
connHolder.markReusable();
} else {
// 连接不可重用
connHolder.markNonReusable();
}
...
// 校验请求
...
final HttpEntity entity = response.getEntity();
if (entity == null || !entity.isStreaming()) {
// 连接不需要并且(假定)处于可重用状态
connHolder.releaseConnection();
return new HttpResponseProxy(response, null);
}
// HttpResponse的代理类,可以用来释放与原始响应关联的客户端连接
return new HttpResponseProxy(response, connHolder);
} catch (final Exception ex) {
...
}
}
至此整个请求流程就大概完成了,下面看看连接什么时候放回连接池;
EntityUtils.toString(response.getEntity(), "UTF-8");
最后会走到下面这个方法
private static String toString(
final HttpEntity entity,
final ContentType contentType) throws IOException {
final InputStream inStream = entity.getContent();
if (inStream == null) {
return null;
}
try {
int capacity = (int)entity.getContentLength();
Charset charset = null;
...
final Reader reader = new InputStreamReader(inStream, charset);
final CharArrayBuffer buffer = new CharArrayBuffer(capacity);
final char[] tmp = new char[1024];
int l;
while((l = reader.read(tmp)) != -1) {
buffer.append(tmp, 0, l);
}
return buffer.toString();
} finally {
inStream.close();
}
}
public InputStream getContent() throws IOException {
return new EofSensorInputStream(this.wrappedEntity.getContent(), this);
}
关键在于EofSensorInputStream
这个对象,该对象的核心功能就是检测EOF标识符并触发close()
操作,主要用于在使用响应体或不再需要连接时自动释放底层托管的连接。
public int read() throws IOException {
int readLen = -1;
if (isReadAllowed()) {
try {
readLen = wrappedStream.read();
checkEOF(readLen); // 检查eof
} catch (final IOException ex) {
checkAbort();
throw ex;
}
}
return readLen;
}
// checkEOF会走到这里
public boolean eofDetected(final InputStream wrapped) throws IOException {
try {
if (wrapped != null) {
wrapped.close();
}
releaseConnection(); // 释放连接
} catch (final IOException ex) {
abortConnection(); // 中断连接
throw ex;
} catch (final RuntimeException ex) {
abortConnection();
throw ex;
} finally {
cleanup(); // 关闭连接
}
return false;
}
当从流中读取到EOF
标志符后会主动释放连接;
ConnectionHolder#releaseConnection
private void releaseConnection(final boolean reusable) {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
if (reusable) {
// 把连接放回连接池
this.manager.releaseConnection(this.managedConn,
this.state, this.validDuration, this.timeUnit);
} else {
try {
this.managedConn.close();
} catch (final IOException ex) {
} finally {
this.manager.releaseConnection(
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
}
}
}
}
}
这里的managedConn
为CPoolProxy
对象,manager
为PoolingHttpClientConnectionManager
对象,如果请求完毕后只是释放,那么会把连接放回到连接池;
AbstractConnPool#release
public void release(final E entry, final boolean reusable) {
this.lock.lock();
try {
if (this.leased.remove(entry)) {
// 根据route取连接池
final RouteSpecificPool pool = getPool(entry.getRoute());
// 将RouteSpecificPool的连接从租用池中移除,放入空闲池
pool.free(entry, reusable);
if (reusable && !this.isShutDown) {
this.available.addFirst(entry);
} else {
entry.close();
}
onRelease(entry); // hook
Future future = pool.nextPending();
// 如果有的话从等待队列中拿到等待请求的future
if (future != null) {
this.pending.remove(future);
} else {
future = this.pending.poll();
}
if (future != null) {
// 唤醒其他被阻塞的请求线程
this.condition.signalAll();
}
}
} finally {
this.lock.unlock();
}
}
将释放的连接从租用池中移回到空闲连接池中,然后从等待队列取出第一个请求,如果有的话会唤醒被阻塞线程,注意这里取的操作,是优先取同一个路由下的请求,如果同一个路由下没有才会取其他路由的请求;
shutdown和close的区别
- close
此方法会优雅地关闭连接,在关闭底层socket之前会刷新内部缓冲区,close不允许从其他线程调用来强制关闭连接。
public void close() throws IOException {
final Socket socket = this.socketHolder.getAndSet(null);
if (socket != null) {
try {
// 先会刷新缓存区
this.inBuffer.clear();
this.outbuffer.flush();
try {
try {
socket.shutdownOutput();
} catch (final IOException ignore) {
}
try {
socket.shutdownInput();
} catch (final IOException ignore) {
}
} catch (final UnsupportedOperationException ignore) {
// if one isn't supported, the other one isn't either
}
} finally {
socket.close();
}
}
}
- shutdown
会立即强制关闭这个连接,该方法可以允许从不同的线程来调用它终止连接。在关闭底层scoket之前,不会尝试刷新任何的内部缓冲区;
public void shutdown() throws IOException {
final Socket socket = this.socketHolder.getAndSet(null);
if (socket != null) {
try {
// 设置关闭socket的操作
socket.setSoLinger(true, 0);
} catch (final IOException ex) {
} finally {
socket.close();
}
}
}
TCP协议中RST标志位用来表示重置连接、复位连接,关闭异常的连接,
发送RST包关闭连接时,不必等缓冲区的包都发出去(正常要说关闭要经历4次挥手),直接就丢弃缓存区的数据并发送RST包;而接收端收到RST包后,也不必发送ACK包来确认。
setSoLinger
的第一个参数为false时,TCP关闭连接时会保存默认的操作,缓冲区的数据会正常刷新;
当第一个参数为true时,会依赖第二个参数的值,当第二个参数为0时,调用close的时候,TCP连接会立即断开;缓冲区中未被发送的数据将被丢弃,并向对方发送一个RST包,TCP连接将不会进入TIME_WAIT状态(上面提到的对端不会复回ACK);
当第二个参数值不为0时,会在设置的时间段内继续尝试发送缓冲区的数据,如果超时则丢弃缓冲区的数据,并且调用close会阻塞直到设置的时间结束后才返回;