OkHttp源码解析

159 阅读15分钟

阅读了 OkHttp 的源码,记录一下自己的总结; 如果存在问题,欢迎指出;

OkHttpClient

作为 Okhttp 的发送客户端,负责将所有请求发送出去并拿到对应的响应数据

OkHttpClient.Builder 很明显的 构建者模式,通过该模式可以设置 Client 的各项配置:

  • 超时时间
  • 添加拦截器
  • 指定连接池等
 val client = OkHttpClient.Builder()
     .addInterceptor { chain ->
         val req = chain.request()
         println("Logging[send request]: ${req.url}")
         val response = chain.proceed(req)
         println("Logging[receive response]: ${req.url}")
         response
     }
     .readTimeout(Duration.of(10L, ChronoUnit.SECONDS))
     .writeTimeout(Duration.of(10L, ChronoUnit.SECONDS))
     .retryOnConnectionFailure(true)
     .build()

RealCall

对应一个请求的所有操作以及相关的数据;

  • 连接池、超时控制、取消操作、发送的操作等

所有的请求,无论是同步的还是异步的,都需要封装为 RealCall;

超时控制

timeout字段实现了一个计时器

// AsyncTimeout
 private val timeout = object : AsyncTimeout() {
     override fun timedOut() {
         cancel()
     }
 }.apply {
     timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)
 }

多个 AsyncTimeout 会以 单链表 的形式按照 距离超时的剩余时间 进行排序:

// AsyncTimeout
 private var head: AsyncTimeout? = null // 静态变量

会有一个看门狗 WatchDog 的守护线程检测该条单链表上面是否存在超时;如果超时了由该线程负责调用对应的 timeout 方法;

AsyncTimeout#enter 开始计时,最终调用该静态方法 AsyncTimeout#scheduleTimeout

  • 若未启动看门狗线程,则启动;
  • 将当前的 AsyncTimeout 实例按照超时时间插入到单链表中
 private fun scheduleTimeout(
     node: AsyncTimeout, 
     timeoutNanos: Long, 
     hasDeadline: Boolean
 ) {
     AsyncTimeout.lock.withLock {
         check(!node.inQueue) { "Unbalanced enter/exit" }
         node.inQueue = true
         
         // 没有头部节点,说明当前没有需要计时,看门狗线程还未启动;
         if (head == null) {
             head = AsyncTimeout() // 这里构建了一个头部,与实际的超时节点没有关系
             Watchdog().start()  // 只是用来判断看门狗线程是否启动
         }
         
         // 计算出超时的时间点
         val now = System.nanoTime()
         if (timeoutNanos != 0L && hasDeadline) {
             // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap
             // around, minOf() is undefined for absolute values, but meaningful for relative ones.
             node.timeoutAt = now + minOf(timeoutNanos, node.deadlineNanoTime() - now)
         } else if (timeoutNanos != 0L) {
             node.timeoutAt = now + timeoutNanos
         } else if (hasDeadline) {
             node.timeoutAt = node.deadlineNanoTime()
         } else {
             throw AssertionError()
         }
         
         // 按照距离超时的剩余时间插入到单链表中
         val remainingNanos = node.remainingNanos(now)
         var prev = head!!
         while (true) {
             if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {
                 node.next = prev.next
                 prev.next = node
                 // 如果是需要插入最前面,需要更新看门狗线程当前检测的节点
                 if (prev === head) {
                     // Wake up the watchdog when inserting at the front.
                     condition.signal()
                 }
                 break
             }
             prev = prev.next!!
         }
     }
 }

AsyncTimeout#exit 为退出计时,最终调用的是该方法: AsyncTimeout#cancelScheduledTimeout:

  • AsyncTimeout 从单链表中移除
 private fun cancelScheduledTimeout(node: AsyncTimeout): Boolean {
     AsyncTimeout.lock.withLock {
         if (!node.inQueue) return false
         node.inQueue = false
         // Remove the node from the linked list.
         var prev = head
         while (prev != null) {
             if (prev.next === node) {
                 prev.next = node.next
                 node.next = null
                 return false
             }
             prev = prev.next
         }
         // The node wasn't found in the linked list: it must have timed out!
         return true
     }
 }

接下来了解一下看门狗线程是如何检测的:

 private class Watchdog internal constructor() : Thread("Okio Watchdog") {
     init {
         isDaemon = true
     }
     override fun run() {
         while (true) {
             try {
                 var timedOut: AsyncTimeout? = null
                 AsyncTimeout.lock.withLock {
                     // 等待超时,awaitTimeout 会堵塞当前线程的执行
                     // 当 awaitTimeout 执行返回结果时,timeOut就是需要执行超时操作的节点
                     timedOut = awaitTimeout()
 ​
                     // 当队列里面没有节点了,需要退出当前的看门狗线程
                     // 当下次调用 `scheduleTimeout`的时候重新启动看门狗线程
                     if (timedOut === head) {
                         head = null
                         return
                     }
                 }
                 // 
                 timedOut?.timedOut()
             } catch (ignored: InterruptedException) {
             }
         }
     }
 }

