OKHttp 源码分析

OKHttp 是一个来自 Square 的 HTTP 客户端框架,用于 Java 和 Android 应用程序。它的设计是为了更快地加载资源并节省带宽。

OKHttp 源码地址:https://github.com/square/okhttp

OKHttp 的简单使用

下面让我们从一个简单的 Demo 来开始 OKHttp 的源码阅读。

class OKHttpGetDemo {
    private val client = OkHttpClient();

    fun run(url: String): String? {
        val request = Request.Builder()
            .url(url)
            .build()
        return client.newCall(request).execute().body?.use {
            it.string()
        }
    }
}

fun main() {
    val example = OKHttpGetDemo()
    val response = example.run("https://rwa.github.com/square/okhttp/master/README.md")
    println(response)
}

OKHttpClient

在使用 OKHttp 时,我们首先需要一个 OKHttpClient。它作为一个请求发起的 Client,保存了很多关于处理请求的配置。当然这里使用了 OKHttpClient 的无参构造,也就是说我们使用的都是默认的配置。 如果我们想要对 OKHttpClient 进行配置,那么就需要使用 OKHttpClient.Build 来配置 OKHttpClient。注意,这里使用到了建造者模式,这是因为 OKHttpClient 有很多参数。日常开发中,如果遇到一个类的构造需要很多参数的情况,也可以尝试使用建造者模式

Request

Request 类表示请求,可以看到这里也是用了建造者模式。

open class Builder {
  internal var url: HttpUrl? = null
  internal var method: String
  internal var headers: Headers.Builder
  internal var body: RequestBody? = null

  /** A mutable map of tags, or an immutable empty map if we don't have any. */
  internal var tags: MutableMap, Any> = mutableMapOf()
  // ...
}

想要发起一个请求,我们需要知道

  1. 请求的 url,
  2. 请求的方法,
  3. 请求头,
  4. 以及请求体(非必须)

RealCall

在创建好 Request 后,就可以通过 OKHttpClient 去发起请求,这里调用了下面的方法:

client.newCall(request).execute()

该方法最终会返回一个 Response 对象。

可以看到我们先通过 client.newCall(request) 得到了一个 Call ,那这个 Call 是什么呢?

A call is a request that has been prepared for execution. A call can be canceled. As this object represents a single request/response pair (stream), it cannot be executed twice.

Call 是一个已经准备好执行的 Request,一个 Call 实例能够被取消。该实例表示的是单个请求/响应(流),因此不能被执行两次。

也就是说 Call 会支持一个已经准备好的 Request 然后调用 execute 方法可以得到 Response

override fun newCall(request: Request): Call = RealCall(this, request, forWebSocket = false)

newCall 的方法如上所示。

最终又返回了一个 RealCall 对象。继续往下看看 RealCall 的 execute 方法的实现:

override fun execute(): Response {
  check(executed.compareAndSet(false, true)) { "Already Executed" }

  timeout.enter()
  callStart()
  try {
    client.dispatcher.executed(this)
    return getResponseWithInterceptorChain()
  } finally {
    client.dispatcher.finished(this)
  }
}

executed 是一个 AtomicBoolean 类型的一个对象,这里通过 CPS 来确保一个 RealCall 只能执行一次。这里的这个 CPS 的含义是将 executed 的值和 false 比较,如果比较通过则返回 更新为 true,并且返回 true,否则返回 false。

timeout.enter() 表示开始进入超时的倒计时。

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

接下来看看 callStart 方法:

private fun callStart() {
  this.callStackTrace = Platform.get().getStackTraceForCloseable("response.body().close()")
  eventListener.callStart(this)
}

也可以使用 RealCall 进行异步请求。下面我们来看一个异步请求的 Demo。

class OKHttpPostDemo {  
    private val client = OkHttpClient();  
  
    fun run(url: String)  {  
        val request = Request.Builder()  
            .url(url)  
            .build()  
        client.newCall(request).enqueue(object : Callback {  
            override fun onFailure(call: Call, e: IOException) {  
                throw e  
            }  
  
            override fun onResponse(call: Call, response: Response) {  
                println(response.body?.string())  
            }  
  
        })  
    }  
}  
  
fun main() {  
    val example = OKHttpPostDemo()  
    example.run("https://rwa.github.com/square/okhttp/master/README.md")  
  
}

接下来我们来看看异步的 enquene 方法:

override fun enqueue(responseCallback: Callback) {
  // 检查当前请求是否正在执行
  check(executed.compareAndSet(false, true)) { "Already Executed" }  
  
  callStart()  
  client.dispatcher.enqueue(AsyncCall(responseCallback))  
}

Dispatcher 的意思是调度或者分发,内部维护了一个请求的队列,而这里 enqueue 方法就像它的名字一样,会将当前的请求压入队列中。

Platform

