What happened in httpClient.execute(httpGet)

无聊的时候是否想过response = httpClient.execute(httpGet)对你干了啥?

题图:from Zoommy

1. 引言

HttpClient作为Http工具在Java开发中十分流行。

只要调用response = httpClient.execute(httpGet),开发者便可以拿到Http响应。看起来十分简单。但是,你是否好奇过这段代码究竟做了什么?

本文就通过分析HttpClient源码,来理顺一遍httpClient.execute(httpGet)代码段的主要执行逻辑。

2. HttpClient

想知道httpClient.execute(httpGet)做了什么,首先要知道其调用发起者httpclient是什么。

定义:HttpClient接口定义了最基本的用于Httpclient请求的约定。具体的约束、身份验证、重定向等实现细节由不同的实现类来完成。

HttpClient中所有的请求都是由该接口的实现类来执行完成的,常见代码:httpClient.execute(httpGet)

2.1 以InternalHttpClient为例

InternalHttpClient类中实现了doExecute方法,这个方法就是执行Http请求的核心。

分析源码就不难发现,该方法就干了两件事,包装、调用execChain.execute。说白了,真正发起请求的不是HttpClient,而是这个execChain——ClientExecChain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 属性之一
private final ClientExecChain execChain;
// 覆写方法
@Override
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 {
// 包装request、config
final HttpRequestWrapper wrapper = HttpRequestWrapper.wrap(request);
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);
}
} else {
config = HttpClientParamConfig.getRequestConfig(params);
}
}
if (config != null) {
localcontext.setRequestConfig(config);
}
// 设置context、route
setupContext(localcontext);
final HttpRoute route = determineRoute(target, wrapper, localcontext);
// 关键, 执行execute
return this.execChain.execute(route, wrapper, localcontext, execAware);
} catch (final HttpException httpException) {
throw new ClientProtocolException(httpException);
}
}

3. ClientExecChain

execChain.execute看起来只是一个方法调用,实际上他是一串调用链。

定义:ClientExecChain接口定义了一个Http请求执行链上的节点,每个节点既可以是另一个节点的装饰器也可以是最终节点。该接口只包含一个待实现方法(execute)。此方法的实现,既可以是将请求传递给下一个节点执行,也可以是真实的向服务端传输请求。

如图。

  1. 如果ClientExecChain对象中包含另一个ClientExecChain对象
    那么他就是执行链上的一个装饰节点,核心还是调用内部ClientExecChain对象的execute,只不过在执行前后添加一些装饰逻辑。
  2. 如果ClientExecChain对象中包含一个HttpRequestExecutor对象
    那么他就是执行链的最终节点,直接调用HttpRequestExecutor对象的execute,也就是真正向服务端发送Http请求。

由以上分析,可以发现,最终节点只可能是MinimalClientExec、MainClientExec两者之一。

下面分别就装饰节点和最终节点进行分析。

3.1 装饰节点,以ProtocolExec为例

ProtocolExec类的代码十分干脆,直接实现execute方法,做一些Http协议相关的装饰逻辑。如上节所说,ProtocolExec是执行链上的一个装饰节点。阅读源码也确实发现,他只做了三件事。

  1. 解析request中的uri、target等属性
  2. 在调用下一节点的execute前后添加装饰逻辑(拦截器方式)
  3. 调用下一个节点的execute方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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");
// 解析request中的uri、target等属性
final HttpRequest original = request.getOriginal();
URI uri = null;
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
} else {
final String uriString = original.getRequestLine().getUri();
try {
uri = URI.create(uriString);
} catch (final IllegalArgumentException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug("Unable to parse '" + uriString + "' as a valid URI; " +
"request URI and Host header may be inconsistent", ex);
}
}
}
request.setURI(uri);
// Re-write request URI if needed
rewriteRequestURI(request, route);
final HttpParams params = request.getParams();
HttpHost virtualHost = (HttpHost) params.getParameter(ClientPNames.VIRTUAL_HOST);
// HTTPCLIENT-1092 - add the port if necessary
if (virtualHost != null && virtualHost.getPort() == -1) {
final int port = route.getTargetHost().getPort();
if (port != -1) {
virtualHost = new HttpHost(virtualHost.getHostName(), port,
virtualHost.getSchemeName());
}
if (this.log.isDebugEnabled()) {
this.log.debug("Using virtual host" + virtualHost);
}
}
HttpHost target = null;
if (virtualHost != null) {
target = virtualHost;
} else {
if (uri != null && uri.isAbsolute() && uri.getHost() != null) {
target = new HttpHost(uri.getHost(), uri.getPort(), uri.getScheme());
}
}
if (target == null) {
target = route.getTargetHost();
}
// Get user info from the URI
if (uri != null) {
final String userinfo = uri.getUserInfo();
if (userinfo != null) {
CredentialsProvider credsProvider = context.getCredentialsProvider();
if (credsProvider == null) {
credsProvider = new BasicCredentialsProvider();
context.setCredentialsProvider(credsProvider);
}
credsProvider.setCredentials(
new AuthScope(target),
new UsernamePasswordCredentials(userinfo));
}
}
// 执行HttpRequest拦截器
// Run request protocol interceptors
context.setAttribute(HttpCoreContext.HTTP_TARGET_HOST, target);
context.setAttribute(HttpClientContext.HTTP_ROUTE, route);
context.setAttribute(HttpCoreContext.HTTP_REQUEST, request);
this.httpProcessor.process(request, context);
// 关键,调用执行链上下一个节点的execute方法
final CloseableHttpResponse response = this.requestExecutor.execute(route, request,
context, execAware);
try {
// 执行HttpResponse拦截器
// Run response protocol interceptors
context.setAttribute(HttpCoreContext.HTTP_RESPONSE, response);
this.httpProcessor.process(response, context);
return response;
} catch (final RuntimeException ex) {
response.close();
throw ex;
} catch (final IOException ex) {
response.close();
throw ex;
} catch (final HttpException ex) {
response.close();
throw ex;
}
}