如何等待超时?

 // AsyncTimeout
 internal fun awaitTimeout(): AsyncTimeout? {
     // Get the next eligible node.
     val node = head!!.next
     // 当前单链表里面没有节点,则等待指定时间 IDLE_TIMEOUT_MILLIS
     if (node == null) {
         val startNanos = System.nanoTime()
         // 使用 condition 堵塞指定时间
         condition.await(IDLE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
         return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {
             head // 如果等待了还是没有新的节点插入,那么返回 head
             // 根据上面看门狗的判断,当前看门狗线程会退出
         } else {
             null // 已经有新的节点插入
         }
     }
     
     // 拿到该节点的剩余时间
     var waitNanos = node.remainingNanos(System.nanoTime())
     // 等待指定的时间,在下次循环调用该方法的时候就会执行下面的移除链表
     if (waitNanos > 0) {
         condition.await(waitNanos, TimeUnit.NANOSECONDS)
         return null
     }
     // 当前节点已经超时
     head!!.next = node.next
     node.next = null
     return node
 }

取消操作

cancel 方法:

 override fun cancel() {
     if (canceled) return // Already canceled.
     canceled = true
     // 取消连接,关闭和服务器的连接
     exchange?.cancel()
     connectionToCancel?.cancel()
     eventListener.canceled(this)
 }

Exchange 和 ExchangeCodec

ExchangeCodec是一个接口,根据不同的协议来编码 Http 请求以及解码 Http 响应;

实现了该接口的有:

  • Http1ExchangeCodec:使用 Http/1.1 协议
  • Http2ExchangeCodec:使用 Http/2 中的帧

Exchange 是 ExchangeCodec 的上层封装,将不同协议的 ExchangeCodec 统一,提供相同的方法调用;


Client 同步发送请求流程

Request 是网络请求的封装请求,也是使用了 构建者模式 来创建一个 Request 实例;

简单的 同步发送

 val request = Request.Builder()
     .url("https://www.baidu.com")
     .get()
     .build()
 val response = client.newCall(request)
     .execute()

OkHttp需要发送一个请求,需要调用 client 的 newCall 封装为 RealCall 对象,并通过 execute 发送请求;

同步的方式发送请求就是直接调用其 execute 方法,在当前线程中执发送请求:

 // RealCall#execute
 override fun execute(): Response {
     // 设置:执行中
     check(executed.compareAndSet(false, true)) { "Already Executed" }
     // 开始计时
     timeout.enter()
     callStart()
     try {
         // 交给 dispatch 去直接 “执行”
         client.dispatcher.executed(this)
         // 通过责任链处理 request 并拿到响应
         return getResponseWithInterceptorChain()
     } finally {
         // 从队列中移除
         client.dispatcher.finished(this)
     }
 }
  • 这个 callStart 调用 eventListener.callStart ,在 OkHttpClient 构建过程中,允许传入 eventListener 或者是 eventListenerFactory ,能够允许在请求某些事件发生时进行回调;
  • 请求是直接交给 client.dispatcher ”执行“,并不会像异步那样先入队等待执行;(此处执行并不是真正的执行,仅仅是加入了一个运行中的队列)
  • getResponseWithInterceptorChain() 将请求经过拦截器链处理后发送,拿到响应后再回来,此处分析在下面的拦截链部分

继续往下看请求是怎么执行的:

  • 将请求 call 加入到正在执行的同步请求队列中
 // Dispatcher
 
 // 正在运行的同步请求,包括没有执行结束的请求
 private val runningSyncCalls = ArrayDeque<RealCall>()
 
 
 @Synchronized internal fun executed(call: RealCall) {
   runningSyncCalls.add(call)
 }

诶,只是加入了这个队列,但是并没有执行呀?

  • 其实具体的执行是在后面的 getResponseWithInterceptorChain 方法里面;

那为什么还要加入这个队列里面呢?

  • 方便统一管理啦,将所有请求(正在执行的同步请求、异步请求、等待执行的请求)集中在一个地方(Dispatcher);

    当需要取消的时候,就能够对这些请求直接取消了

想想,如果在一个子线程同步发送一个请求,此时应用退出:

如果不没有放在Dispatcher中,那么这个请求就没法call掉,除非在子线程中进行判断应用是否被 kill 掉,这是一件很麻烦的事情;

从上面可以知道,最终的执行过程就是调用 RealCall#getResponseWithInterceptorChain 方法;


Client异步发送请求分析

上面说完同步,接下来就到异步发送的方式了,同样 Request 异步发送也是很简单的,只需要调用 enqueue 方法并传入对应的响应处理回调即可;

 fun main() {
     val request = Request.Builder()
         .url("https://www.baidu.com")
         .get()
         .build()
 ​
     client.newCall(request)
         .enqueue(object: Callback {
             override fun onFailure(call: Call, e: IOException) {
                 println("failure, ${e.message}")
             }
 ​
             override fun onResponse(call: Call, response: Response) {
                 println("response: ${response.code}")
             }
         })
 }
 ​

此时 enqueue 就是关键入口:

 // RealCall
 override fun enqueue(responseCallback: Callback) {
     check(executed.compareAndSet(false, true)) { "Already Executed" }
     // 事件通知 callStart
     callStart()
     // 入队等待执行
     client.dispatcher.enqueue(AsyncCall(responseCallback))
 }

此时将 responseCallback 封装成了 AsyncCall

  • 这个类是 RealCall 的内部类

     internal inner class AsyncCall(
       private val responseCallback: Callback
     ) : Runnable 
    
  • 实现了 Runnable 接口,那么重点肯定是它的 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 {
                     // 将IO异常结果回调
                     responseCallback.onFailure(this@RealCall, e)
                 }
             } catch (t: Throwable) {
                 // 非 IO 异常结果的先取消执行
                 cancel()
                 if (!signalledCallback) {
                     val canceledException = IOException("canceled due to $t")
                     canceledException.addSuppressed(t)
                     responseCallback.onFailure(this@RealCall, canceledException)
                 }
                 throw t
             } finally {
                 // 从正在执行的异步队列中移除
                 client.dispatcher.finished(this)
             }
         }
     }
    