可以看到 callStart 方法中调用了 Platform 类的方法。Platform 是将一些与平台相关的代码抽取到了这个类中。

Platform 类现在又如下的实现:

image.png

OKHttp 中对 Platform 分为了两类,Andoird 和 JVM。这里以 JVM 为例。

private fun findJvmPlatform(): Platform {

  if (isConscryptPreferred) {

    val conscrypt = ConscryptPlatform.buildIfSupported()

    if (conscrypt != null) {

      return conscrypt

    }

  }

  if (isBouncyCastlePreferred) {

    val bc = BouncyCastlePlatform.buildIfSupported()

    if (bc != null) {

      return bc

    }

  }

  if (isOpenJSSEPreferred) {

    val openJSSE = OpenJSSEPlatform.buildIfSupported()

    if (openJSSE != null) {

      return openJSSE

    }

  }

  // An Oracle JDK 9 like OpenJDK, or JDK 8 251+.

  val jdk9 = Jdk9Platform.buildIfSupported()

  if (jdk9 != null) {

    return jdk9

  }

  // An Oracle JDK 8 like OpenJDK, pre 251.

  val jdkWithJettyBoot = Jdk8WithJettyBootPlatform.buildIfSupported()

  if (jdkWithJettyBoot != null) {

    return jdkWithJettyBoot

  }

  return Platform()

}

前面判断了一堆的内容,这里有对 JVM 版本的判断,以及其他的一些判断。我们假定最终返回了默认的 Platform ,他的 getStackTraceForClosable 方法如下:

open fun getStackTraceForCloseable(closer: String): Any? {

  return when {

    logger.isLoggable(Level.FINE) -> Throwable(closer) // These are expensive to allocate.

    else -> null

  }

}

判断为 log 的级别为 FINE,返回一个异常。这里的 Log 级别是 Java 中的 log 级别。

Java 中的 log 级别分的很多,每一个级别都对应一个值,值越高级别越大,从高到低依次是:

级别 描述
OFF
SEVERE 最高级别的日志,输出一些导致程序不能正常运行的信息
WARNING 警告
INFO 输出一些比较重要的信息
CONFIG 输出一些配置信息,例如 CPU 类型等
FINE 根据信息的日志级别
FINER 根据信息的日志级别
FINEST 根据信息的日志级别
ALL 全部输出

所以在 JVM 平台 callStackTrace 可能是 Throwable("response.body().close()") 或者 null。

callStart 方法接下来的一行是 eventListener.callStart(this)

EventListener

EventListener 是 OKHttp 中的事件监听器,监听了请求链路中的所有事件。我们可以通过 OKHttp.Build 来配置 EventListener。配置的方式有两种:

  • eventListenerFactory

  • eventListener

一个是配置一个 EventListener.Factory,一个是配置 EventListener

/**

  - Configure a single client scoped listener that will receive all analytic events for this

  - client.

  *

  - @see EventListener for semantics and restrictions on listener implementations.

  */

fun eventListener(eventListener: EventListener) = apply {

  this.eventListenerFactory = eventListener.asFactory()

}

/**

  - Configure a factory to provide per-call scoped listeners that will receive analytic events

  - for this client.

  *

  - @see EventListener for semantics and restrictions on listener implementations.

  */

fun eventListenerFactory(eventListenerFactory: EventListener.Factory) = apply {

  this.eventListenerFactory = eventListenerFactory

}

如果我们配置的是 EventListener 那么会在方法内部通过 asFactory() 方法转换成一个工厂对象。方法如下:

fun EventListener.asFactory() = EventListener.Factory { this }

工厂的方法如下:

fun interface Factory {

  /**

    - Creates an instance of the [EventListener] for a particular [Call]. The returned

    - [EventListener] instance will be used during the lifecycle of [call].

    *

    - This method is invoked after [call] is created. See [OkHttpClient.newCall].

    *

    - **It is an error for implementations to issue any mutating operations on the [call] instance

    - from this method.**

    */

  fun create(call: Call): EventListener

}

asFactory 方法是 Kotlin 的一个扩展方法,后面的 EventListener.Factory { this } 是通过 lambda 创建的一个工厂对象,每次调用这个工厂对象的 create 方法都会返回 this,也就是我们传入的 EventListener 对象。

所以,如果我们调用 eventListener 方法,那么后续通过该配置创建的 OKHttpClient 对象产生的请求都使用的是同一个 EventListener 实例。

**eventListenerFactory** 方法不同,需要我们传入一个工厂对象,在这个工厂对象的 **create** 方法中可以定义自已创建 **EventListener** 的逻辑。

每一个 RealCall 被创建处理的时候都会调用一次 eventListenerFactorycreate 方法。如下:

internal val eventListener: EventListener = client.eventListenerFactory.create(this)

