之前分析了 okhttp 的缓存拦截器 CacheInterceptor 的实现机制,这次再来详细分析一下 okhttp 的工作流程和其他拦截器。先看一下工作流程(本文基于 4.9.3)
使用
OkHttpClient client = new OkHttpClient();
// Create request for remote resource.
Request request = new Request.Builder()
.url(ENDPOINT)
.build();
// Execute the request and retrieve the response.
try (Response response = client.newCall(request).execute()) {
// Deserialize HTTP response to concrete type.
ResponseBody body = response.body();
List<Contributor> contributors = CONTRIBUTORS_JSON_ADAPTER.fromJson(body.source());
// Sort list by the most contributions.
Collections.sort(contributors, (c1, c2) -> c2.contributions - c1.contributions);
// Output list of contributors.
for (Contributor contributor : contributors) {
System.out.println(contributor.login + ": " + contributor.contributions);
}
}
okhttp 支持同步和异步请求,上面是同步请求,先创建 OkHttpClient 对象再创建
Request 对象,调用 OkHttpClient 对象的 newCall 方法返回 RealCall 对象。RealCall 是 Call 的唯一实现类,其中有两个方法 execute 是同步请求方法 enqueue 是异步请求方法,先看一下 execute 的工作流程
// okhttp3.internal.connection.RealCall#execute
override fun execute(): Response {
// 重复执行会抛出异常
check(executed.compareAndSet(false, true)) { "Already Executed" }
// 超时监测
timeout.enter()
// EventListener 回调
callStart()
try {
// 将请求加入调度器
client.dispatcher.executed(this)
// 执行拦截器返回响应
return getResponseWithInterceptorChain()
} finally {
client.dispatcher.finished(this)
}
}
// okhttp3.Dispatcher#executed
// 只是将请求加入 runningSyncCalls 队列中
@Synchronized internal fun executed(call: RealCall) {
// runningSyncCalls 表示正在执行的同步请求列表
runningSyncCalls.add(call)
}
再看看一下异步请求之后再回来看拦截器链
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(ENDPOINT)
.build();
Call call = client.newCall(request);
// 异步请求需要传入回调接口
call.enqueue(new Callback() {
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
ResponseBody body = response.body();
List<Contributor> contributors = CONTRIBUTORS_JSON_ADAPTER.fromJson(body.source());
Collections.sort(contributors, (c1, c2) -> c2.contributions - c1.contributions);
for (Contributor contributor : contributors) {
System.out.println(contributor.login + ": " + contributor.contributions);
}
}
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
}
});
// okhttp3.internal.connection.RealCall#enqueue
override fun enqueue(responseCallback: Callback) {
// 同样不能重复执行
check(executed.compareAndSet(false, true)) { "Already Executed" }
// EventListener 接口回调
callStart()
// 传入 responseCallback 创建一个 AsyncCall 对象加入请求调度器
client.dispatcher.enqueue(AsyncCall(responseCallback))
}
// okhttp3.Dispatcher#enqueue
internal fun enqueue(call: AsyncCall) {
synchronized(this) {
// 加入等待执行的异步请求队列中
readyAsyncCalls.add(call)
// 如果不是 forWebSocket 请求
if (!call.call.forWebSocket) {
// 查找同一域名/主机名下的请求
val existingCall = findExistingCallWithHost(call.host)
// 记录同一域名/主机名下的请求数
if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
}
}
// okhttp3.Dispatcher#promoteAndExecute
promoteAndExecute()
}
异步请求也跟同步请求差不多加入到 readyAsyncCalls 队列中,然后记录了同一域名/主机名下请求数最后调用了 promoteAndExecute 方法
private fun promoteAndExecute(): Boolean {
this.assertThreadDoesntHoldLock()
val executableCalls = mutableListOf<AsyncCall>()
val isRunning: Boolean
synchronized(this) {
val i = readyAsyncCalls.iterator()
// 遍历待执行的异步请求列表
while (i.hasNext()) {
val asyncCall = i.next()
// runningAsyncCalls 表示的是正在执行的异步网络请求列表
// 如果当前正在执行的异步请求数超出最大请求数 maxRequests = 64 则不执行请求
if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
// 如果同一个主机名下的请求数超出 maxRequestsPerHost = 5 则不执行请求
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
// 从待执行列表中移除异步请求
i.remove()
// 主机名下的请求数加一
asyncCall.callsPerHost.incrementAndGet()
// 将请求加入一个临时列表
executableCalls.add(asyncCall)
// 将请求加入正在执行的异步请求列表
runningAsyncCalls.add(asyncCall)
}
// 是否有正在执行的网络请求(包含同步和异步请求)
isRunning = runningCallsCount() > 0
}
for (i in 0 until executableCalls.size) {
val asyncCall = executableCalls[i]
// AsyncCall 继承自 Runnable 加入线程池去执行
asyncCall.executeOn(executorService)
}
// 返回是否有正在执行的网络请求
return isRunning
}
总结一下异步请求的工作流程,传入异步请求 Callback 创建 AsyncCall 对象,再将 AsyncCall 对象加入到待执行的异步请求列表中然后遍历这个待执行的异步请求列表,如果当前正在执行的异步请求列表数不大于 64 并且要请求的主机名下的请求数小于 5 则将请求加入到正在执行的异步请求列表并且因为 AsyncCall 继承自 Runnable 将它加入到线程池去执行
// okhttp3.internal.connection.RealCall.AsyncCall#executeOn
fun executeOn(executorService: ExecutorService) {
client.dispatcher.assertThreadDoesntHoldLock()
var success = false
try {
// 加入线程池去执行
executorService.execute(this)
success = true
} catch (e: RejectedExecutionException) {
val ioException = InterruptedIOException("executor rejected")
ioException.initCause(e)
noMoreExchanges(ioException)
responseCallback.onFailure(this@RealCall, ioException)
} finally {
if (!success) {
// 如果任务没有执行成功
client.dispatcher.finished(this)
}
}
}
// okhttp3.Dispatcher#finished
internal fun finished(call: AsyncCall) {
// 将这个异步请求对应的主机名下的请求数减一
call.callsPerHost.decrementAndGet()
finished(runningAsyncCalls, call)
}
private fun <T> finished(calls: Deque<T>, call: T) {
val idleCallback: Runnable?
synchronized(this) {
if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")
idleCallback = this.idleCallback
}
// 再次调用 promoteAndExecute 方法尝试去执行其他异步请求
val isRunning = promoteAndExecute()
// 如果 isRunning 为 false 并且 idleCallback 不为空
if (!isRunning && idleCallback != null) {
// 执行 idleCallback
// 这里与 IdleHandler 机制很相似当消息列表中没有消息时执行 IdleHandler
idleCallback.run()
}
}
上面是将 AsyncCall 加入到线程池去执行,如果执行失败调用 finished 去尝试执行其他异步请求,再回头看一下 AsyncCall 的 run 方法
override fun run() {
threadName("OkHttp ${redactedUrl()}") {
var signalledCallback = false
// 超时监测机制
timeout.enter()
try {
// 调用拦截器链返回响应
val response = getResponseWithInterceptorChain()
signalledCallback = true
// 回调响应结果
responseCallback.onResponse(this@RealCall, response)
} catch (e: IOException) {
if (signalledCallback) {
// Do not signal the callback twice!
Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)
} else {
// 回调请求失败
responseCallback.onFailure(this@RealCall, e)
}
} catch (t: Throwable) {
cancel()
if (!signalledCallback) {
val canceledException = IOException("canceled due to $t")
canceledException.addSuppressed(t)
// 回调请求失败
responseCallback.onFailure(this@RealCall, canceledException)
}
throw t
} finally {
// 流程结束后同样调用 finished 尝试去执行其他异步请求,具体流程上面已经分析过了
client.dispatcher.finished(this)
}
}
}
到这里就与同步请求一样了最后都是调用拦截器链返回响应,这一步也算是 okhttp 的核心了
internal fun getResponseWithInterceptorChain(): Response {
val interceptors = mutableListOf<Interceptor>()
// 开发者自己配置的应用拦截器
interceptors += client.interceptors
// 请求重试和重定向拦截器
interceptors += RetryAndFollowUpInterceptor(client)
// 添加请求头拦截器
interceptors += BridgeInterceptor(client.cookieJar)
// 缓存拦截器
interceptors += CacheInterceptor(client.cache)
// 建立连接拦截器
interceptors += ConnectInterceptor
if (!forWebSocket) {
// 如果不是 webSocket 请求添加开发者设置的网络请求拦截器
interceptors += client.networkInterceptors
}
// 真正发送请求接收响应的拦截器
interceptors += CallServerInterceptor(forWebSocket)
// 创建拦截器链
val chain = RealInterceptorChain(
call = this,
interceptors = interceptors,
index = 0,
exchange = null,
request = originalRequest,
connectTimeoutMillis = client.connectTimeoutMillis,
readTimeoutMillis = client.readTimeoutMillis,
writeTimeoutMillis = client.writeTimeoutMillis
)
var calledNoMoreExchanges = false
try {
// 调用拦截器链的 proceed 方法依次执行每一个拦截器
val response = chain.proceed(originalRequest)
if (isCanceled()) {
response.closeQuietly()
throw IOException("Canceled")
}
return response
} catch (e: IOException) {
calledNoMoreExchanges = true
throw noMoreExchanges(e) as Throwable
} finally {
if (!calledNoMoreExchanges) {
noMoreExchanges(null)
}
}
}
先来看一下 proceed 方法的代码
override fun proceed(request: Request): Response {
check(index < interceptors.size)
calls++
if (exchange != null) {
// 如果中间改变了域名或者端口抛出异常
check(exchange.finder.sameHostAndPort(request.url)) {
"network interceptor ${interceptors[index - 1]} must retain the same host and port"
}
// 如果同一个拦截器多次执行此方法抛出异常
check(calls == 1) {
"network interceptor ${interceptors[index - 1]} must call proceed() exactly once"
}
}
// 使用下一个拦截器创建新的拦截器链
val next = copy(index = index + 1, request = request)
val interceptor = interceptors[index]
// 执行下一个拦截器的逻辑
val response = interceptor.intercept(next) ?: throw NullPointerException(
"interceptor $interceptor returned null")
if (exchange != null) {
check(index + 1 >= interceptors.size || next.calls == 1) {
"network interceptor $interceptor must call proceed() exactly once"
}
}
check(response.body != null) { "interceptor $interceptor returned a response with no body" }
return response
}
拦截器分为应用拦截器 interceptors 和网络拦截器 networkInterceptors 它们的区别就是执行时机不同,interceptors 首先执行 networkInterceptors 仅在 CallServerInterceptor 之前执行,下面来看第一个拦截器 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor
// okhttp3.internal.http.RetryAndFollowUpInterceptor#intercept
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
// 重定向次数
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
// 创建 ExchangeFinder 对象用于重用或者新建连接 RealConnection
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
// 正常流程直接执行下一个拦截器的逻辑
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
// 因为路由失败,请求可能还没有发出
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
// 可能与服务器通信失败,请求可能已经发出了
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
// 代码省略 ..
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
RetryAndFollowUpInterceptor 包含两部分重试和重定向,先来看重试逻辑。如果发生了路由或者 IO 异常则尝试重新发送请求,具体是否需要重试的逻辑在 recover 方法里
// okhttp3.internal.http.RetryAndFollowUpInterceptor#recover
private fun recover(
e: IOException,
call: RealCall,
userRequest: Request,
requestSendStarted: Boolean
): Boolean {
// 如果应用层禁止重试,默认是 true 可设置
if (!client.retryOnConnectionFailure) return false
// 如果请求已经发送并且请求是一次性的(默认是 false 子类可重写)
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false
// 异常是致命的不能重试,比如证书验证失败
if (!isRecoverable(e, requestSendStarted)) return false
// 没有其他路线可重试
if (!call.retryAfterFailure()) return false
// 重试
return true
}
以上是重试的逻辑如果几个条件都不满足则会一直重试,下面接着看一下重定向的逻辑
// okhttp3.internal.http.RetryAndFollowUpInterceptor#intercept
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
// 代码省略 ..
// 关联上一次请求的响应,上一次请求的响应体一定为 null
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
// 如果重定向请求为 null 说明不需要重定向直接返回响应
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
// 如果重定向请求是一次性的返回响应
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
// 如果超出最大重定向次数 20
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
// 记录上一次的请求和响应继续 while 循环执行请求
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
可以看到是否需要重定向取决于 followUpRequest 方法的返回值
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
// 代码省略 ..
// 这里只分析状态码是 3 开头的情况,其他认证或者服务器错误相关忽略
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
// 代码省略 ..
}
}
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// 如果不允许重试默认是 true 可设置
if (!client.followRedirects) return null
// 如果响应头里没有 Location 字段
// 根据网络协议 Location 必须包含在响应报文里并且只有配合重定向状态码才有意义
// 表示服务器要求重定向的 uri
val location = userResponse.header("Location") ?: return null
// 如果返回的字段合法创建 HttpUrl 对象
val url = userResponse.request.url.resolve(location) ?: return null
// 如果不允许从 https/http 到 http/https 默认为 true 可配置
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
// 根据请求方法重新构建请求
val requestBuilder = userResponse.request.newBuilder()
if (HttpMethod.permitsRequestBody(method)) {
val responseCode = userResponse.code
val maintainBody = HttpMethod.redirectsWithBody(method) ||
responseCode == HTTP_PERM_REDIRECT ||
responseCode == HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
requestBuilder.method("GET", null)
} else {
val requestBody = if (maintainBody) userResponse.request.body else null
requestBuilder.method(method, requestBody)
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding")
requestBuilder.removeHeader("Content-Length")
requestBuilder.removeHeader("Content-Type")
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
对于响应码是 3 开头的会获取服务器返回的响应头里的 Location 字段并且重新创建请求。总结因为重试和重定向都有可能重新发送网络请求所以它们的处理逻辑在同一个拦截器里,重试是 okhttp 框架层的处理并且没有限制次数,重定向是网络协议的部分次数限制为 20 次。下面来看 BridgeInterceptor
BridgeInterceptor
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
val requestBuilder = userRequest.newBuilder()
val body = userRequest.body
if (body != null) {
val contentType = body.contentType()
if (contentType != null) {
// 添加请求头 Content-Type 表示请求体的类型如 application/pdf
requestBuilder.header("Content-Type", contentType.toString())
}
val contentLength = body.contentLength()
if (contentLength != -1L) {
// 如果长度不是 -1 添加请求头 Content-Length 表示请求体 body 的长度移除请求头 Transfer-Encoding
requestBuilder.header("Content-Length", contentLength.toString())
requestBuilder.removeHeader("Transfer-Encoding")
} else {
// 否则添加请求头 Transfer-Encoding 表示 body 不是定长的分块传输移除请求头 Content-Length
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
// Host 请求字段只能出现在请求头里,是唯一一个 http/1.1 要求必须出现的字段
// 告诉服务器这个请求由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候服务端就需要此字段来做选择
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
// 保持连接
// http 是基于 tcp/ip 的每次请求响应都需要三次握手四次挥手代价比较高所以从 http/1.1 默认启用长连接
// 只要向服务器发送一次请求后续的请求都会重复利用第一次打开的 tcp 连接也就是长连接
// 不管客户端是否显式要求长连接,如果服务器支持长连接总会在响应报文里放一个
// Connection: keep-alive 字段告诉客户端是支持长连接的
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
// 如果没有设置压缩算法并且不是分段请求则添加压缩算法 gzip 但这只是告诉服务器客户端支持这种压缩算法
// 但是响应真正有没有按照这个方式压缩取决于响应头里的 Content-Encoding
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
// cookies 默认是空实现如果有需要的话可在创建 OkHttpClient 时传入
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
// User-Agent 默认是 okhttp/${OkHttp.VERSION}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
// 调用拦截器链方法执行下一个拦截器
val networkResponse = chain.proceed(requestBuilder.build())
// 取响应头里的 Set-Cookie 保存下来
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
// 如果响应头 Content-Encoding 是 gzip 并且包含响应体则解压缩
// gzip 通常只对文本有比较好的压缩率,而图片、音视频等多媒体数据本省已经是高度压缩的
// 再用 gzip 压缩也不会变小(甚至还有可能会增大一点)所以它就失效了
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
}
因为使用 okhttp 不需要指定请求头但是协议规范上需要的所以 BridgeInterceptor 做的事情就是添加必要的请求头和解压缩(如果需要)下一个拦截器是 CacheInterceptor 因为已经分析过了所以不再重复分析了,这里简单总结一下。okhttp 缓存的实现就是依据 http 协议解析请求响应头得到 CacheControl 对象再通过里面的属性判断构造了 CacheStrategy 对象,对 CacheStrategy 里的两个变量 networkRequest 和 cacheResponse 赋值来决定后续的逻辑是返回缓存还是继续网络请求,并且如果命中缓存则不会执行网络拦截器。下面直接看 ConnectInterceptor
ConnectInterceptor
// okhttp3.internal.connection.ConnectInterceptor
object ConnectInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 关键代码
// 复用/创建连接 RealConnection
// 通过 RealConnection 对象创建 ExchangeCodec 对象
// ExchangeCodec 的作用是对请求编码和对响应解码,它有两个实现类分别对应 http/1 和 http/2
// 最后通过 ExchangeCodec 创建 Exchange 对象,它用来发送 http 请求和接收 http 响应
val exchange = realChain.call.initExchange(chain)
val connectedChain = realChain.copy(exchange = exchange)
return connectedChain.proceed(realChain.request)
}
}
// okhttp3.internal.connection.RealCall#initExchange
internal fun initExchange(chain: RealInterceptorChain): Exchange {
// 代码省略 ..
val exchangeFinder = this.exchangeFinder!!
// 复用/创建连接 RealConnection 创建 ExchangeCodec 对象
val codec: ExchangeCodec = exchangeFinder.find(client, chain)
val result = Exchange(this, eventListener, exchangeFinder, codec)
this.interceptorScopedExchange = result
this.exchange = result
// 代码省略 ..
return result
}
// okhttp3.internal.connection.ExchangeFinder#find
fun find(
client: OkHttpClient,
chain: RealInterceptorChain
): ExchangeCodec {
try {
// 复用/创建连接 RealConnection
val resultConnection = findHealthyConnection(
connectTimeout = chain.connectTimeoutMillis,
readTimeout = chain.readTimeoutMillis,
writeTimeout = chain.writeTimeoutMillis,
pingIntervalMillis = client.pingIntervalMillis,
connectionRetryEnabled = client.retryOnConnectionFailure,
doExtensiveHealthChecks = chain.request.method != "GET"
)
// 创建 ExchangeCodec 对象
return resultConnection.newCodec(client, chain)
} catch (e: RouteException) {
trackFailure(e.lastConnectException)
throw e
} catch (e: IOException) {
trackFailure(e)
throw RouteException(e)
}
}
ConnectInterceptor 的代码是几个拦截器里最简单的了,但是包含的内容却不简单。主要做的事情是创建/复用连接 RealConnection 对象,并且使用这对连接创建 ExchangeCodec 对象它用来对请求编码和响应解码,最后创建 Exchange 对象并传给拦截器链用于在下一个拦截器中真正收发请求响应,下面看一下创建/复用连接的逻辑
// okhttp3.internal.connection.ExchangeFinder#findHealthyConnection
private fun findHealthyConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean,
doExtensiveHealthChecks: Boolean
): RealConnection {
while (true) {
val candidate = findConnection(
connectTimeout = connectTimeout,
readTimeout = readTimeout,
writeTimeout = writeTimeout,
pingIntervalMillis = pingIntervalMillis,
connectionRetryEnabled = connectionRetryEnabled
)
// Confirm that the connection is good.
if (candidate.isHealthy(doExtensiveHealthChecks)) {
return candidate
}
// 设置此连接不可用
candidate.noNewExchanges()
// 如果还有路线可用继续寻找可用连接
if (nextRouteToTry != null) continue
val routesLeft = routeSelection?.hasNext() ?: true
if (routesLeft) continue
val routesSelectionLeft = routeSelector?.hasNext() ?: true
if (routesSelectionLeft) continue
// 如果没有找到可用连接并且没有路线可用抛出异常
throw IOException("exhausted all routes")
}
}
private fun findConnection(
connectTimeout: Int,
readTimeout: Int,
writeTimeout: Int,
pingIntervalMillis: Int,
connectionRetryEnabled: Boolean
): RealConnection {
if (call.isCanceled()) throw IOException("Canceled")
val callConnection = call.connection
if (callConnection != null) {
var toClose: Socket? = null
synchronized(callConnection) {
// 如果连接不可用,如果服务端响应头包含 Connection:close 则会设置连接不可用
// 如果主机或端口号不同
if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
// 从这个连接分配到的 Call 列表中删除此 Call
toClose = call.releaseConnectionNoEvents()
}
}
// 重用这个连接
if (call.connection != null) {
check(toClose == null)
return callConnection
}
// 连接被释放关闭 socket
toClose?.closeQuietly()
eventListener.connectionReleased(call, callConnection)
}
// We need a new connection. Give it fresh stats.
refusedStreamCount = 0
connectionShutdownCount = 0
otherFailureCount = 0
// 尝试从线程池获取连接
if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
// Nothing in the pool. Figure out what route we'll try next.
val routes: List<Route>?
val route: Route
if (nextRouteToTry != null) {
// Use a route from a preceding coalesced connection.
routes = null
route = nextRouteToTry!!
nextRouteToTry = null
} else if (routeSelection != null && routeSelection!!.hasNext()) {
// Use a route from an existing route selection.
routes = null
route = routeSelection!!.next()
} else {
// Compute a new route selection. This is a blocking operation!
var localRouteSelector = routeSelector
if (localRouteSelector == null) {
localRouteSelector = RouteSelector(address, call.client.routeDatabase, call, eventListener)
this.routeSelector = localRouteSelector
}
val localRouteSelection = localRouteSelector.next()
routeSelection = localRouteSelection
routes = localRouteSelection.routes
if (call.isCanceled()) throw IOException("Canceled")
// 切换新的路线从线程池中获取连接
// 我的理解是 http/2 支持多路复用,不同的域名可能返回多个 ip 地址并且可能会有重叠,这里就是为了处理这种情况不确定
if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
val result = call.connection!!
eventListener.connectionAcquired(call, result)
return result
}
route = localRouteSelection.next()
}
// 如果没有可复用的连接则创建新的连接
val newConnection = RealConnection(connectionPool, route)
call.connectionToCancel = newConnection
try {
// 新的连接握手创建 Socket 对象
newConnection.connect(
connectTimeout,
readTimeout,
writeTimeout,
pingIntervalMillis,
connectionRetryEnabled,
call,
eventListener
)
} finally {
call.connectionToCancel = null
}
call.client.routeDatabase.connected(newConnection.route())
// 因为获取连接的操作并不是同步的所以再尝试获取一次连接,如果找到则关闭之前创建的连接复用获取到的连接
if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
val result = call.connection!!
nextRouteToTry = route
newConnection.socket().closeQuietly()
eventListener.connectionAcquired(call, result)
return result
}
// 否则使用新创建的连接并且将连接加入到连接池
synchronized(newConnection) {
connectionPool.put(newConnection)
call.acquireConnectionNoEvents(newConnection)
}
eventListener.connectionAcquired(call, newConnection)
return newConnection
}
如果没有可复用的连接则创建新的连接并且把新创建的连接加入到连接池,并且在加入连接池的时候启动定时任务清理空闲连接
// okhttp3.internal.connection.RealConnectionPool#put
fun put(connection: RealConnection) {
connection.assertThreadHoldsLock()
connections.add(connection)
cleanupQueue.schedule(cleanupTask)
}
// okhttp3.internal.connection.RealConnectionPool#cleanupTask
private val cleanupTask = object : Task("$okHttpName ConnectionPool") {
override fun runOnce() = cleanup(System.nanoTime())
}
// okhttp3.internal.connection.RealConnectionPool#cleanup
fun cleanup(now: Long): Long {
// 正在使用的连接数
var inUseConnectionCount = 0
// 空闲连接数
var idleConnectionCount = 0
// 空闲时间最长的连接
var longestIdleConnection: RealConnection? = null
// 空闲最长时间
var longestIdleDurationNs = Long.MIN_VALUE
// Find either a connection to evict, or the time that the next eviction is due.
for (connection in connections) {
synchronized(connection) {
// 如果连接正在使用中
if (pruneAndGetAllocationCount(connection, now) > 0) {
inUseConnectionCount++
} else {
idleConnectionCount++
// 记录空闲时间最长的连接和这个连接对应的时间
val idleDurationNs = now - connection.idleAtNs
if (idleDurationNs > longestIdleDurationNs) {
longestIdleDurationNs = idleDurationNs
longestIdleConnection = connection
} else {
Unit
}
}
}
}
when {
// this.keepAliveDurationNs 允许的最大空闲时间 5 分钟
// this.maxIdleConnections 允许的最大空闲连接数 5 个
// 如果上面记录的空闲连接的空闲时长超过 '允许的最大空闲时间' 或者空闲连接数超出 '允许的最大空闲连接数' 则关闭这个连接
// 并且返回 0 表示立即进行下一次清理
longestIdleDurationNs >= this.keepAliveDurationNs
|| idleConnectionCount > this.maxIdleConnections -> {
val connection = longestIdleConnection!!
synchronized(connection) {
if (connection.calls.isNotEmpty()) return 0L // No longer idle.
if (connection.idleAtNs + longestIdleDurationNs != now) return 0L // No longer oldest.
connection.noNewExchanges = true
connections.remove(longestIdleConnection)
}
connection.socket().closeQuietly()
if (connections.isEmpty()) cleanupQueue.cancelAll()
// Clean up again immediately.
return 0L
}
// 如果有空闲连接但是上面记录的空闲连接的空闲时长没有超过 '允许的最大空闲时间' 则过一段时间再次清理
// 比如允许的最大空闲时间 5 分钟并且这个连接空闲了 3 分钟则 5 - 3 = 2 分钟后启动清理任务
idleConnectionCount > 0 -> {
// A connection will be ready to evict soon.
return keepAliveDurationNs - longestIdleDurationNs
}
// 如果没有空闲连接但有正在使用的连接则 5 分钟后再次启动清理任务
inUseConnectionCount > 0 -> {
// All connections are in use. It'll be at least the keep alive duration 'til we run
// again.
return keepAliveDurationNs
}
else -> {
// 否则说明没有任何连接则什么都不做
return -1
}
}
}
连接的清理跟线程池的空闲线程的处理和 Handler 消息机制中对消息队列中消息的处理都比较相似也更容易理解,对网络协议理解不够其中路由、代理、通道一些相关实现看起来比较吃力。执行完这个拦截器之后就是开发者自己添加的网络拦截器了之后是最有一个拦截器 CallServerInterceptor 设计的很精妙
CallServerInterceptor
class CallServerInterceptor(private val forWebSocket: Boolean) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
// 上一个拦截器中创建的 Exchange 对象
val exchange = realChain.exchange!!
val request = realChain.request
val requestBody = request.body
// 发送请求的时间
val sentRequestMillis = System.currentTimeMillis()
// 使用 exchange 对象写请求头
exchange.writeRequestHeaders(request)
var invokeStartEvent = true
var responseBuilder: Response.Builder? = null
// 如果需要些请求体并且请求体不为空
if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
// 通知服务器客户端要发送一个体积可能很大的消息体,在服务器回复 100 后再发送请求体避免发送了不能处理造成浪费
if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
exchange.flushRequest()
// 读取响应头
responseBuilder = exchange.readResponseHeaders(expectContinue = true)
exchange.responseHeadersStart()
invokeStartEvent = false
}
// 不管 http1/2 如果响应码是 100 responseBuilder 都为 null 继续发送请求体
if (responseBuilder == null) {
if (requestBody.isDuplex()) {
exchange.flushRequest()
val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
requestBody.writeTo(bufferedRequestBody)
} else {
val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
requestBody.writeTo(bufferedRequestBody)
bufferedRequestBody.close()
}
} else {
exchange.noRequestBody()
// 如果不是 http2 请求标记不能复用
if (!exchange.connection.isMultiplexed) {
exchange.noNewExchangesOnConnection()
}
}
} else {
// 如果不需要响应体 get head 方法流程结束
exchange.noRequestBody()
}
if (requestBody == null || !requestBody.isDuplex()) {
exchange.finishRequest()
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
invokeStartEvent = false
}
}
var response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
// 记录请求发送和接收响应时间,这两个值的差就是一个请求的时长
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
var code = response.code
if (code == 100) {
// 这里处理了一个例外的情况就是客户端 header 没有 100-continue 字段
// 但是服务端返回 100 此时仍然把字段里面的值读取出来
responseBuilder = exchange.readResponseHeaders(expectContinue = false)!!
if (invokeStartEvent) {
exchange.responseHeadersStart()
}
response = responseBuilder
.request(request)
.handshake(exchange.connection.handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build()
code = response.code
}
exchange.responseHeadersEnd(response)
response = if (forWebSocket && code == 101) {
// 如果是 webSocket 请求并且服务器返回 101 协议升级之后使用 webSocket 协议收发消息
// http 是一种半双工的通信模式,请求-应答是主要的工作方式只能客户端主动请求
// 在一些实时性要求比较高的场景就会使用轮询
// WebSocket 是一个真正“全双工”的通信协议,一旦服务器有新的数据,就可以立即“推送”给客户端,
// 不需要客户端轮询,“实时通信”的效率也就提高了
response.newBuilder()
.body(EMPTY_RESPONSE)
.build()
} else {
// 默认 http 请求读取响应
response.newBuilder()
.body(exchange.openResponseBody(response))
.build()
}
// http/1 默认开启长连接如果服务器返回 Connection:close 表示想要关闭这个连接不再能复用
if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
"close".equals(response.header("Connection"), ignoreCase = true)) {
exchange.noNewExchangesOnConnection()
}
// 204/5 状态码表示没有响应体但是返回了响应体抛出异常
if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
throw ProtocolException(
"HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
}
// 否则就返回响应一个个拦截器回去
return response
}
}
不管是同步请求还是异步请求之后会都调用 okhttp3.Dispatcher#finished 从列表中移除这个 Call 对象并调用 promoteAndExecute 去执行其他请求。总结来说 okhttp 是对 http 协议的代码实现,看懂代码的前提是有基本的网络协议知识,在看源码过程中也能感受到网络协议知识还是不够