RealCall 被封装成一个带有回调 Callback 的异步 AsyncCall

接下来就是 client.dispatcher 的入队 enqueue 操作了

熟悉 dispatcher 的都知道,负责分发队列中的内容给不同的执行者执行,这类执行者一般都是线程池之类的

 // Dispatcher
 // 所有等待执行的异步请求队列
 private val readyAsyncCalls = ArrayDeque<AsyncCall>()
 ​
 internal fun enqueue(call: AsyncCall) {
     synchronized(this) {
         // 入队,但是还没有执行
         readyAsyncCalls.add(call)
         
         // 对于相同域名的访问是有限制的,callsPerHost 是表示当前对该域名的请求数量
         if (!call.call.forWebSocket) {
             // 这个 callsPerHost 是一个 AtomicInteger;
             // 这里的操作就是将相同域名的 callsPerHost 共享
             // 即全部相同的域名的 call 都会使用同一个 callsPerHost
             val existingCall = findExistingCallWithHost(call.host)
             if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
         }
     }
     // 看名字就知道是执行的意思了
     promoteAndExecute()
 }
  • 上面需要有一个点可以注意,就是 call.reuseCallsPerHostFrom(existingCall) 这个操作是将相同域名的 callsPerHost 共享,因此可以保证每个请求的最大数量是相同的;

接下来看 promoteAndExecute 如何执行的:

  • 将可以执行的请求拿出来到 executableCallsrunningAsyncCalls,什么是可以执行的呢?

    1. 正在执行的请求数目未达到上限 maxRequests
    2. 同一域名的请求数量未达到上限 maxRequestsPerHost
  • 最终将 executableCalls 的所有请求都调用 asyncCall.executeOn(executorService)

 // Dispatcher
 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()
             // 达到请求上限,退出
             if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
             // 达到同一域名的请求上限,跳过
             if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.
             i.remove()
             // 这里将同一域名的请求数量+1,结合上面的 enqueue 部分理解
             asyncCall.callsPerHost.incrementAndGet()
             executableCalls.add(asyncCall)
             runningAsyncCalls.add(asyncCall)
         }
         isRunning = runningCallsCount() > 0
     }
     // 交给 executorService 执行
     for (i in 0 until executableCalls.size) {
         val asyncCall = executableCalls[i]
         asyncCall.executeOn(executorService)
     }
     return isRunning
 }

执行点在于 Async.executeOn 方法,在此之前,先看看 executorService

 // Dispatcher
 @get:Synchronized
 @get:JvmName("executorService") val executorService: ExecutorService
   get() {
     if (executorServiceOrNull == null) {
       executorServiceOrNull = ThreadPoolExecutor(0, Int.MAX_VALUE, 60, TimeUnit.SECONDS,
           SynchronousQueue(), threadFactory("$okHttpName Dispatcher", false))
     }
     return executorServiceOrNull!!
   }

这就是一个线程池,没有核心线程,最多存活60s,结合 Async 是一个 Runnable,那么其 executeOn 方法就是将自己提交到这个线程池去执行

 // AsyncCall
 fun executeOn(executorService: ExecutorService) {
   client.dispatcher.assertThreadDoesntHoldLock()
   var success = false
   try {
       // 提交到线程池,最终调用的就是其 run 方法了
       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) // This call is no longer running!
     }
   }
 }

如果仔细思考下,好像存在一个问题:

promoteAndExecute 方法执行时,readyAsyncCall 因为请求数目达到上线或者域名请求达到上限而剩余一些请求未执行;

而这一步似乎只有 enqueue 才会调用,那是不是接下来不发送请求的时候会导致剩余的请求无法被执行?

NoNoNo,注意上面的执行代码,同步的 executeAsyncCall中的executeOnrun 方法都会有一个 finally块,保证了每个请求无论失败与否,都会执行内部的方法:

 client.dispatcher.finished(this)

最终调用的是这个方法:

  • 每次一个请求结束之后,都会调用一次 promoteAndExecute,这就保证了上面情况下剩余的请求能够执行
  • 同时还会判断队列是否空闲了,如果空闲了就会执行 idleCallback(这个 idleCallback 可以自定义 Diapatcher 来设置,并传给 OkHttpClient)
 // Dispatcher
 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
     }
     val isRunning = promoteAndExecute()
     if (!isRunning && idleCallback != null) {
         idleCallback.run()
     }
 }

请求的发送流程

上面讲完同步和异步的发送流程,发现最终发送请求并获得响应的核心方法是 getResponseWithInterceptorsChain

这里不涉及拦截器责任链如何进行的讲解,只关注发送过程中经历了哪些处理

 // RealCall: 省略部分代码
 @Throws(IOException::class)
 internal fun getResponseWithInterceptorChain(): Response {
   // Build a full stack of interceptors.
   val interceptors = mutableListOf<Interceptor>()
   interceptors += client.interceptors
   interceptors += RetryAndFollowUpInterceptor(client)
   interceptors += BridgeInterceptor(client.cookieJar)
   interceptors += CacheInterceptor(client.cache)
   interceptors += ConnectInterceptor
   if (!forWebSocket) {
     interceptors += client.networkInterceptors
   }
   interceptors += CallServerInterceptor(forWebSocket)
     
   val chain = RealInterceptorChain(...)
   var calledNoMoreExchanges = false
   try {
       
     val response = chain.proceed(originalRequest)
     return response
   }
 }

从上面组装拦截链的代码可以看到拦截器可以分为以下:

  • client.interceptors:应用拦截器,可以拿到原始的请求,执行添加一些header、参数加密等操作
  • RetryAndFollowUpInterceptor:重试和重定向拦截器,处理错误重试和重定向
  • BridgeInterceptor:应用层和网络层的桥接拦截器,给请求加上通用的请求头、添加cookie;存储响应结果的 cookie,如果响应经过 gzip 压缩则解压处理;
  • CacheInterceptor:缓存拦截器,如果请求命中缓存,则直接返回缓存而不发起网络请求
  • ConnectInterceptor:连接拦截器,内部会维护一个连接池,负责连接复用、创建连接(三次握手等等)、释放连接以及创建连接上的socket流。
  • networkInterceptors:自定义的网络拦截器,通常用于监控网络层的数据传输。
  • CallServerInterceptor:请求拦截器,将请求发送出去,真正的网络请求

RetryAndFollowUpInterceptor

通过while 循环控制错误重试以及重定向:

  • 对于错误重试:如果出现异常,会调用 recover 方法判断是否能够重试;对于无法重试的请求则直接抛出异常;
  • 对于重定向:会将前一轮的响应保存下来,按照重定向返回的信息继续发送请求;

什么时候停止?

  • 对于错误重试:在内部未看到相关次数的控制,先猜测是外部的 timeout 计时器判断超时后强制取消
  • 对于重定向:每次重定向发送请求都会增加 followUpCount 值,当这个值大于 MAX_FOLLOW_UPS (20)后,就会抛出异常停止重定向;
 class RetryAndFollowUpInterceptor(private val client: OkHttpClient) : Interceptor {
 ​
     // 省略大部分代码
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
         // 重定向次数
         var followUpCount = 0
         // 前一轮循环的响应结果
         var priorResponse: Response? = null
         // 重试过程中遇到的异常  
         var recoveredFailures = listOf<IOException>()
         // 循环执行:
         while (true) {
             var response: Response
             try {
                 // 外部调用 canncel,终止重试
                 if (call.isCanceled()) {
                     throw IOException("Canceled")
                 }
                 try {
                     // 拿到响应结果
                     response = realChain.proceed(request)
                 } catch (e: RouteException) {
                     // recover omit...
                     recoveredFailures += e.firstConnectException
                     continue
                 } catch (e: IOException) {
                     // recover omit...
                     recoveredFailures += e
                     continue
                 }
                 // 判断是否需要重定向
                 val followUp = followUpRequest(response, exchange)
                 // 无需重定向,直接返回结果
                 if (followUp == null) {
                       ...
                      return response
                 }
                 // isOneShot 判断该请求是否最多只能传输一次
                 val followUpBody = followUp.body
                 if (followUpBody != null && followUpBody.isOneShot()) {
                   closeActiveExchange = false
                   return response
                 }
 ​
                 response.body?.closeQuietly()
 ​
                 if (++followUpCount > MAX_FOLLOW_UPS) {
                   throw ProtocolException("Too many follow-up requests: $followUpCount")
                 }
 ​
                 request = followUp
                 priorResponse = response
             } finally {
                 call.exitNetworkInterceptorExchange(closeActiveExchange)
             }
         }
     }
 }

BridgeInterceptor

主要是给请求头添加 Cookie、请求头

 if (body != null) {
     // 请求体的内容类型
     val contentType = body.contentType()
     if (contentType != null) {
         requestBuilder.header("Content-Type", contentType.toString())
     }
     // 请求体的内容长度
     val contentLength = body.contentLength()
     if (contentLength != -1L) {
         requestBuilder.header("Content-Length", contentLength.toString())
         requestBuilder.removeHeader("Transfer-Encoding")
     } else {
         requestBuilder.header("Transfer-Encoding", "chunked")
         requestBuilder.removeHeader("Content-Length")
     }
 }
 // 域名、主机名
 if (userRequest.header("Host") == null) {
     requestBuilder.header("Host", userRequest.url.toHostHeader())
 }
 // 是否为长连接(TCP复用)
 if (userRequest.header("Connection") == null) {
     requestBuilder.header("Connection", "Keep-Alive")
 }
 ​
 // 允许接收 gzip 压缩后的内容,在后面也会负责该部分的解压
 var transparentGzip = false
 if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
       transparentGzip = true
       requestBuilder.header("Accept-Encoding", "gzip")
 }
 ​
 // Cookie操作,如果有则添加上
 val cookies = cookieJar.loadForRequest(userRequest.url)
 if (cookies.isNotEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies))
 }
 ​
 // UA
 if (userRequest.header("User-Agent") == null) {
      requestBuilder.header("User-Agent", userAgent)
 }