callStart 是一个请求的第一个方法,从名字也可以看出来。

Dispatcher 任务分发

Dispatcher 是 OKHttp 中进行任务分发的一个类。我们来介绍一些 Dispatcher 类中重要属性:

  • readyAsyncCalls : 准备好执行的 call。
  • runningAsyncCalls: 正在运行的 call。
internal fun enqueue(call: AsyncCall) {  
  synchronized(this) {
    // 将 call 添加到 readyAsyncCalls 中
    readyAsyncCalls.add(call)  
  
    // Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to  
    // the same host.    
    if (!call.call.forWebSocket) {
      // 查找拥有相同 host 的 call  
      val existingCall = findExistingCallWithHost(call.host)  
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)  
    }  
  }
  promoteAndExecute()  
}

上面这个方法会将需要执行的 call 添加到准备队列中,并且会在 readyAsyncCallsrunningAsyncCalls 中查找能够复用的 call。

private fun findExistingCallWithHost(host: String): AsyncCall? {  
  for (existingCall in runningAsyncCalls) {  
    if (existingCall.host == host) return existingCall  
  }  
  for (existingCall in readyAsyncCalls) {  
    if (existingCall.host == host) return existingCall  
  }  
  return null  
}

上面的方法是从 readyAsyncCallsrunningAsyncCalls 中查找于给定 host 相同的 call。

@Volatile var callsPerHost = AtomicInteger(0)  
  private set  
  
fun reuseCallsPerHostFrom(other: AsyncCall) {  
  this.callsPerHost = other.callsPerHost  
}

这里的 reuseCallsPerHostFrom 可不是指复用 call,而是一个限流的作用,callPerHost 表示当前的 host 一共有多少的 call,也就是有多少个请求。

private fun promoteAndExecute(): Boolean {  
  this.assertThreadDoesntHoldLock()  
  
  val executableCalls = mutableListOf()  
  val isRunning: Boolean  
  synchronized(this) {
    val i = readyAsyncCalls.iterator()  
    while (i.hasNext()) {  
      val asyncCall = i.next()  
      // 如果 runningAsyncCalls 的大小已经达到了最大的请求数量
      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      // 这里就是前面提到的限制同一个 host 同时存在太多的请求
      if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue // Host max capacity.  
  
      i.remove()
      // callsPerhost + 1  
      asyncCall.callsPerHost.incrementAndGet()
      // 添加到正在执行的队列中  
      executableCalls.add(asyncCall)  
      runningAsyncCalls.add(asyncCall)  
    }  
    isRunning = runningCallsCount() > 0  
  }

  for (i in 0 until executableCalls.size) {  
    val asyncCall = executableCalls[i]
    // 在 executorService 上执行 call
    asyncCall.executeOn(executorService)  
  }  
  
  return isRunning  
}

这个方法会将符合条件的 call 从 readyAsyncCalls 迁移到 runningAsyncCalls,并执行这些 Call。promoteAndExecute 方法的返回值表示当前是否还有任务正在运行。 Call 的执行事通过 Call 的 executeOn 方法,executorService 是 OKhttp 的线程池。

@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!!  
  }

可以看到如果我们没有指定线程池,则会创建一个默认的线程池。

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) // This call is no longer running!  
    }  
  }  
}

上面代码中执行网络请求的代码是 executorService.execute(this)。也就是说 RealCall 是继承自 Runnable 的,网络请求的核心代码在 run 方法中。下面我们来看看 run 方法。

override fun run() {
    // threadName 方法会修改当前线程的名称,并执行 block 块中的代码
    threadName("OkHttp ${redactedUrl()}") {  
      var signalledCallback = false
      // 开始检测是否超时  
      timeout.enter()  
      try {
        // 获取 response
        val response = getResponseWithInterceptorChain()  
        signalledCallback = true  
        responseCallback.onResponse(this@RealCall, response)  
      } catch (e: IOException) {
        // 这里是防止有两次 callback  
        if (signalledCallback) {  
          // Do not signal the callback twice!  
          Platform.get().log("Callback failure for ${toLoggableString()}", Platform.INFO, e)  
        } else {
          // 有 IOException 表示请求失败  
          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 {
        // dispatcher 调用当前 call 执行完毕  
        client.dispatcher.finished(this)  
      }  
    }  
  }  
}

可以看到在 RealCall 的 run 方法中执行了真正的网络请求,并处理了请求的 callback。在执行完毕了调用了 dispatcher 的 finished 方法,表示当前 call 的执行结束了。

internal fun finished(call: AsyncCall) {  
  call.callsPerHost.decrementAndGet()  
  finished(runningAsyncCalls, call)  
}

private fun  finished(calls: Deque, call: T) {  
  val idleCallback: Runnable?  
  synchronized(this) {
    // 从 runningAsyncCalls 中删除当前 call  
    if (!calls.remove(call)) throw AssertionError("Call wasn't in-flight!")  
    idleCallback = this.idleCallback  
  }  
  // 如果当前还在运行
  val isRunning = promoteAndExecute()  

  // 如果当前没有任务运行了,那么调用空闲的一个回调
  if (!isRunning && idleCallback != null) {  
    idleCallback.run()  
  }  
}

Interceptor

接下来我们来看 getResponseWithInterceptorChain 方法:

@Throws(IOException::class)
internal fun getResponseWithInterceptorChain(): Response {
  // Build a full stack of interceptors.
  val interceptors = mutableListOf()
  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(
    call = this,
    interceptors = interceptors,
    index = 0,
    exchange = null,
    request = originalRequest,
    connectTimeoutMillis = client.connectTimeoutMillis,
    readTimeoutMillis = client.readTimeoutMillis,
    writeTimeoutMillis = client.writeTimeoutMillis
  )

  var calledNoMoreExchanges = false
  try {
    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)
    }
  }
} 