3.2 最终节点,以MainClientExec为例

通过执行链上节点间的逐级调用,最终来到了执行链的终点,Http请求的真正发起者。由于其execute方法过长,这里选择性省略。其主要内容包含三部分。

  1. 包含大量身份认证、连接校验、设置超时、设置存活时间代码
  2. 创建socket链路(establishRoute
  3. 调用HttpRequestExecutor对象的execute方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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");
// ... 此处忽略了大段代码
// 身份认证、连接校验等操作
try {
if (execAware != null) {
execAware.setCancellable(connHolder);
}
HttpResponse response;
for (int execCount = 1;; execCount++) {
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");
}
if (!managedConn.isOpen()) {
this.log.debug("Opening connection " + route);
try {
// 建立socket链路
establishRoute(proxyAuthState, managedConn, route, request, context);
} catch (final TunnelRefusedException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage());
}
response = ex.getResponse();
break;
}
}
// ... 此处忽略了部分代码
// 设置超时、身份认证等操作
// 关键, 调用HttpRequestExecutor对象的execute方法
response = requestExecutor.execute(request, managedConn, context);
// ... 此处忽略了大段代码
// 设置connection存活时间、身份认证相关操作
// 释放连接,返回response
// check for entity, release connection if possible
final HttpEntity entity = response.getEntity();
if (entity == null || !entity.isStreaming()) {
// connection not needed and (assumed to be) in re-usable state
connHolder.releaseConnection();
return new HttpResponseProxy(response, null);
} else {
return new HttpResponseProxy(response, connHolder);
}
} catch (final ConnectionShutdownException ex) {
final InterruptedIOException ioex = new InterruptedIOException(
"Connection has been shut down");
ioex.initCause(ex);
throw ioex;
} catch (final HttpException ex) {
connHolder.abortConnection();
throw ex;
} catch (final IOException ex) {
connHolder.abortConnection();
throw ex;
} catch (final RuntimeException ex) {
connHolder.abortConnection();
throw ex;
}
}

4. 发起Http请求,HttpRequestExecutor

类如其名,HttpRequest请求执行者。其execute方法很简单,直接调用HttpClientConnection类的方法实现发送request接收response,而后将response返回。至此,Http请求的所有组装过程结束,请求发出,接收响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public HttpResponse execute(
final HttpRequest request,
final HttpClientConnection conn,
final HttpContext context) throws IOException, HttpException {
Args.notNull(request, "HTTP request");
Args.notNull(conn, "Client connection");
Args.notNull(context, "HTTP context");
try {
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;
}
}

5. 总结

httpClient.execute(httpGet)短短一行代码,实际内部包含了大量逻辑,请求执行链上的每一个节点都走过一遍、所有附加逻辑都组装到一起,Http请求才最终发出。

只有通过了解httpClient.execute(httpGet)的内部逻辑,我们才能了解HttpClient的设计理念,我们才能了解到之前没有注意到的细节。

希望这篇分析,可以抛砖引玉,引发大家深究的兴趣。也借由这篇分析为大家理清代码的脉络,方便追踪、分析。

6. 后续

  1. 如果继续往下跟下去,具体实现涉及流操作、原始数据的解析等,最后跟到native方法,改由jvm实现。
  2. 如果想继续跟下去,可以使用一些小技巧,比如将socketTimeout设置为1,迫使其产生超时异常,通过异常栈即可找到调用路径。