从上面可以看到,除了请求体的内容类型会被强制重写外,其他的请求头都会判断是否已经存在再去添加,那么说明我们可以在前面自定的拦截器中添加自定义的请求头而不会被覆盖;

接下来就是对响应的处理:

 val networkResponse = chain.proceed(requestBuilder.build())
 // 将 Cookie 保存下里
 cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
 val responseBuilder = networkResponse.newBuilder()
     .request(userRequest)
 ​
 // 负责 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()))
   }
 }

CacheInterceptor

缓存拦截器,这部分的缓存需要传递一个 Cache 来实现,默认的 OkHttpClient 构建并不会对赋值,即不会进行缓存;

对于缓存操作:

  • 先从 cache 取出缓存,通过 CacheStrategy 来计算是否需要进行网络请求还是使用缓存;

  • 如果使用缓存,那么直接将缓存的响应结果返回;

  • 如果需要网络请求,那么会将响应的结果更新到缓存中(对于一些特殊情况不会更新,详细看下面)

 @Throws(IOException::class)
 override fun intercept(chain: Interceptor.Chain): Response {
     val call = chain.call()
     // 取出url对应的缓存
     val cacheCandidate = cache?.get(chain.request())
     val now = System.currentTimeMillis()
     
     // 根据已有的请求和缓存数据判断是否进行网络请求
     val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
     // 判断结果
     val networkRequest = strategy.networkRequest
     val cacheResponse = strategy.cacheResponse
     
     cache?.trackResponse(strategy)
     val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE
     
     // 存在缓存,但判断结果为空,则代表这个缓存不能使用
     if (cacheCandidate != null && cacheResponse == null) {
         cacheCandidate.body?.closeQuietly()
     }
   
     // 如果禁止使用网络请求(要求使用缓存),同时没有缓存,返回一个错误的Response
     if (networkRequest == null && cacheResponse == null) {
          return Response.Builder()
             .request(chain.request())
             .protocol(Protocol.HTTP_1_1)
             .code(HTTP_GATEWAY_TIMEOUT)
             .message("Unsatisfiable Request (only-if-cached)")
             .body(EMPTY_RESPONSE)
             .sentRequestAtMillis(-1L)
             .receivedResponseAtMillis(System.currentTimeMillis())
             .build().also {
               listener.satisfactionFailure(call, it)
             }
     }
   
     // 命中缓存,直接返回缓存
     if (networkRequest == null) {
         return cacheResponse!!.newBuilder()
             .cacheResponse(stripBody(cacheResponse))
             .build().also {
               listener.cacheHit(call, it)
             }
     }
     if (cacheResponse != null) {
         listener.cacheConditionalHit(call, cacheResponse)
     } else if (cache != null) {
         listener.cacheMiss(call)
     }
     
     // 需要发送网络请求
     var networkResponse: Response? = null
     try {
         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) {
           cacheCandidate.body?.closeQuietly()
         }
     }
       // If we have a cache response too, then we're doing a conditional get.
     if (cacheResponse != null) {
         // HTTP_MOT_MODIFIED 表示服务器资源没有修改,可以直接使用缓存
         if (networkResponse?.code == HTTP_NOT_MODIFIED) {
             val 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.also {
                 listener.cacheHit(call, it)
             }
         } else {
             cacheResponse.body?.closeQuietly()
         }
     }
    
     val response = networkResponse!!.newBuilder()
         .cacheResponse(stripBody(cacheResponse))
         .networkResponse(stripBody(networkResponse))
         .build()
     // 更新缓存操作
     if (cache != null) {
         if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
             // Offer this request to the cache.
             val cacheRequest = cache.put(response)
             return cacheWritingResponse(cacheRequest, response).also {
                 if (cacheResponse != null) {
                     // This will log a conditional cache miss only.
                     listener.cacheMiss(call)
                 }
             }
         }
         // 请求方法不支持缓存,那么将缓存移除
         if (HttpMethod.invalidatesCache(networkRequest.method)) {
             try {
                 cache.remove(networkRequest)
             } catch (_: IOException) {
                 // The cache cannot be written.
             }
         }
     }
     return response
 }

ConnectInterceptor

连接拦截器,用于打开和目标服务器的连接

exchange 是用于发送请求和接收响应的上层封装,其对 ExchangeCodec (有不同协议的实现)统一外部的接口;

 object ConnectInterceptor : Interceptor {
     @Throws(IOException::class)
     override fun intercept(chain: Interceptor.Chain): Response {
         val realChain = chain as RealInterceptorChain
         val exchange = realChain.call.initExchange(chain)
         val connectedChain = realChain.copy(exchange = exchange)
         return connectedChain.proceed(realChain.request)
     }
 }
 ​

关键点就是这个 initExchange

 internal fun initExchange(chain: RealInterceptorChain): Exchange {
     // 检查一些参数,
     synchronized(this) {
         // 本次请求有更多的交换信息,默认是true
         check(expectMoreExchanges) { "released" }
         // 响应体有没有被读取中
         check(!responseBodyOpen) 
         // 请求体有没有被读取中
         check(!requestBodyOpen)
     }
     val exchangeFinder = this.exchangeFinder!!
     val codec = exchangeFinder.find(client, chain)
     val result = Exchange(this, eventListener, exchangeFinder, codec)
     this.interceptorScopedExchange = result
     this.exchange = result
     synchronized(this) {
         this.requestBodyOpen = true
         this.responseBodyOpen = true
     }
     if (canceled) throw IOException("Canceled")
     return result
 }