拦截器链是 OKHttp 的一个核心,我们首先来看看 Interceptor 和 Chain 的接口定义:

fun interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response

  companion object {
    /**
     * Constructs an interceptor for a lambda. This compact syntax is most useful for inline
     * interceptors.
     *
     * ```
     * val interceptor = Interceptor { chain: Interceptor.Chain ->
     *     chain.proceed(chain.request())
     * }
     * ```
     */
    inline operator fun invoke(crossinline block: (chain: Chain) -> Response): Interceptor =
      Interceptor { block(it) }
  }

  interface Chain {
    fun request(): Request

    @Throws(IOException::class)
    fun proceed(request: Request): Response

    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    fun connection(): Connection?

    fun call(): Call

    fun connectTimeoutMillis(): Int

    fun withConnectTimeout(timeout: Int, unit: TimeUnit): Chain

    fun readTimeoutMillis(): Int

    fun withReadTimeout(timeout: Int, unit: TimeUnit): Chain

    fun writeTimeoutMillis(): Int

    fun withWriteTimeout(timeout: Int, unit: TimeUnit): Chain
  }
}

Interceptor 接口中的 intercept 方法接收一个 Chain 返回一个 Response

这里首先要理解拦截器这种设计模式。(如果不懂,可以去网上搜一些文章先看看)

在 OKHttp.Build 中提供了添加和获取 interceptor 的方法。

fun addInterceptor(interceptor: Interceptor) = apply {
      interceptors += interceptor
}

fun addNetworkInterceptor(interceptor: Interceptor) = apply {
      networkInterceptors += interceptor
}
// ...

可以看到 interceptor 被分为了两类:

  • interceptor
  • networkInterceptor

从上面的代码中可以看出,在拦截器链中首先添加的就是 OKHttpClient 的 interceptors,之后又添加了 RetryAndFollowUpInterceptorBridgeInterceptorCacheInterceptor 以及 ConnectInterceptor 之后才会添加 OKHttpClient 的 networkInterceptors。所以点一个区别就是添加的顺序。添加的顺序不同,导致 networkInterceptors 中的 Interceptor 能获取到更多的信息。

第二个不同是在 websocket 请求时是不会将 networkInterceptors 添加到拦截器链中的。

在下面的代码中构造了 RealInterceptorChain 实例,并调用了 chain.proceed 方法。

@Throws(IOException::class)
  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"
      }
    }

    // Call the next interceptor in the chain.
    val next = copy(index = index + 1, request = request)
    val interceptor = interceptors[index]

    @Suppress("USELESS_ELVIS")
    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
  }

Chain 类像一个链条一样用来组织这些 Interceptor。RealInterceptorChain 中有一个 index 参数表示当前执行的第几个过滤器。在拦截器链的开始输出 Request ,最终返回一个 Response。每个 Interceptor 的 intercept 会调用 Chain 的 process 方法来执行后面的过滤器。它的时序图如下图所示。

image_IYaKTbBevo.png

接下来我们逐个分析这些拦截器。

RetryAndFollowUpInterceptor

这个拦截器的主要作用是处理重试以及重定向。

