首先看一下继承实现的关系图:
下面我们就根据上篇的例子来分析源码的处理流程,先说一下本片博客的主要内容及流程
继续跟踪httpclient.execute()方法,发现其内部会调用CloseableHttpClient.doExecute()方法,实际会调到InternalHttpClient类的抽象方法doExecute(),该抽象方法有三种实现,如下:
三种实现分别为基础配置实现类,全量配置实现类和基本配置实现类,我们从全量配置实现类InternalHttpClient开始分析,那我们就先来看一下InternalHttpClient类的字段,然后再看对父类doExecute()的实现。
下面解释一下上面的三个字段:
1.HttpClientConnectionManager:看名字就知道是连接管理器是一个接口,有两个实现BasicHttpClientConnectionManager和PoolingHttpClientConnectionManager分别是单线程管理器的和连接池管理器,来管理连接的生命周期,比如Connection被创建出来后处于闲置状态,由连接池管理,调用时会校验是否是open状态,不是的话会进行connect。connect的过程就是 基于不同schema(http、https、ftp等,就是https://baidu.com这种)创建不同的socket连接(ssl和plain)并且将http请求(连接)绑定到socket。这个不是本文讨论的重点,后面有机会单独开一篇PoolingHttpClientConnectionManager连接池的介绍。
2.ClientExecChain:类似于执行器,执行完整的调用执行过程,它是一个包装类,类似于java io类,每个包装类完成一个特定的功能,多层嵌套完成一个系统性的功能,比如处理协议范畴的例如cookie、报文解析等,又比如处理自动重试、重定向、身份验证等功能。
3.HttpRoutePlanner:从HttpHost, HttpRequest, HttpContext 三个值来获取HttpRoute的过程,可以理解成从参数和环境中获取连接的目标地址。
4.Lookup<CookieSpecProvider>:是 CookieSpec(cookie的管理规范)的工厂,后者定义了Set-Cookie以及Cookie的转换方式
5.Lookup<AuthSchemeProvider>:是AuthScheme的工厂,跟权限验证有关,不太清楚。。。
6.CookieStore:用来保存cookie的,和增删改的操作。
7.CredentialsProvider:跟第五条有关,不清楚。。。
8.RequestConfig:配置request的。
9.List<Closeable>:用来关闭流、释放资源。
下面看看核心方法doExecute()
protected CloseableHttpResponse doExecute(
final HttpHost target,
final HttpRequest request,
final HttpContext context) throws IOException, ClientProtocolException {
Args.notNull(request, "HTTP request");
HttpExecutionAware execAware = null;
if (request instanceof HttpExecutionAware) {
execAware = (HttpExecutionAware) request;
}
try {
final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request, target);
final HttpClientContext localcontext = HttpClientContext.adapt(
context != null ? context : new BasicHttpContext());
RequestConfig config = null;
if (request instanceof Configurable) {
config = ((Configurable) request).getConfig();
}
if (config == null) {
final HttpParams params = request.getParams();
if (params instanceof HttpParamsNames) {
if (!((HttpParamsNames) params).getNames().isEmpty()) {
config = HttpClientParamConfig.getRequestConfig(params, this.defaultConfig);
}
} else {
config = HttpClientParamConfig.getRequestConfig(params, this.defaultConfig);
}
}
if (config != null) {
localcontext.setRequestConfig(config);
}
setupContext(localcontext);
final HttpRoute route = determineRoute(target, wrapper, localcontext);
return this.execChain.execute(route, wrapper, localcontext, execAware);
} catch (final HttpException httpException) {
throw new ClientProtocolException(httpException);
}
}
其实InternalHttpClient类的主要的字段了解了,上面的代码都不需要仔细看了,主要是通过对请求对象(HttpGet、HttpPost等)进行一番包装后,最后实际由execChain.execute()来真正执行请求,(上面的倒数第三行)继续跟着方法往下招实现,由于我们在上篇的例子中使用的是HttpClients.createDefault()这个默认方法构造了CloseableHttpClient,没有指定ClientExecChain接口的具体实现类,所以系统默认会使用RedirectExec这个实现类,
由于RedirectExec#execute()方法,如下:
public CloseableHttpResponse execute(
final HttpRoute route,
final HttpRequestWrapper request,
final HttpClientContext context,
final HttpExecutionAware execAware) throws IOException, HttpException {
// 判断是否为空
Args.notNull(route, "HTTP route");
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
final List<URI> redirectLocations = context.getRedirectLocations();
if (redirectLocations != null) {
redirectLocations.clear();
}
final RequestConfig config = context.getRequestConfig();
final int maxRedirects = config.getMaxRedirects() > 0 ? config.getMaxRedirects() : 50;
HttpRoute currentRoute = route;
HttpRequestWrapper currentRequest = request;
// 无限循环是为了除了多重的重定向,其实这个类主要的功能是处理重定向
// 然后调用MainClientExec类中的#execute()方法,而这个方法才是真正的发送请求
for (int redirectCount = 0;;) {
// 每次都调用自己进行处理,知道没有重定向请求,然后才返回值(在下面)
final CloseableHttpResponse response = requestExecutor.execute(
currentRoute, currentRequest, context, execAware);
try {
// 满足重定向的要求,就会发生重定向请求,和后面的else相对应
// 如果远端返回结果标识需要重定向(响应头部是301、302、303、307等重定向标识),则HttpClient默认会自动帮我们做重定向
if (config.isRedirectsEnabled() &&
this.redirectStrategy.isRedirected(currentRequest.getOriginal(), response, context)) {
// 重定向的次数默认最大50次
if (redirectCount >= maxRedirects) {
throw new RedirectException("Maximum redirects ("+ maxRedirects + ") exceeded");
}
redirectCount++;
final HttpRequest redirect = this.redirectStrategy.getRedirect(
currentRequest.getOriginal(), response, context);
if (!redirect.headerIterator().hasNext()) {
final HttpRequest original = request.getOriginal();
redirect.setHeaders(original.getAllHeaders());
}
currentRequest = HttpRequestWrapper.wrap(redirect);
if (currentRequest instanceof HttpEntityEnclosingRequest) {
RequestEntityProxy.enhance((HttpEntityEnclosingRequest) currentRequest);
}
final URI uri = currentRequest.getURI();
final HttpHost newTarget = URIUtils.extractHost(uri);
if (newTarget == null) {
throw new ProtocolException("Redirect URI does not specify a valid host name: " +
uri);
}
// Reset virtual host and auth states if redirecting to another host
if (!currentRoute.getTargetHost().equals(newTarget)) {
final AuthState targetAuthState = context.getTargetAuthState();
if (targetAuthState != null) {
this.log.debug("Resetting target auth state");
targetAuthState.reset();
}
final AuthState proxyAuthState = context.getProxyAuthState();
if (proxyAuthState != null && proxyAuthState.isConnectionBased()) {
this.log.debug("Resetting proxy auth state");
proxyAuthState.reset();
}
}
currentRoute = this.routePlanner.determineRoute(newTarget, currentRequest, context);
if (this.log.isDebugEnabled()) {
this.log.debug("Redirecting to '" + uri + "' via " + currentRoute);
}
EntityUtils.consume(response.getEntity());
response.close();
} else {
// 如果没有重定向了,就返回
// 拿到最终真正的业务返回结果后,直接把整个response向外返回,这一步没有帮我们关闭流 ,外层的业务代码在使用完response后,需要我们自己关闭流
return response;
}
} catch (final RuntimeException ex) {
// 如果中途发生了异常,也会帮我们把流关闭
response.close();
throw ex;
} catch (final IOException ex) {
// 如果中途发生了异常,也会帮我们把流关闭
response.close();
throw ex;
} catch (final HttpException ex) {
// Protocol exception related to a direct.
// The underlying connection may still be salvaged.
try {
EntityUtils.consume(response.getEntity());
} catch (final IOException ioex) {
this.log.debug("I/O error while releasing connection", ioex);
} finally {
response.close();
}
throw ex;
}
}
}
注意上面的注释,我们接着上面的RedirectExec#execute()方法中调用的
继续点进去,往下面看具体的功能实现功能:
接着就进入了MainClientExec#execute()返回CloseableHttpResponse:
public CloseableHttpResponse execute(final HttpRoute route,final HttpRequestWrapper request,final HttpClientContext context,final HttpExecutionAware execAware) throws IOException, HttpException {
// 验证为空
Args.notNull(route, "HTTP route");
Args.notNull(request, "HTTP request");
Args.notNull(context, "HTTP context");
// 看到Auth相关的直接跳过,这和认证(HTTPS等)相关,直接往下看;
// 其实很多权限相关的基本上都是Auth开头的,比如shiro
AuthState targetAuthState = context.getTargetAuthState();
if (targetAuthState == null) {
targetAuthState = new AuthState();
context.setAttribute(HttpClientContext.TARGET_AUTH_STATE, targetAuthState);
}
AuthState proxyAuthState = context.getProxyAuthState();
if (proxyAuthState == null) {
proxyAuthState = new AuthState();
context.setAttribute(HttpClientContext.PROXY_AUTH_STATE, proxyAuthState);
}
// 判断请求是不是带有实体,如果是就生成一个代理的Entity,然后set进request
// 里面的实现就不展开了request.setEntity(new RequestEntityProxy(entity));
if (request instanceof HttpEntityEnclosingRequest) {
RequestEntityProxy.enhance((HttpEntityEnclosingRequest) request);
}
// 这是HttpClient中一个标识,它可以用来保证某些连接一段时间内只为某个用户所使用
// 连接池的连接都有state,就是这里的userToken,所有的连接的state默认都是null
// 当我们像向HttpClient连接池中请求userToken为“my”的连接时,它会返回一个连接,并将连接状态标记为my,这样同一个线程下次再来请求连接的时候,就会优先拿到这个连接
Object userToken = context.getUserToken();
// 这里是获取连接,下一篇博客专门来分析他
final ConnectionRequest connRequest = connManager.requestConnection(route, userToken);
// execAware其实是原生的HttpRequest的一个拷贝
// 我估计是防止请求取消中断是,用来返回原生的请求,不然HttpRequest经过很多包装有所变化了,这是我猜的。。。
if (execAware != null) {
if (execAware.isAborted()) {
connRequest.cancel();
throw new RequestAbortedException("Request aborted");
}
execAware.setCancellable(connRequest);
}
// 这是前面一些方法设置的环境和参数
final RequestConfig config = context.getRequestConfig();
final HttpClientConnection managedConn;
try {
final int timeout = config.getConnectionRequestTimeout();
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) {
Throwable cause = ex.getCause();
if (cause == null) {
cause = ex;
}
throw new RequestAbortedException("Request execution failed", cause);
}
// 这个地方将这个连接设置到上下文中
// 使用者可以方便的在调用处获得本次执行所使用的连接(使用连接池的话就有作用了)
// 只需要自己创建一个context,然后让HttpClient使用你的上下文即可
context.setAttribute(HttpCoreContext.HTTP_CONNECTION, managedConn);
if (config.isStaleConnectionCheckEnabled()) {
// 连接是否打开
if (managedConn.isOpen()) {
this.log.debug("Stale connection check");
// 跟上面一样,检查连接是否有效,为什么检查2次,我也不清楚
// 方法比较重要,调用的是BHttpConnectionBase的#isStale(),里面涉及socket知识,有兴趣可以去看一看
if (managedConn.isStale()) {
this.log.debug("Stale connection detected");
managedConn.close();
}
}
}
// 新建一个连接持有类,可能是因为操作连接的N多代码都是重复的,而且操作连接比较频繁
final ConnectionHolder connHolder = new ConnectionHolder(this.log, this.connManager, managedConn);
try {
if (execAware != null) {
execAware.setCancellable(connHolder);
}
HttpResponse response;
for (int execCount = 1;; execCount++) {
// 如果请求过了一次,并且请求是不可重复的,就报错
// 因为我们用的一般是StringEntity,是可重复的读的资源,所以不会报错
// 有时候传文件时,用了InputStreamEntity就是不可重复读取的
if (execCount > 1 && !RequestEntityProxy.isRepeatable(request)) {
throw new NonRepeatableRequestException("Cannot retry request " +
"with a non-repeatable request entity.");
}
if (execAware != null && execAware.isAborted()) {
throw new RequestAbortedException("Request aborted");
}
// 连接如果没有打开,则要建立网络连接(sockt连接)
if (!managedConn.isOpen()) {
this.log.debug("Opening connection " + route);
try {
establishRoute(proxyAuthState, managedConn, route, request, context);
} catch (final TunnelRefusedException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage());
}
response = ex.getResponse();
break;
}
}
final int timeout = config.getSocketTimeout();
if (timeout >= 0) {
managedConn.setSocketTimeout(timeout);
}
if (execAware != null && execAware.isAborted()) {
throw new RequestAbortedException("Request aborted");
}
if (this.log.isDebugEnabled()) {
this.log.debug("Executing request " + request.getRequestLine());
}
// 认证相关的,没什么用,跳过。
if (!request.containsHeader(AUTH.WWW_AUTH_RESP)) {
if (this.log.isDebugEnabled()) {
this.log.debug("Target auth state: " + targetAuthState.getState());
}
this.authenticator.generateAuthResponse(request, targetAuthState, context);
}
if (!request.containsHeader(AUTH.PROXY_AUTH_RESP) && !route.isTunnelled()) {
if (this.log.isDebugEnabled()) {
this.log.debug("Proxy auth state: " + proxyAuthState.getState());
}
this.authenticator.generateAuthResponse(request, proxyAuthState, context);
}
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
// 调用执行,意思是将请求,连接,上下文放入response加工工厂
// 返回一个响应,下一篇博客主要分析这个方法
response = requestExecutor.execute(request, managedConn, context);
// 执行完了,判断这个连接是不是要保持长连接的
// 如果是需要保持长连接的,则将其标记为可重用
if (reuseStrategy.keepAlive(response, context)) {
// Set the idle duration of this connection
final long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
if (this.log.isDebugEnabled()) {
final String s;
if (duration > 0) {
s = "for " + duration + " " + TimeUnit.MILLISECONDS;
} else {
s = "indefinitely";
}
this.log.debug("Connection can be kept alive " + s);
}
connHolder.setValidFor(duration, TimeUnit.MILLISECONDS);
connHolder.markReusable();
} else {
connHolder.markNonReusable();
}
// 下面都没啥用省略了
// 。。。。。。。
// 。。。。。。。。
}
本章就是分析了一下主要的流程和MainClientExec#execute,下章才是重点,怎么获取带连接的。也就是分析上面方法的