首先是检查了一些参数:

  • expectMoreExchanges 默认是true,当请求结束之后,就会将其置为 false;这里的检查应该是判断该请求是否已经发送过;
  • responseBodyOpenrequestBodyOpen 都是请求体或者响应体是否读取,置为 true 的位置仅有下面的同步块中执行;检查避免多次发送

通过 exchangeFinder(这是一个从连接池中查找可用连接的对象)找到一个链接

graph LR
A("ExchangeFinder#find") --> B("ExchangeFinder#findHealthyConnection")
B --> C("ExchangeFinder#findConnection")
  • findConnection 这一步,他会尝试复用当前 Call 的连接:如果没有释放(关闭)连接,那么就会直接复用;
  • 否则就会在 connectionPool 中查找是否有可用的连接:connectionPool.callAcquirePooledConnection
  • 如果没有可用链接,则需要创建一个新的连接 val newConnection = RealConnection(connectionPool, route) 同时会将其放入连接池中;
  • 拿到连接之后会封装为 ExchangeCodec 负责数据的传输(根据不同的协议实现)

拿到的新连接会调用其 connect 进行三次握手以及 TLS 等验证操作,建立和服务器的连接

最后封装为 Exchange 放入 链中,交给接下来的自定义 NetworkInteceptors 处理

CallServerInterceptor

真正发送请求的地方;

大致的流程:(会先构造出一个 responseBuilder

  1. exchange.writeRequestHeaders(request) 将请求头写入连接,http/1.1的实现为 sink(BufferedSink),http/2的实现为 stream( Http2Stream,内部是一个 FramingSink)

    • 对于请求头中存在 "Expect: 100-continue",那么会将写入进去的请求头先发送,等待服务器响应结果;
    • 除了上面的情况外,将请求体写入:requestBody.writeTo(bufferedRequestBody)
  2. 开始等待读取响应头:exchange.readResponseHeaders,生成一个新的响应:

     response = responseBuilder
         .request(request)
         .handshake(exchange.connection.handshake())
         .sentRequestAtMillis(sentRequestMillis)
         .receivedResponseAtMillis(System.currentTimeMillis())
         .build()
    
  3. 随后开始读取响应体:

     response.newBuilder()
         .body(exchange.openResponseBody(response))
         .build()
    

其余的是一些针对不同的响应码以及特殊情况进行处理,比如检查响应头是否需要保持连接:

 if ("close".equals(response.request.header("Connection"), ignoreCase = true) ||
     "close".equals(response.header("Connection"), ignoreCase = true)) {
     exchange.noNewExchangesOnConnection()
 }

拦截链的责任链设计模式

OkHttp的拦截器接口 Interceptor 中同时还包含了 Chain 接口的声明:

  • Interceptor 是一个函数式接口:

    fun interface Interceptor {
      @Throws(IOException::class)
      fun intercept(chain: Chain): Response
     
        // omit..
    }
    

    intercept 就是拦截器的核心处理方法

  • 内部声明了 Chain 接口:

    interface Chain {
        @Throws(IOException::class)
        fun proceed(request: Request): Response
        // 省略其他方法
    }
    

    process 方法就是沿着责任链向下传递的关键

getResponseWithInterceptorChain

RealCall 也就是封装的网络请求类中, getResponseWithInterceptorChain 方法是将请求发送出去,经过拦截链后拿到响应结果;

拦截链的设置也是在此处完成的,从左到右分别是

  • OkHttpClient设置的自定义拦截器
  • 重定向拦截器 RetryAndFollowUpInterceptor
  • 桥拦截器 RetryAndFollowUpInterceptor:负责给请求添加所需的请求头,以及根据需要给拿到的响应进行解压;
  • 缓存拦截器 CacheInterceptor:负责HTTP缓存的处理;
  • 连接拦截器 ConnectInterceptor:负责建立与目标服务器的连接;
  • 发送请求拦截器:CallServerInterceptor最后一个拦截器,将请求发送出去;
// RealCall#getResponseWithInterceptorChain

val interceptors = mutableListOf<Interceptor>()
// OkHttpClient 构建时的应用
interceptors += client.interceptors
interceptors += RetryAndFollowUpInterceptor(client)
interceptors += BridgeInterceptor(client.cookieJar)
interceptors += CacheInterceptor(client.cache)
interceptors += ConnectInterceptor
if (!forWebSocket) {
  interceptors += client.networkInterceptors
}
interceptors += CallServerInterceptor(forWebSocket)

什么时候将请求传入?

// RealCall#getResponseWithInterceptorChain

val chain = RealInterceptorChain(
    call = this, 
    interceptors = interceptors,
    index = 0,
    exchange = null,
    request = originalRequest, // 需要发送的请求
    connectTimeoutMillis = client.connectTimeoutMillis,
    readTimeoutMillis = client.readTimeoutMillis,
    writeTimeoutMillis = client.writeTimeoutMillis
)

...

// 传入拦截器链,开始处理这个请求;
val response = chain.proceed(originalRequest)

究竟是怎么实现 Request 经过拦截器链处理后发送得到响应还能经过拦截器回来的呢?

  • 多个拦截器的一去一回,不难联想到递归;

  • 所有的拦截器,接收 Chain,相当于拿到 Request,通过 Chain.proceed 拿到对应的 Response

  • 那递归总得有一个起点和终点吧:上面的 chain.proceed 就是启动,而终点自然就是拦截链的最后一个拦截器 CallServerInterceptor

拦截链起点

RealInterceptorChain 是拦截链的起点,也是沿着拦截链传递的“驱动”;

内部保存了一整个拦截链 interceptors,当前请求在拦截器的位置 index;

从上面实例化看到其 index 的初始值为 0,也就是说从第一个拦截器开始调用;

override fun proceed(request: Request): Response {
  check(index < interceptors.size)
  ...
  // Call the next interceptor in the chain.
  val next = copy(index = index + 1, request = request)
  val interceptor = interceptors[index]
    
  val response = interceptor.intercept(next) ?: throw NullPointerException(
      "interceptor $interceptor returned null")
  
  ...
  return response
}

递归调用,每次调用 intercept 时都会指定下一个拦截器的位置 index + 1

在拦截器的 intercept 方法中会调用 chain.proceed(request) 即回到这处代码重复执行;

graph TB
A(RealInterceptorChain) --interceptors-0#intercept--> B(interceptors-0)
B --chain.proceed --> C(RealInterceptorChain-copy-1)
C --interceptors-0#intercept--> D(interceptor-1)
D --重复--> E(CallServerInterceptor)


拦截链终点

处于拦截链的最后一个拦截器 CallServerInterceptor,负责将请求发送出去,并接收其响应;

在其 intercept 方法,并没有调用 Chain.proceed 继续往下传递(因为已经是最后一个了);

此时获取响应并进行一次处理后,就会返回 Response 对象;

(此时就是递归过程中的 “归”)

graph TB
A("CallServerInterceptor") --"return response"--> B(RealInterceptorChain-copy-n)
B--"return response"-->C("中间的拦截器 response=chain.proceed(chain)")
C--"return response"--> D("RealInterceptorChain")

OKHttp的缓存实现

上面的请求发送流程中的 CacheInterceptor 拦截器负责缓存;

该拦截器需要传入一个 Cache 对象来实现缓存,以及使用 CacheStrategy 来判断是否使用缓存

Cache

实例化 Cache 对象需要指定缓存目录以及最大缓存大小;

 class Cache internal constructor(
   directory: File,
   maxSize: Long,
   fileSystem: FileSystem
 ) : Closeable, Flushable

其内部使用了 DiskLruCache 实现本地文件的缓存

  • 通过 journalFile 文件来记录缓存操作的日志(格式如下)

     libcore.io.DiskLruCache
     1
     100
     2
     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
     DIRTY 335c4c6028171cfddfbaae1a9c313c52
     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
     REMOVE 335c4c6028171cfddfbaae1a9c313c52
     DIRTY 1ab96a171faeeee38496d8b330771a7a
     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
     READ 335c4c6028171cfddfbaae1a9c313c52
     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
    

    其中一长串的字符串是每个请求url对应的 key 值(类似哈希值)

  • 其中维护了一系列文件,是:

     // 用于读的文件(没有被修改,所以是clean的)
     internal val cleanFiles = mutableListOf<File>()
     // 用于写的文件(已经被修改,所以是dirty脏的)
     internal val dirtyFiles = mutableListOf<File>()
    

主要还是看 Cache 怎么更新缓存:

  • 从 put 方法可以知道 OkHttp 仅支持 GET 方法的缓存,
 // Cache
 internal fun put(response: Response): CacheRequest? {
     val requestMethod = response.request.method
     // 判断是否为不合适缓存的请求方法
     if (HttpMethod.invalidatesCache(response.request.method)) {
         try {
             remove(response.request)
         } catch (_: IOException) {
             // The cache cannot be written.
         }
         return null
     }
     if (requestMethod != "GET") {
         // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some
         // POST requests, but the complexity of doing so is high and the benefit is low.
         return null
     }
     if (response.hasVaryAll()) {
         return null
     }
     // 将响应封装为 Entry,该类会以一种规定的标准写入到文件和读取数据
     val entry = Entry(response)
     var editor: .Editor? = null
     try {
         editor = cache.edit(key(response.request.url)) ?: return null
         entry.writeTo(editor)
         return RealCacheRequest(editor)
     } catch (_: IOException) {
         abortQuietly(editor)
         return null
     }
 }

里面有一个 response.hasVaryAll(),是对 Vary 响应头进行判断,判断其是否为 *

  • 对于 Vary 请求头,是用来验证请求头和缓存响应中的响应头是否匹配,从而决定是否使用缓存

  • 比如同一份数据有多种编码方式,在缓存服务器上如果不判断请求方的 Accept-Encoding 就可能导致返回请求方无法解码的数据,此时就需要 Vary 请求头来标识哪些请求头会对返回的响应产生影响,在这里 Vary: Accept-Encoding,缓存服务器就会匹配该头部是否匹配,如果匹配才会返回数据;

  • 对于 Vary: * 的请求,说明任何的头部字段都会对最终的响应产生影响,因此不能缓存;

最终写入到 DiskLruCache 中(使用LRU淘汰算法)

  • 会根据 key (url对应的唯一标识符)生成 DiskLruCache.Entry

     // 用来读的文件(没有修改过所以是clean的)
     internal val cleanFiles = mutableListOf<File>()
     // 用来写的文件(修改过所以是dirty的)
     internal val dirtyFiles = mutableListOf<File>()
    
    • 在初始化的时候会生成对应的文件:

       init {
           // The names are repetitive so re-use the same builder to avoid allocations.
           val fileBuilder = StringBuilder(key).append('.')
           val truncateTo = fileBuilder.length
           // 新建文件
           for (i in 0 until valueCount) {
               fileBuilder.append(i)
               // 没有 .tmp 的
               cleanFiles += File(directory, fileBuilder.toString())
               fileBuilder.append(".tmp")
               // 带有 .tmp 的
               dirtyFiles += File(directory, fileBuilder.toString())
               fileBuilder.setLength(truncateTo)
           }
       }
      
    • 写入响应时,会响应头和响应体分别放在两个不同的文件

       // Cache
       // 对应下标常量
       private const val ENTRY_METADATA = 0
       private const val ENTRY_BODY = 1
      

具体的 LRU 算法就不在这里展开了;


CacheStrategy

CacheInterceptor 中,使用 CacheStrategy 来判断是否使用缓存:

  • 需要传入当前时间、当前发送的请求 request 以及缓存的响应 response(可能为空)

     class Factory(
         private val nowMillis: Long,
         internal val request: Request,
         private val cacheResponse: Response?
     )
    
  • 返回一个 CacheStrategy 对象,内部分别有 networkRequestcachedResponse 两个属性,通过这两个属性是否为空来决定是否使用缓存

最终调用了 compute 方法,实际上是调用该方法:

  • CacheStrategy 的构造方法第一个是 networkRequest,第二个参数是 cachedResponse
 // CacheStrategy.Factory
 ​
 private fun computeCandidate(): CacheStrategy {
     // 没有缓存的方法
     if (cacheResponse == null) {
         return CacheStrategy(request, null)
     }
     // 没有握手拿到的响应可能有问题
     // Drop the cached response if it's missing a required handshake.
     if (request.isHttps && cacheResponse.handshake == null) {
         return CacheStrategy(request, null)
     }
     
     // 判断这个 cacheRequest 是否可以缓存
     // 1. 符合特定的响应码,比如200、204、203、301、404等
     // 2. 其请求头或响应头没有 `Cache-Control: no-store
     if (!isCacheable(cacheResponse, request)) {
         return CacheStrategy(request, null)
     }
     
     
     val requestCaching = request.cacheControl
     // 带有 `Cache-Control: no-cache` 或 `If-Modified-Since` `If-None-Match`
     // 这些头部信息都需要向服务器发送请求来校验是否有效
     if (requestCaching.noCache || hasConditions(request)) {
         return CacheStrategy(request, null)
     }
     
     // 检查是否换粗是否过期
     val responseCaching = cacheResponse.cacheControl
     val ageMillis = cacheResponseAge()
     var freshMillis = computeFreshnessLifetime()
     if (requestCaching.maxAgeSeconds != -1) {
         freshMillis = minOf(freshMillis, SECONDS.toMillis(requestCaching.maxAgeSeconds.toLong()))
     }
     var minFreshMillis: Long = 0
     if (requestCaching.minFreshSeconds != -1) {
         minFreshMillis = SECONDS.toMillis(requestCaching.minFreshSeconds.toLong())
     }
     var maxStaleMillis: Long = 0
     if (!responseCaching.mustRevalidate && requestCaching.maxStaleSeconds != -1) {
         maxStaleMillis = SECONDS.toMillis(requestCaching.maxStaleSeconds.toLong())
     }
     // 缓存没有过期
     if (!responseCaching.noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
         val builder = cacheResponse.newBuilder()
         if (ageMillis + minFreshMillis >= freshMillis) {
             builder.addHeader("Warning", "110 HttpURLConnection "Response is stale"")
         }
         val oneDayMillis = 24 * 60 * 60 * 1000L
         if (ageMillis > oneDayMillis && isFreshnessLifetimeHeuristic()) {
             builder.addHeader("Warning", "113 HttpURLConnection "Heuristic expiration"")
         }
         return CacheStrategy(null, builder.build())
     }
     // Find a condition to add to the request. If the condition is satisfied, the response body
     // will not be transmitted.
     // 根据 Cache-Control 不同的头部信息来设置对应的值
     val conditionName: String
     val conditionValue: String?
     when {
         etag != null -> {
             conditionName = "If-None-Match"
             conditionValue = etag
         }
         lastModified != null -> {
             conditionName = "If-Modified-Since"
             conditionValue = lastModifiedString
         }
         servedDate != null -> {
             conditionName = "If-Modified-Since"
             conditionValue = servedDateString
         }
         else -> return CacheStrategy(request, null) // No condition! Make a regular request.
     }
     // 将缓存的一些信息比如 ETag 或 Last-Modifited,添加到请求头中,和服务端进行校验
     val conditionalRequestHeaders = request.headers.newBuilder()
     conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)
     val conditionalRequest = request.newBuilder()
         .headers(conditionalRequestHeaders.build())
         .build()
     return CacheStrategy(conditionalRequest, cacheResponse)
 }