@Throws(IOException::class)
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()
  while (true) {
    // 这个方法是为后续的处理创建一个连接寻找器
    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) {
        // 连接一个路由失败了,请求还没有发送
        // The attempt to connect via a route failed. The request will not have been sent.
        if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
          throw e.firstConnectException.withSuppressed(recoveredFailures)
        } else {
          recoveredFailures += e.firstConnectException
        }
        // 不需要新的 ExchangeFinder 了
        newExchangeFinder = false
        // 重试
        continue
      } catch (e: IOException) {
        // 与服务器的交流沟通失败了,请求已经发送了
        // An attempt to communicate with a server failed. The request may have been sent.
        if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
          throw e.withSuppressed(recoveredFailures)
        } else {
          recoveredFailures += e
        }
        newExchangeFinder = false
        continue
      }
      // 下面的这些代码是请求已经完成其他拦截器链,并且没有发生异常
        // 如果之前没有创建一个 body 为空的 Response,在这里创建一个。
      // Attach the prior response if it exists. Such responses never have a body.
      if (priorResponse != null) {
        response = response.newBuilder()
          .priorResponse(priorResponse.newBuilder()
            .body(null)
            .build())
          .build()
      }
      val exchange = call.interceptorScopedExchange
      // followUpRequest 会进行一些异常处理和重定向
      val followUp = followUpRequest(response, exchange)
        // 如果 followUp 为空,说明不允许或者不需要重定向,直接返回 Response
      if (followUp == null) {
        if (exchange != null && exchange.isDuplex) {
          call.timeoutEarlyExit()
        }
        closeActiveExchange = false
        return response
      }
      val followUpBody = followUp.body
      // isOneShot 如果仅允许一次重定向
      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 {
      // 释放 exchange
      call.exitNetworkInterceptorExchange(closeActiveExchange)
    }
  }
} 

该拦截器中有一个 while 死循环,以便当失败的时候进行重试。在开始的时候进行了一些数据准备,然后调用 proceed 进行后续的请求,如果这些拦截器抛出了异常会在 catch 中进行拦截,方便进行恢复和重试。

RouteException

当发生 RouteException,也就是连接路由失败了,然后会进行调用 recover 来进行恢复。此时的请求还没有发出去。

private fun recover(
    e: IOException,
    call: RealCall,
    userRequest: Request,
    requestSendStarted: Boolean
  ): Boolean {
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure) return false

    // We can't send the request body again.
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false

    // This exception is fatal.
    if (!isRecoverable(e, requestSendStarted)) return false

    // No more routes to attempt.
    if (!call.retryAfterFailure()) return false

    // For failure recovery, use the same route selector with a new connection.
    return true
  }

如果在创建 OKHttp 的时候设置了 retryOnConnectionFailure 为 false,那么表示将不允许重试,该值默认为 true。

private fun isRecoverable(e: IOException, requestSendStarted: Boolean): Boolean {
  // If there was a protocol problem, don't recover.
  if (e is ProtocolException) {
    return false
  }
  // If there was an interruption don't recover, but if there was a timeout connecting to a route
  // we should try the next route (if there is one).
  if (e is InterruptedIOException) {
    return e is SocketTimeoutException && !requestSendStarted
  }
  // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
  // again with a different route.
  if (e is SSLHandshakeException) {
    // If the problem was a CertificateException from the X509TrustManager,
    // do not retry.
    if (e.cause is CertificateException) {
      return false
    }
  }
  if (e is SSLPeerUnverifiedException) {
    // e.g. a certificate pinning error.
    return false
  }
  // An example of one we might want to retry with a different route is a problem connecting to a
  // proxy and would manifest as a standard IOException. Unless it is one we know we should not
  // retry, we return true and try a new route.
  return true
}

当然不是所有的异常情况都能恢复,如果发生了下面的这些异常时是不能恢复的:

  • ProtocolException
  • InterruptedIOException,IO 中断异常。当然如果请求没有开始并且是 SocketTimeoutException,也就是 Socket 连接超时了是可以进行恢复的。
  • SSLHandshakeException 并且是因为 CertificateException(证书异常) 导致的
  • SSLPeerUnverifiedException 证书过期或者失效
    call.retryAfterFailure() 用来判断是否还有更多的路由,如果没有了就不进行重试了。

IOException

IOException 和 RouteException 类似,也都是调用了 recover 来进行恢复,区别是是如果发生了 IOException 表示请求已经发送出去了。

重定向

val followUp = followUpRequest(response, exchange)
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()
if (++followUpCount > MAX_FOLLOW_UPS) {
  throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response

调用 followUpRequest 来获取重定向的 Request,如果返回 null,表示不支持或者不需要重定向。
如果重定向请求的 body 不为空并且 isOneShot 返回 true,直接返回 response

这里的 isOneShot 是什么呢?

/**
 * Returns true if this body expects at most one call to [writeTo] and can be transmitted
 * at most once. This is typically used when writing the request body is destructive and it is not
 * possible to recreate the request body after it has been sent.
 *
 * This method returns false unless it is overridden by a subclass.
 *
 * By default OkHttp will attempt to retransmit request bodies when the original request fails
 * due to any of:
 *
 *  * A stale connection. The request was made on a reused connection and that reused connection
 *    has since been closed by the server.
 *  * A client timeout (HTTP 408).
 *  * A authorization challenge (HTTP 401 and 407) that is satisfied by the [Authenticator].
 *  * A retryable server failure (HTTP 503 with a `Retry-After: 0` response header).
 *  * A misdirected request (HTTP 421) on a coalesced connection.
 */
open fun isOneShot(): Boolean = false

如果请求的 body 最多只能进行一次 writeTo 的调用并且最多可以传输一次,则返回 true。因为在有些情况下,调用 writeTo 方法后会调用请求体的数据被破环。例如输入流。

从源码中可以看到,下面的这个子类返回的是 true。

/** Returns a new request body that transmits this. */

@JvmStatic
@JvmName("create")
fun FileDescriptor.toRequestBody(contentType: MediaType? = null): RequestBody {
    return object : RequestBody() {
        override fun contentType() = contentType
        override fun isOneShot(): Boolean = true
        override fun writeTo(sink: BufferedSink) {
            FileInputStream(this@toRequestBody).use {
                sink.buffer.writeAll(it.source())
            }
        }
    }
}

上面的代码是从文件创建一个 RequestBody。当该 RequestBody 调用 writeTo 方法的时候,文件流一个被读取了,读取完成后这个流就不可以再次被读取了,所以这里的 isOneShot 方法返回 true。

在默认情况下 isOneShot 返回的是 false,当请求以如下的原因失败的时候,OkHttp 将尝试重新传输请求体。

  • 旧的连接。请求在一个重用的连接上发出的,该重用的连接已被服务器关闭。
  • 客户端超时(HTTP 408)
  • 一个授权请求(HTTP 401 和 407),并且 Authenticator 能够满足授权。
  • 可重试的服务器故障(HTTP 503 并且有 Retry-After: 0 的响应头)
  • HTTP 421,这个是OKHttp4.x以后新加的,即使域名不同,OkHttp也可以合并HTTP/2连接,如果服务器返回了421,会进行重试。

接下来我们看看 followUpRequest 方法:

@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
  val route = exchange?.connection?.route()
  val responseCode = userResponse.code
  val method = userResponse.request.method
  when (responseCode) {
    HTTP_PROXY_AUTH -> {
      val selectedProxy = route!!.proxy
      if (selectedProxy.type() != Proxy.Type.HTTP) {
        throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
      }
      return client.proxyAuthenticator.authenticate(route, userResponse)
    }
    HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
    HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
      return buildRedirectRequest(userResponse, method)
    }
    HTTP_CLIENT_TIMEOUT -> {
      // 408's are rare in practice, but some servers like HAProxy use this response code. The
      // spec says that we may repeat the request without modifications. Modern browsers also
      // repeat the request (even non-idempotent ones.)
      if (!client.retryOnConnectionFailure) {
        // The application layer has directed us not to retry the request.
        return null
      }
      val requestBody = userResponse.request.body
      if (requestBody != null && requestBody.isOneShot()) {
        return null
      }
      val priorResponse = userResponse.priorResponse
      if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
        // We attempted to retry and got another timeout. Give up.
        return null
      }
      if (retryAfter(userResponse, 0) > 0) {
        return null
      }
      return userResponse.request
    }
    HTTP_UNAVAILABLE -> {
      val priorResponse = userResponse.priorResponse
      if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
        // We attempted to retry and got another timeout. Give up.
        return null
      }
      if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
        // specifically received an instruction to retry without delay
        return userResponse.request
      }
      return null
    }
    HTTP_MISDIRECTED_REQUEST -> {
      // OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
      // RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
      // we can retry on a different connection.
      val requestBody = userResponse.request.body
      if (requestBody != null && requestBody.isOneShot()) {
        return null
      }
      if (exchange == null || !exchange.isCoalescedConnection) {
        return null
      }
      exchange.connection.noCoalescedConnections()
      return userResponse.request
    }
    else -> return null
  }
}
  • HTTP_CLIENT_TIMEOUT(408):响应状态码 **408 Request Timeout** 表示服务器想要将没有在使用的连接关闭。一些服务器会在空闲连接上发送此信息,即便是在客户端没有发送任何请求的情况下。这种情况下会进行重试。

  • client.retryOnConnectionFailure 如果设置了不允许重试,这里就直接返回了

  • requestBody != _null_ && requestBody.isOneShot() 上面分析过这种情况,重试不了。

  • retryAfter(userResponse, 0) > 0 这里服务端通过 Retry-After 字段来告诉客户端多久后可以重试。当然如果在服务端返回的响应头中没有获取到该字段,返回就返回默认值 0。如果返回的结果不为 0,也就是不能立即重试,这里就返回 null 了。

  • HTTP_UNAVAILABLE(503):服务不可用。

  • HTTP_MISDIRECTED_REQUEST(421):这个是OKHttp4.x以后新加的,即使域名不同,OkHttp也可以合并HTTP/2连接,如果服务器返回了421,会进行重试。

  • HTTP_PROXY_AUTH(407):客户端使用了代理服务器,并且在请求头中添加了 Proxy Authentication Required 要求代理服务器进行授权重定向。

  • HTTP_UNAUTHORIZED(401):未授权,要求用户进行授权,授权完成后重定向。

  • HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER

    • HTTP_MULT_CHOICE(300):表示该请求拥有多种可能的响应。用户代理或者用户自身应该从中选择一个。由于没有如何进行选择的标准方法,这个状态码极少使用。
    • HTTP_MOVED_PERM(301):永久重定向,在浏览器接收到重定向之前的 url 时直接替换成缓存(假如已经请求过了并且有缓存)中的 url。
    • HTTP_SEE_OTHER(303):HTTP 303 See Other 重定向状态码,通常作为 PUT 或 POST 操作的返回结果,它表示重定向链接指向的不是新上传的资源,而是另外一个页面,比如消息确认页面或上传进度页面。而请求重定向页面的方法要总是使用 [GET](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/GET)
    • HTTP_MOVED_TEMP(302):临时重定向,与永久重定向不同,临时重定向浏览器还会每次请求重定向之前的 url。
    • HTTP_TEMP_REDIRECT(307):HTTP 307 Temporary Redirect,临时重定向响应状态码,表示请求的资源暂时地被移动到了响应的 Location 首部所指向的 URL 上。
    • HTTP_PERM_REDIRECT(308):308与301定义一致,唯一的区别在于,308状态码不允许浏览器将原本为POST的请求重顶到GET请求上。
  • HTTP_CLIENT_TIMEOUT(408):响应状态码 408 Request Timeout 表示服务器想要将没有在使用的连接关闭。一些服务器会在空闲连接上发送此信息,即便是在客户端没有发送任何请求的情况下。这种情况下会进行重试。

    • client.retryOnConnectionFailure 如果设置了不允许重试,这里就直接返回了
    • requestBody != null && requestBody.isOneShot() 上面分析过这种情况,重试不了。
    • retryAfter(userResponse, 0) > 0 这里服务端通过 Retry-After 字段来告诉客户端多久后可以重试。当然如果在服务端返回的响应头中没有获取到该字段,返回就返回默认值 0。如果返回的结果不为 0,也就是不能立即重试,这里就返回 null 了。
  • HTTP_UNAVAILABLE(503):服务不可用。

  • HTTP_MISDIRECTED_REQUEST(421):这个是OKHttp4.x以后新加的,即使域名不同,OkHttp也可以合并HTTP/2连接,如果服务器返回了421,会进行重试。

BridgeInterceptor

BridgeInterceptor 是把应用的一些代码转换成网络的代码。首先会从一个用户发起的请求构建出一个网络的请求。然后调用 proceed 方法进行其他的拦截器的处理。最终从网络的 Response 构建一个用户的 Response。说白了就是把我们的一个设置转换成网络的设置。

@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) {
      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())
  }

  if (userRequest.header("Connection") == null) {
    requestBuilder.header("Connection", "Keep-Alive")
  }

  // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
  // the transfer stream.
  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))
  }

  if (userRequest.header("User-Agent") == null) {
    requestBuilder.header("User-Agent", userAgent)
  }

  val networkResponse = chain.proceed(requestBuilder.build())

  cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

  val responseBuilder = networkResponse.newBuilder()
      .request(userRequest)

  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()
}

这里会把我们设置的一些属性转换成 Request Header。
Content-Type 这个请求头表示的是内容的类型。在 OkHttp 中它对应的是 MediaType 类型。如果我们想要发送一个 JSON 请求,就需要设置 MediaType 。
下面演示一下如何使用 OkHttp 发送一个 json 请求。首先我们使用 ktor 简单写一个 echo 接口。

fun main() {
    val server = embeddedServer(Netty, port = 8080) {
        install(ContentNegotiation) {
            routing {
                post("/echo") {
                    val post = call.receiveText()
                    call.respondText(
                        contentType = ContentType.parse("application/json"),
                        text = post
                    )
                }
            }
        }
    }
    server.start(wait = true)
}
fun main() {
    val okHttpClient = OkHttpClient()

    val data = """
        {
            "Hello": "hello"
        }
    """.trimIndent()

    val requestBody = data.toRequestBody("Application/json".toMediaType())
    val request = Request.Builder()
        .url("http://localhost:8080/echo")
        .post(requestBody)
        .build()
    val response = okHttpClient.newCall(request).execute()
    println(response.body?.string())
}

Content-Length 表示请求体的长度。
Transfer-Encoding: chunked 表示请求的长度不能确定。
Host 主机。
Connection 长连接。
Accept-Encoding 表示客户端可以接受的压缩格式。如果没有设置这里指定 gzip。
Range 表示请求部分资源。
Cookie Cookie 。

OkHttp 中并没有缓存 Cookie,不过提供了 CookieJar 来把缓存 Cookie 的工作交给了使用方。
可以通过 OkHttp.Build 来设置 CookieJar 。

interface CookieJar {
  /**
   * Saves [cookies] from an HTTP response to this store according to this jar's policy.
   *
   * Note that this method may be called a second time for a single HTTP response if the response
   * includes a trailer. For this obscure HTTP feature, [cookies] contains only the trailer's
   * cookies.
   */
  fun saveFromResponse(url: HttpUrl, cookies: List)

  /**
   * Load cookies from the jar for an HTTP request to [url]. This method returns a possibly
   * empty list of cookies for the network request.
   *
   * Simple implementations will return the accepted cookies that have not yet expired and that
   * [match][Cookie.matches] [url].
   */
  fun loadForRequest(url: HttpUrl): List

  companion object {
    /** A cookie jar that never accepts any cookies. */
    @JvmField
    val NO_COOKIES: CookieJar = NoCookies()
    private class NoCookies : CookieJar {
      override fun saveFromResponse(url: HttpUrl, cookies: List) {
      }

      override fun loadForRequest(url: HttpUrl): List {
        return emptyList()
      }
    }
  }
}

CookieJar 提供了两个方法:

  • saveFromResponse 保存响应头中的 Cookie。
  • loadForRequest 为请求添加 Cookie。
// 载入 Cookie
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
  requestBuilder.header("Cookie", cookieHeader(cookies))
}

if (userRequest.header("User-Agent") == null) {
  requestBuilder.header("User-Agent", userAgent)
}

val networkResponse = chain.proceed(requestBuilder.build())

// 保存 Cookie
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)

fun CookieJar.receiveHeaders(url: HttpUrl, headers: Headers) {
  if (this === CookieJar.NO_COOKIES) return

  val cookies = Cookie.parseAll(url, headers)
  if (cookies.isEmpty()) return

  saveFromResponse(url, cookies)
}

OKHttpClient 中默认的 Cookie 是 CookieJar 的伴生对象中的 NO_COOKIES 。所以默认是不会对 Cookie 进行保存的,会直接丢弃掉。
User-Agent 可以理解为告诉服务器发起请求的客户端是什么。如果没有添加,OkHttp 的默认为:

const val userAgent = "okhttp/${OkHttp.VERSION}

接下来调用了 val networkResponse = chain.proceed(requestBuilder.build()) 处理其他的拦截器了,当其他拦截器处理完毕后,构建返回的 Response。

val responseBuilder = networkResponse.newBuilder()
    .request(userRequest)
// 如果请求头中的 Accept-Encoding 是 gzip 并且返回的 Content-Encoding 也是 gzip
// 并且有响应体,那么就调用 gzip 进行解压
if (transparentGzip &&
    "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
    networkResponse.promisesBody()) {
  val responseBody = networkResponse.body
  if (responseBody != null) {
    // GzipSource 包装了 responseBody.source
    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")
    // 重新构造了正真的 ResponseBody
    responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
  }
}

/**
 * Returns true if the response headers and status indicate that this response has a (possibly
 * 0-length) body. See RFC 7231.
 */
fun Response.promisesBody(): Boolean {
  // HEAD requests never yield a body regardless of the response headers.
  if (request.method == "HEAD") {
    return false
  }

  val responseCode = code
  if ((responseCode < HTTP_CONTINUE || responseCode >= 200) &&
      responseCode != HTTP_NO_CONTENT &&
      responseCode != HTTP_NOT_MODIFIED) {
    return true
  }

  // If the Content-Length or Transfer-Encoding headers disagree with the response code, the
  // response is malformed. For best compatibility, we honor the headers.
  if (headersContentLength() != -1L ||
      "chunked".equals(header("Transfer-Encoding"), ignoreCase = true)) {
    return true
  }

  return false
}

在有些情况下是没有响应体的:

  • HEAD 当请求的 medthod 为 HEAD 的时候是没有响应体的。
  • HTTP status 204: HTTP 204 No Content 成功状态响应码,表示该请求已经成功了,但是客户端客户不需要离开当前页面。默认情况下 204 响应是可缓存的。一个 ETag 标头包含在此类响应中。
  • HTTP status 304: HTTP 304 未改变说明无需再次传输请求的内容,也就是说可以使用缓存的内容。

这里当 Content-Length 为 -1 或者 Transfer-Encoding: chunked 仅仅表示长度未知,不能说明没有响应体。

CacheInterceptor

未完待续...

ConnectInterceptor

处理连接的 Interceptor。

/**
 * Opens a connection to the target server and proceeds to the next interceptor. The network might
 * be used for the returned response, or to validate a cached response with a conditional GET.
 */
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)
  }
}

你可能感兴趣的:(OKHttp 源码分析)