Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
// 自定义普通拦截器
interceptors.addAll(client.interceptors());
// 1.重试机制
interceptors.add(retryAndFollowUpInterceptor);
// 2.封装请求首部
interceptors.add(new BridgeInterceptor(client.cookieJar()));
// 3.处理缓存相关
interceptors.add(new CacheInterceptor(client.internalCache()));
// 4.TCP连接拦截器
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 5.自定义网络拦截器
interceptors.addAll(client.networkInterceptors());
}
// 6.连接建立, 开始请求
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
最开始分析ConnectInterceptor时非常的吃力, 因为TCP/IP协议相关的知识掌握的太少了, 基本上每一步都需要查阅不少文章学习涉及到的网络协议知识.
这个拦截器涉及到的网络协议知识包括以下几个方面:
1、HTTP1.0、HTTP1.X、HTTP2.0之间的区别
2、DNS域名解析、负载均衡
3、代理服务器: DIRECT(直连)、HTTP、SOCKS
整个代码链路也是非常的长, 看完下来感觉挺懵逼了, 边看边画流程图, 整理总结每块代码做的事情, 防止以后再回顾时又要重头开始捋代码, 关于OKHTTP中很多都是有迹可循的, 基本都是按照书本上的知识再加上一些设计模式进行的编写.
大致是这么个流程:
(1) ROUTE流程图
Route为用于连接到服务器的具体路由, 由于存在代理或者DNS可能返回多个IP地址的情况, 所以同一个接口地址可能会对应多个ROUTE.
(2) 更细节的流程图
(3) ConnectionPool、RealConnection、StreamAllocation关系
(4) 该拦截器中所有对象描述
Route :
route用于连接到服务器的具体路由, 由于存在代理或者DNS可能返回多个IP地址的情况, 所以同一个接口地址可能会对应多个route.RouteSelector :
Route选择器, 其中存储了到某一个代理服务器上所有可用的RouteRealConnection :
使用Socket建立HTTP/HTTPS连接, 对应一个TCP连接, 同一个RealConnection上可能装载多个HTTP请求.ConnectionPool :
连接池, 缓存RealConnection, 与OkHttpClient是1:1关系, 一般情况全局只有一个OkHttpClient, 因此也只有一个全局的ConnectionPool, ConnectionPool具有缓存的能力, 因此肯定也提供了缓存清理的功能, 定期清理超时的RealConnection或者数量超过阈值的RealConnectionStreamAllocation :
结合源码可以看出, StreamAllocation贯穿了整个请求, 一个StreamAllocation对应一个Request, 同个StreamAllocation将ConnectionPool、RealConnection、Route、Request、RealCall、Interceptors进行关联. 可以理解为StreamAllocation就是OKHTTP网络请求层的桥梁, 将所有对象进行关联, 然后完成一次请求1. RealInterceptorChain.streamAllocation
OKHTTP第一个拦截器中对StreamAllocation进行了初始化, 然后传入到RealInterceptorChain
RetryAndFollowUpInterceptor:public Response intercept(Chain chain) {
...
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
}
这里需要关注两个对象: ConnectionPool、createAddress(request.url()).
结合代码可以了解到一个OkHttpClient对应一个ConnectionPool, 通常情况下或者是结合我们自己的项目, 全局实际上只有一个OkHttpClient实例. 所以也可以理解成全局只有一个ConnectionPool.1.1 ConnectionPool
public class OkHttpClient.Builder {
public Builder() {
...
connectionPool = new ConnectionPool();
}
}
public class ConnectionPool {
public ConnectionPool() {
this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
this.maxIdleConnections = maxIdleConnections;
this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);
}
}
截止到目前结合代码只知道: ConnectionPool里面记录了两个值: maxIdleConnections(最大空闲连接数)、keepAliveDurationNs(存活时间)
这里就涉及到HTTP1.0、HTTP1.X、HTTP2.0的区别:
1.2 HTTP1.0、HTTP1.X、HTTP2.0关于keep-alive
HTTP1.0 : 需要主动设置connection:keep-alive的连接方式, 才能开启长连接. Client每次请求都需要与服务器建立一个TCP连接, Server处理完成以后立即断开TCP连接, Server不跟踪每个客户端也不记录过去的请求(无状态)
HTTP1.1 : 默认支持keep-alive, 避免了连接建立和释放的开销, 但服务器必须按照客户端请求的先后顺序依次回送相应的结果, 以保证客户端能够区分出每次请求的响应内容. 通过Content-Length字段来判断当前请求的数据是否已经全部接收. 不允许同时存在两个并行的响应 HTTP2.0 : HTTP2.0实现了真正的并行传输, 它能够在一个TCP上进行任意数量的HTTP请求, 而这个强大的功能则是基于 **二进制分帧**的特性
同时还需要注意HTTP的keep-alive与TCP的KeepAlive的关系与区别
2.建立连接
StreamAllocation以及Address初始化完成之后, 开始进行连接操作
2.1 ConnectInterceptor.intercept
@Override
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 找到一个可复用的连接, 或者新建一个连接
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
streamAllocation.newStream查找可用连接或者新建连接, 调用链有点长, 流程图如下
2.1.1 ConnectionPool、RealConnection、StreamAllocation关系
一个连接池中最多持有5个空闲的连接, 每个连接上可以挂载多个Request请求
流程实在是太长了, 耐心挨个分析吧
2.2 StreamAllocation.findHealthyConnection
包括两个流程:
- 1、获取可复用的连接、或者新建一个连接
- 2、连接可用性校验
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
// 1.查找可用连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// 2.连接可用性校验
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
2.3 StreamAllocation.findConnection
如果再次阅读这块代码, 切记直接就被带到代码细节里面无法自拔了. 一定要先看大体流程, 带着流程带着疑问再看这一大段代码
找到可复用的连接的流程比较复杂, 代码比较长, 但是分析完成之后发现也有迹可循, 主要涉及到网络协议的以下几方面的知识:
总结这一块的流程:
- 1、
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
Connection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new streams.
releasedConnection = this.connection;
toClose = releaseIfNoNewStreams();
// 1.每一次Request都对应一个RealCall, 构造RealCall时会构造一个与该Request对应的StreamAllocation,
// 每一个StreamAllocation都会尝试从一个全局的ConnectionPool中取RealConnection, 如果取到了就与自己绑定,
// 如果没有取到, 则自己构建一个RealConnection, 然后丢到ConnectionPool中进行缓存, 以便其他Request可以复用
if (this.connection != null) {
// We had an already-allocated connection and it's good.
// 如果有则直接使用
result = this.connection;
releasedConnection = null;
}
if (!reportedAcquired) {
// If the connection was never reported acquired, don't report it as released!
releasedConnection = null;
}
if (result == null) {
// 2.连接池中找可复用的连接
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
// 3.这里是一个设计模式, 将关键节点通过Listener的方式对外暴露接口, 使用者可以通过hook这些关键节点获取
// 自己需要的数据
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
// 如果找到了可复用的RealConnection, 则直接返回
return result;
}
boolean newRouteSelection = false;
// 4.执行到这里说明未获取到可复用的连接. 关于Route相关放在<模块三>中进行分析
// 这里的设计模型也是有迹可循的, 因为存在多个代理服务器或者负载均衡一个host可能对于多个IP, 所以Client
// 到每一个Proxy之间都可能存在多个route, 每一个Proxy上的多个Route挂载到Selection上
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
// 5.查找Client到下一个代理服务器上面的所有路由
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
if (newRouteSelection) {
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
// 6.遍历查找合适的RealConnection
Internal.instance.get(connectionPool, address, this, route);
if (connection != null) {
// 找到了标识位置为true
foundPooledConnection = true;
result = connection;
this.route = route;
// 如果找到了退出该循环, 未找到退出
break;
}
}
}
if (!foundPooledConnection) {
// 说明未找到
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
route = selectedRoute;
refusedStreamCount = 0;
// 7.新建连接, 到这里思考一个问题: 创建的这个连接在何时被添加到ConnectionPool中,
// ConnectionPool中设置的最大RealConnection最大数为5, 如果超过了怎么处理新建的
// RealConnection
result = new RealConnection(connectionPool, selectedRoute);
acquire(result, false);
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
// 8.建立连接, 连接建立流程对应<模块四>
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// RealConnection添加到ConnectionPool中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
2.4 ConnectionPool.get
从连接池中查找可复用的连接, 这里主要涉及到三个知识点:
- 1、代理服务器
- 2、DNS域名解析
- 3、HTTP1.0、HTTP1.X、HTTP2.0之间keep-alive的区别
@Nullable
RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
// 1.判断连接是否可复用
if (connection.isEligible(address, route)) {
// 2.找到可复用的连接, 使用它, 与StreamAllocation进行绑定
streamAllocation.acquire(connection, true);
return connection;
}
}
return null;
}
如果不清楚代理服务器、DNS域名解析这些知识, 阅读这块的代码会非常吃力
2.5 找可用连接RealConnection.isEligible
Protocol枚举解析:
| 变量/方法 | 解释 |
|---|---|
| Protocol(String protocol) | 构造一个协议 |
| Protocol get(String potocol) | 获取协议 |
| HTTP_1_0("http/1.0") | HTTP1.0协议 |
| HTTP_1_1("http/1.1") | HTTP1.1协议 |
| HTTP_2("h2") | HTTP2.0协议 |
public boolean isEligible(Address address, @Nullable Route route) {
// 1.如果是HTTP1.0、HTTP1.1不支持, 如果是HTTP2.0支持连接复用:
// allocationLimit默认为1, 如果建立TCP连接时根据protocol获取当前为HTTP2.0,
// 则修改allocationLimit的值, 如果当前协议是HTTP1.0或者HTTP1.1, allocationLimit = 1,
// 既不支持RealConnection复用.
if (allocations.size() >= allocationLimit || noNewStreams) return false;
// 2.如果Address匹配不成功, 该连接不可复用
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// 3.如果主机名相同, 则认为肯定可以复用该连接
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// 4.http2Connection表示当前协议版本类型, 如果是非HTTP2.0协议, 则认为不可复用
if (http2Connection == null) return false;
// 5.执行到这里, 说明address对应的host与当前RealConnection的host不相同, 但是host不相同并不代表连接不可复用,
// 因为DNS解析, 请求域名最终会被解析为IP地址.
if (route == null) return false;
if (route.proxy().type() != Proxy.Type.DIRECT) return false;
if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
if (!this.route.socketAddress().equals(route.socketAddress())) return false;
if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // The caller's address can be carried by this connection.
}
2.6 连接绑定StreamAllocation.acquire
public void acquire(RealConnection connection, boolean reportedAcquired) {
assert (Thread.holdsLock(connectionPool));
if (this.connection != null) throw new IllegalStateException();\
// 1.连接绑定
this.connection = connection;
// 2.标识位置为true
this.reportedAcquired = reportedAcquired;
// 3.将StreamAllocation挂载到RealConnection上面
connection.allocations.add(new StreamAllocationReference(this, callStackTrace));
}
三、路由Route
- 1、Route为用于连接到服务器的具体路由, 由于存在代理或者DNS可能返回多个IP地址的情况, 所以同一个接口地址可能会对应多个ROUTE.
- 2、RouteSelector: Route选择器, 其中存储了所有可用的route, 在准备连接时通过
RouteSelector.next方法获取下一个route
3.1 RouteSelector
结合源码RouteSelector在StreamAllocation中进行初始化
public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
EventListener eventListener, Object callStackTrace) {
this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
}
public RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
EventListener eventListener) {
// 初始化代理服务器, 如果没有, 使用Proxy.NO_PROXY
resetNextProxy(address.url(), address.proxy());
}