在第一篇文章 okhttp 源码解析笔记整理——同步、异步请求 中可以知道,不管是同步请求还是异步请求,都最终是通过
RealCall # getResponseWithInterceptorChain()
来实现真实的请求的。
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
// 收集拦截器的临时列表
List<Interceptor> interceptors = new ArrayList<>();
// 先添加通过 OkHttpClient.Builder # addInterceptor() 方法添加的拦截器
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
// 如果不是 WebSocket 请求,
// 则添加通过 OkHttpClient.Builder # addNetworkInterceptor() 方法添加的拦截器
interceptors.addAll(client.networkInterceptors());
}
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);
}
从上面的方法可知,除了自定义的拦截器外,固定的会添加 5 个系统拦截器,来组成拦截器链(InterceptorChain
) 来实现请求。
首先来说一下拦截器链的本质,就是 责任链模式。
通过构建一系列的拦截器,来一层一层的处理请求时涉及到的逻辑,分工明确,从而实现了最终的请求。
其大概的代码模型如下:
其中 InterceptorChain
的 proceed()
方法会根据 index
取出 interceptors
拦截器链中的目标拦截器来进行处理,而拦截器就是通过 intercept()
方法来处理请求的。
class RealTestInterceptorChain(private val interceptors: List<TestInterceptor>, private val index: Int): TestInterceptor.TestChain {
override fun proceed(): TestResponse {
if (index >= interceptors.size) throw AssertionError()
val next = RealTestInterceptorChain(interceptors, index + 1)
val testInterceptor: TestInterceptor = interceptors[index]
val response = testInterceptor.intercept(next)
return response
}
}
可以看到,在 RealTestInterceptor
类型的拦截器中,又是通过调用 chain.proceed()
来获取下一级拦截器的 response
。
class RealTestInterceptor(private val curIndex: Int) : TestInterceptor {
override fun intercept(chain: TestInterceptor.TestChain): TestResponse {
// 在 chain.proceed() 之前,即请求之前,对 request 进行处理
val response = chain.proceed()
// 在 chain.proceed() 之后,即请求之后,对 response 进行处理
return response
}
}
就是这样,通过 InterceptorChain
来将对请求的处理传递到下一级拦截器,而当前拦截器 RealTestInterceptor
会借由 InterceptorChain
来获得下一级拦截器处理的后的结果,即 response
。
当然,这里还需要注意,拦截器是不可能无限调用的,总会有一个终点,而这个终点就是最后一个拦截器,在该拦截器里,不会继续再使用 chain.proceed()
,而是会构造出最原始的 response
来返回给之前的拦截器。就像下面的示例代码一样。
class RealTestFinalInterceptor(private val curIndex: Int) : TestInterceptor {
override fun intercept(chain: TestInterceptor.TestChain): TestResponse {
// 请求前,对 request 进行加工
val response = buildOriginalResponse()
// 请求后,对 response 进行加工
return response
}
}
最后附上最外层的调用代码:
val interceptors = ArrayList<TestInterceptor>()
interceptors.add(RealTestInterceptor(0))
interceptors.add(RealTestInterceptor(1))
interceptors.add(RealTestInterceptor(2))
interceptors.add(RealTestInterceptor(3))
interceptors.add(RealTestInterceptor(4))
// 最后一个 Interceptor 为特殊的
// 在里面不会直接再进行 chain.proceed() 的逻辑
// 而是会构造原始的 response 进行返回
// 这样就能够避免需要无限调用 chain.proceed() 的情况
interceptors.add(RealTestFinalInterceptor(5))
val chain = RealTestInterceptorChain(interceptors, 0)
val response = chain.proceed()
讲解了拦截器链的大致实现原理之后,就需要介绍一个前面提到的 5 个原始的拦截器了。
(1) RetryAndFollowUpInterceptor
对失败的请求进行恢复,或者进行重定向。
(2) BridgeInterceptor
在请求之前,根据用户的 request 构建一个网路请求对应的 request,在请求返回之后,根据网络响应的 response 构建一个用户 response。
(3) CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
// 根据 request 得到缓存的 response(可能为 null)
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
long now = System.currentTimeMillis();
// 根据 request 以及前面得到的 cacheCandidate(即 缓存的 response)生成一个缓存策略对象
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get()
// 得到缓存对象 strategy 的成员变量 networkRequest 和 cacheResponse
// 这两个成员变量的值是在 CacheStrategy.Factory#get()方法中根据传入的 now, chain.request(), cacheCandidate 等参数动态设置的
// 用于在后面进行各种情况的判断
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
// 如果不使用网络,且没有缓存的 response,则返回 504
// If we're forbidden from using the network and the cache is insufficient, fail.
if (networkRequest == null && cacheResponse == null) {
return new Response.Builder()
.request(chain.request())
.protocol(Protocol.HTTP_1_1)
.code(504)
.message("Unsatisfiable Request (only-if-cached)")
.body(Util.EMPTY_RESPONSE)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// 如果不使用网络,且有缓存的 response,则直接使用缓存的 response
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
// 前面都不是的话,则进行网络请求,得到网络响应的 response
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
// 如果缓存的 response 不为空,且网络响应的 response 的状态码为 304,
// 则由两者组合构成一个新的 response
// (304 表示服务端资源未发生改变,可直接使用客户端未过期的缓存)
if (networkResponse.code() == HTTP_NOT_MODIFIED) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);// 更新缓存
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// 否则直接由网络响应的 response 构成一个新的 response
// 与前面状态码为 304 时的还是有区别的
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
// 如果设置的 cache 且新的 response 有报文主体,且是可以缓存的,则进行缓存
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
// 如果请求方法不是 GET,则移除该网络请求对应的缓存
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
}
public static boolean invalidatesCache(String method) {
return method.equals("POST")
|| method.equals("PATCH")
|| method.equals("PUT")
|| method.equals("DELETE")
|| method.equals("MOVE"); // WebDAV
}
补充文章:OKHttp源码解析(4)----拦截器CacheInterceptor
(4) ConnectInterceptor
根据目标服务器建立一个连接(可能是一个新的连接,也可能是复用连接池中的连接),实际上建立连接就是调用了 streamAllocation.newStream()
,该方法创建了一个 HttpCodec 对象,它将在后面的步骤中被使用。
如果是复用之前的连接,则不需要再次进行三次握手的操作,如果是新建的连接,则需要进行三次握手操作,才是与服务器建立了真实的连接。
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
// 在连接池中寻找可用的连接,并创建 HttpCodec 实例(HttpCodec 用于编码 Http 的 request 与 response)
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
其中 streamAllocation # newStream()
的具体实现如下:
public HttpCodec newStream(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
// 通过 findHealthyConnection() 方法寻找一个有效的连接
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, chain, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
/**
* Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated
* until a healthy connection is found.
*/
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
// 内部又是不断的调用 findConnection() 方法,直到找到一个有效的连接
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
noNewStreams();
continue;
}
return candidate;
}
}
/**
* Returns a connection to host a new stream. This prefers the existing connection if it exists,
* then the pool, finally building a new connection.
*/
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; // 涉及到了 socket
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();
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) {
// Attempt to get a connection from the pool.
Internal.instance.acquire(connectionPool, address, this, null);
if (connection != null) {
foundPooledConnection = true;
result = connection;
} else {
selectedRoute = route;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
// (1)前面的逻辑会尝试:
// 1、用 this.connection(该成员属性的赋值可能会通过 acquire() 或者 releaseAndAcquire()
// 方法从外部传递进来,所以说是 already-allocated 的);
// 2、从连接池中寻找到的连接
// 如果通过前两步得到的 result 连接不为空,则直接返回该 result
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// (2)如果通过前面的判断,newRouteSelection 为 true,则会再次尝试从连接池中寻找
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
List<Route> routes = routeSelection.getAll();
for (int i = 0, size = routes.size(); i < size; i++) {
Route route = routes.get(i);
Internal.instance.acquire(connectionPool, address, this, route);
if (connection != null) {
foundPooledConnection = true;
result = connection;
this.route = route;
break;
}
}
}
// (3)如果再次尝试依旧不行的话,则会新建一个连接,并放入连接池中
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
// 实例化一个新的连接
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.
// 进行 TCP 与 TLS 握手(TLS 是关于 https 的)
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
reportedAcquired = true;
// Pool the connection.
// 将新建的连接放入连接池中
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
// 如果有另外一个多路复用的连接(多路复用是 http2.0 的概念),则直接使用那个,而释放掉刚才新建的
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
(5) CallServerInterceptor
最后一个拦截器,向服务器发起一次网络访问(这里是发起网络访问,与服务器建立连接是在 ConnectInterceptor
中完成的),负责向服务器发送请求数据,从服务器读取响应数据。其实现是基于 Okio 的。
在最一个拦截器中,就没有调用与 chain.proceed()
,而是真正的实现网络的请求,构造最原始的 response
。
补充文章: