OkHttp源码分析

242 阅读13分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第26天,点击查看活动详情

一、简介

OkHttp官网地址

众所周知,OkHttp是一个客户端用来发送HTTP消息并对服务器的响应做出处理的应用层框架。而且现在流行的Retrofit的底层同样也是基于Okhttp的。那么OkHttp有什么优点呢?

  • 无缝的支持GZIP减少数据流量
  • 缓存响应数据减少重复的网络请求
  • 请求失败自动重试主机的其他ip,自动重定向。
  • 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟。

当网络出现问题时,OkHttp会保持不变:它会无声地从常见的连接问题中恢复。如果您的服务有多个IP地址,如果第一次连接失败,OkHttp将尝试其他地址。这对于IPv4+IPv6和冗余数据中心托管的服务是必要的。OkHttp支持现代TLS特性(TLS 1.3, ALPN,证书固定)。可以将其配置为后退以实现广泛连接。

使用OkHttp很简单。它的请求/响应API是用流畅的构建器和不可变性设计的。它既支持同步阻塞调用,也支持带回调的异步调用。

二、使用方法

添加依赖

implementation("com.squareup.okhttp3:okhttp:4.10.0")

创建 OkHttpClient 对象

OkHttpClient client = new OkHttpClient();

Get

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();
  //同步方法
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
  //异步方法
  client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {

     }
   });
}

Post

public static final MediaType JSON = MediaType.get("application/json");
String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(json, JSON);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  //同步方法    
  try (Response response = client.newCall(request).execute()) {
    return response.body().string();
  }
   //异步方法
  client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        
    }

    @Override
    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {

     }
   });
}

三、源码分析

从代码看,请求流程是从OKHttpClient.newCall(Request)开始的,调用该方法前,主要是一些初始化工作。

将Request对象封装为Call对象

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

Call是接口,实现类是RealCall

RealCall通过execute发起同步 通过enqueue发起异步

同步方法

//同步请求
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)
  }
}

通过Dispatcher类加入同步队列

@Synchronized internal fun executed(call: RealCall) {
  runningSyncCalls.add(call)
}

通过责任链模式组成的拦截器,发起真正的网络请求

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) {
    interceptors += client.networkInterceptors
  }
  interceptors += CallServerInterceptor(forWebSocket)
  //通过index 按照顺序执行拦截器功能
  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)
    }
  }
}

异步方法

同样通过通过Dispatcher类加入异步队列

//异步请求
override fun enqueue(responseCallback: Callback) {
  check(executed.compareAndSet(false, true)) { "Already Executed" }
   
  callStart()
  //加入异步队列 
  client.dispatcher.enqueue(AsyncCall(responseCallback))
}

通过promoteAndExecute开始遍历异步队列的中的请求

internal fun enqueue(call: AsyncCall) {
  synchronized(this) {
    //加入异步队列
    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) {
      val existingCall = findExistingCallWithHost(call.host)
      if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
    }
  }
  //执行异步队列中的请求
  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()
      //增加最大请求限制
      if (runningAsyncCalls.size >= this.maxRequests) break // Max capacity.
      //host 限制
      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.executeOn(executorService)
  }

  return isRunning
  }

AsyncCall本身就是一个封装的线程,在这里调用executeOn传入线程池,可见异步请求是通过线程池完成的,进入executeOn方法一探究竟

internal inner class AsyncCall(
  private val responseCallback: Callback
) : Runnable {
  @Volatile var callsPerHost = AtomicInteger(0)
    private set

  fun reuseCallsPerHostFrom(other: AsyncCall) {
    this.callsPerHost = other.callsPerHost
  }

  val host: String
    get() = originalRequest.url.host

  val request: Request
      get() = originalRequest

  val call: RealCall
      get() = this@RealCall

  /**
   * Attempt to enqueue this async call on [executorService]. This will attempt to clean up
   * if the executor has been shut down by reporting the call as failed.
   */
  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!
      }
    }
  }
  //真正执行的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 {
        client.dispatcher.finished(this)
      }
    }
  }
}

深入源码会发现,同步或异步请求都是通过分发器Dpatcher将任务加入到不同的队列进行管理。

  • 同步请求会调用getResponseWithInterceptorChain()立即执行请求,执行完毕后,将请求从队列中移除。
  • 异步请求会创建AsyncCall对象,AsyncCall是继承自Runnable的可执行对象。分发器为异步处理创建了线程池,通过线程池对任务队列进行异步处理。

最后还是通过RealCall的execute方法去执行单个请求。

那么线程池什么时候初始化的呢?

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

在构建Client对象时通过Dpatcher已经创建了,可以看到这是一个没有核心线程的超大容量缓存线程池,超时时常为60s,SynchronousQueue可以简单的理解为一个无法存储元素的队列,因此这将导致任何任务都会立刻执行。

从其特性来看,这类线程池适合执行大量耗时较少的任务。当整个线程池处于闲置状态时,线程池中的线程都会因为超时而被停止,这个时候缓存线程池之中实际上是没有线程的,它几乎不占用任何系统资源。

到此可以简单梳理出整个框架的流程图

image.png

可以看到前期的实现主要处理了异步合同步的问题,真正的核心逻辑都是通过拦截器来实现的,下面我们重点分析下拦截器

四、拦截器

从代码可以看到,整个请求流程从请求到取到返回结果前,通过责任链模式,将请求交给一系列的拦截器进行处理,得到最终Response对象。

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

从源码可以看出,责任链中不仅有预先定义好的拦截器,也可以像OKHttpClient对象添加自定义的拦截器对象。

class RealInterceptorChain(
  internal val call: RealCall,
  private val interceptors: List<Interceptor>,
  private val index: Int,
  internal val exchange: Exchange?,
  internal val request: Request,
  internal val connectTimeoutMillis: Int,
  internal val readTimeoutMillis: Int,
  internal val writeTimeoutMillis: Int
) : Interceptor.Chain {
  //已省略非核心代码。。。
  
  @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"
      }
    }

    //通过index递增来遍历依次执行拦截器
    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
  }
}

责任链按照添加的数据进行处理,处理的顺序为:

  • 自定义拦截器
  • RetryAndFollowUpInterceptor
  • BridgeInterceptor
  • CacheInterceptor
  • ConnectInterceptor
  • 自定义networkInterceptors
  • CallServerInterceptor

RetryAndFollowUpInterceptor

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<IOException>()
  while (true) {
    //这里会新建一个ExchangeFinder,ConnectInterceptor会使用到 发起真正的请求
    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) {
        // 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
      }

      // 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
      //找出用于响应接收userResponse的HTTP请求。这将添加身份验证头、重定向或处理客户端请求超时。如果后续操作不必要或不适用,则返回null。
      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()
      //最大重试次数,不同的浏览器是不同的,比如:Chrome为21,Safari则是16
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw ProtocolException("Too many follow-up requests: $followUpCount")
      }
      request = followUp
      priorResponse = response
    } finally {
      //结束网络请求
      call.exitNetworkInterceptorExchange(closeActiveExchange)
    }
  }
}

大致流程如图

image.png

BridgeInterceptor

BridgeInterceptor比较简单,主要负责对请求中没有指定的一些header进行完善和基础优化,并对响应进行相应的处理。

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

  //支持gzip
  var transparentGzip = false
  if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
    transparentGzip = true
    requestBuilder.header("Accept-Encoding", "gzip")
  }

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

上面代码总体来说干了两件事: 1. 对原始的Request进行检查,设置Content-TypeContent-LengthTransfer-EncodingHostConnectionAccept-EncodingCookieUser-Agent这些header 2. 进行网络请求。若是gzip编码,则对响应进行Gzip处理;否则直接返回

CacheInterceptor

CacheInterceptor负责处理请求过程中的缓存问题。cache(InternalCache对象)是全局变量,在Cache类内部实现,使用DiskLruCache对缓存进行管理,缓存在磁盘上。

缓存策略如下

image.png

结合策略进入源码看看

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  val call = chain.call()
  //读取缓存中的响应信息
  val cacheCandidate = cache?.get(chain.request())

  val now = System.currentTimeMillis()
  
  //根据请求和缓存响应创建缓存策略
  //CacheStrategy.networkRequest为空时表示不使用网络
  //CacheStrategy.cacheResponse为空时表示不使用缓存
  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) {
    // The cache candidate wasn't applicable. Close it.
    cacheCandidate.body?.closeQuietly()
  }

  //不使网络,也没有缓存信息时,返回504错误信息
  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)
        }
  }

  //不使用网络,直接读缓存 但是忽略body
  if (networkRequest == null) {
    return cacheResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .build().also {
          listener.cacheHit(call, it)
        }
  }
 //如果网络request不为空,缓存也不为空,通知命中缓存,如果缓存为空,并且Cache不为空,通知缓存丢失。
  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 (cacheResponse != null) {
    //如果304证明未修改 更新至缓存中
    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)) {
      // 存入缓存.
      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
}

接下来看看另外一个要点:CacheStrategy是如何决定使不使用网络请求、响应缓存的。

private fun computeCandidate(): CacheStrategy {
  // 没有缓存
  if (cacheResponse == null) {
    return CacheStrategy(request, null)
  }
  // Drop the cached response if it's missing a required handshake.
  // https请求,但没有必要的握手信息,丢弃缓存
  if (request.isHttps && cacheResponse.handshake == null) {
    return CacheStrategy(request, null)
  }

  // If this response shouldn't have been stored, it should never be used as a response source.
  // This check should be redundant as long as the persistence store is well-behaved and the
  // rules are constant.
 // 不应该被缓存,丢弃缓存
  if (!isCacheable(cacheResponse, request)) {
    return CacheStrategy(request, null)
  }

  val requestCaching = request.cacheControl
  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())
  }
  
  //根据缓存的缓存时间,缓存可接受最大过期时间等等HTTP协议上的规范,来判断缓存是否可用
  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.
  // 请求条件, 当etag、lastModified、servedDate这三种属性存在时,需要向服务器确认缓存的有效性
  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.
  }

  val conditionalRequestHeaders = request.headers.newBuilder()
  conditionalRequestHeaders.addLenient(conditionName, conditionValue!!)

  val conditionalRequest = request.newBuilder()
      .headers(conditionalRequestHeaders.build())
      .build()
  return CacheStrategy(conditionalRequest, cacheResponse)
}

ConnectInterceptor

此拦截器负责建⽴连接。包含了⽹络请求所需要的DNS解析,TCP连接(HTTP),或者TCP之前的TLS连接(HTTPS) ,并且会创建出对应的HttpCodec对象(⽤于编码解码HTTP请求)。我们来看下代码:

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

代码只有行,与服务器建立连接,然后传递到下一个拦截器

我们继续往下看RealCall的initExchange方法:

internal fun initExchange(chain: RealInterceptorChain): Exchange {
  synchronized(this) {
    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
}

这个方法主要是用来查找新的或者合并后的链接以进行即将到来的请求和响应。我们来看一下查找逻辑:

fun find(
  client: OkHttpClient,
  chain: RealInterceptorChain
): ExchangeCodec {
  try {
    //获取连接
    val resultConnection = findHealthyConnection(
        connectTimeout = chain.connectTimeoutMillis,
        readTimeout = chain.readTimeoutMillis,
        writeTimeout = chain.writeTimeoutMillis,
        pingIntervalMillis = client.pingIntervalMillis,
        connectionRetryEnabled = client.retryOnConnectionFailure,
        doExtensiveHealthChecks = chain.request.method != "GET"
    )
     //根据连接,创建并返回一个请求响应编码器:Http1ExchangeCodec 或者 Http2ExchangeCodec,
     //分别对应Http1协议与Http2协议
    return resultConnection.newCodec(client, chain)
  } catch (e: RouteException) {
    trackFailure(e.lastConnectException)
    throw e
  } catch (e: IOException) {
    trackFailure(e)
    throw RouteException(e)
  }
}

其内部调用了findHealthyConnection,该方法会调用findConnection得到一个connection,然后对其调用isHealth(true)方法进行健康诊断。如果健康,那么就可以返回该connection了;否则,从连接池中移除,并继续while循环。

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
    )

    //检查该连接是否合格可用,合格则直接返回该连接
    if (candidate.isHealthy(doExtensiveHealthChecks)) {
      return candidate
    }
    
    // 如果该连接不合格,标记为不可用,从连接池中移除
    candidate.noNewExchanges()

    // Make sure we have some routes left to try. One example where we may exhaust all the routes
    // would happen if we made a new connection and it immediately is detected as unhealthy.
    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")
  }
}

findConnection方法中,会先看已经存在的connection,然后再看连接池,最后都没有就创建新的connection。

@Throws(IOException::class)
private fun findConnection(
  connectTimeout: Int,
  readTimeout: Int,
  writeTimeout: Int,
  pingIntervalMillis: Int,
  connectionRetryEnabled: Boolean
): RealConnection {
  if (call.isCanceled()) throw IOException("Canceled")
  //第一次,尝试重连 call 中的 connection,不需要去重新获取连接
  val callConnection = call.connection 
  if (callConnection != null) {
    var toClose: Socket? = null
    synchronized(callConnection) {
      if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
        toClose = call.releaseConnectionNoEvents()
      }
    }

   //如果 call 中的 connection 还没有释放,就重用它。
    if (call.connection != null) {
      check(toClose == null)
      return callConnection
    }
  
    //如果 call 中的 connection 已经被释放,关闭Socket. 
    toClose?.closeQuietly()
    eventListener.connectionReleased(call, callConnection)
  }

  // 如果没有我们需要一个新的连接,所以重置一些状态
  refusedStreamCount = 0
  connectionShutdownCount = 0
  otherFailureCount = 0

 
  //第二次,尝试从连接池中获取一个连接,不带路由,不带多路复用
  if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
    val result = call.connection!!
    eventListener.connectionAcquired(call, result)
    return result
  }

 //连接池中是空的,准备下次尝试连接的路由
  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")

   //现在我们有了一组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 {
    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
}

在代码中可以看出,一共做了5次尝试去得到连接:

  1. 第一次,尝试重连 call 中的 connection,不需要去重新获取连接。
  2. 第二次,尝试从连接池中获取一个连接,不带路由,不带多路复用。
  3. 第三次,再次尝试从连接池中获取一个连接,带路由,不带多路复用。
  4. 第四次,手动创建一个新连接。
  5. 第五次,再次尝试从连接池中获取一个连接,带路由,带多路复用。

OK,到了这一步,就算建立起了连接。

流程图大致如下

image.png

CallServerInterceptor

CallServerInterceptor是最后一个拦截器,将Request对象写入流信息,发送给服务器,并将从服务器返回的流信息转化为和Resposne对象。

@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
  val realChain = chain as RealInterceptorChain
  val exchange = realChain.exchange!!
  val request = realChain.request
  val requestBody = request.body
  val sentRequestMillis = System.currentTimeMillis()
  //写入请求头
  exchange.writeRequestHeaders(request)

  var invokeStartEvent = true
  var responseBuilder: Response.Builder? = null
   //如果不是GET请求,并且请求体不为空
  if (HttpMethod.permitsRequestBody(request.method) && requestBody != null) {
      //当请求头为"Expect: 100-continue"时,
      //在发送请求体之前需要等待服务器返回"HTTP/1.1 100 Continue" 的response,
      //如果没有等到该response,就不发送请求体。
      //POST请求,先发送请求头,在获取到100继续状态后继续发送请求体
    if ("100-continue".equals(request.header("Expect"), ignoreCase = true)) {
      exchange.flushRequest()
      responseBuilder = exchange.readResponseHeaders(expectContinue = true)
      exchange.responseHeadersStart()
      invokeStartEvent = false
    }
    //写入请求体
    if (responseBuilder == null) {
      if (requestBody.isDuplex()) {
        //如果请求体是双公体,就先发送请求头,稍后在发送请求体
        exchange.flushRequest()
        val bufferedRequestBody = exchange.createRequestBody(request, true).buffer()
        //写入请求体
        requestBody.writeTo(bufferedRequestBody)
      } else {
         //如果获取到了"Expect: 100-continue"响应,写入请求体
        val bufferedRequestBody = exchange.createRequestBody(request, false).buffer()
        requestBody.writeTo(bufferedRequestBody)
        bufferedRequestBody.close()
      }
    } else {
      exchange.noRequestBody()
      if (!exchange.connection.isMultiplexed) {
        // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
        // from being reused. Otherwise we're still obligated to transmit the request body to
        // leave the connection in a consistent state.
        exchange.noNewExchangesOnConnection()
      }
    }
  } else {
    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) {
    // Server sent a 100-continue even though we did not request one. Try again to read the actual
    // response status.
    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) {
    // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
    response.newBuilder()
        .body(EMPTY_RESPONSE)
        .build()
  } else {
    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()
  }
  if ((code == 204 || code == 205) && response.body?.contentLength() ?: -1L > 0L) {
    throw ProtocolException(
        "HTTP $code had non-zero Content-Length: ${response.body?.contentLength()}")
  }
  return response
}

CallServerInterceptor由以下步骤组成:

  1. 向服务器发送 request header
  2. 如果有 request body,就向服务器发送
  3. 读取 response header,先构造一个 Response 对象
  4. 如果有 response body,就在 3 的基础上加上 body 构造一个新的 Response 对象

这里我们可以看到,核心工作都由 HttpCodec 对象完成,而 HttpCodec 实际上利用的是 Okio,而 Okio 实际上还是用的 Socket,只不过一层套一层,层数有点多。

自定义拦截器

如果项目中有特殊需求,还可以自定义拦截器,处理一些特殊的需求。
只需要继承Interceptor,并实现intercept(Chain chain)方法。
只需要使用OKHttpClient.Builder的addInterceptor或addNetworkInterceptor插入即可。
自定义拦截器目前可以用于以下场景:

  • OKHttp自带了一个自定义拦截器HttpLoggingInterceptor,用于打印请求和响应报文的日志信息。
  • 开源库chuck实现了在手机通知栏监控http请求。
  • 封装业务拦截器,处理业务上统一行为,比如插入全局请求参数,统一参数格式,参数加密,封装统一响应结果解析等操作。

五、总结

至此,OkHttp3的源码大致就过了一遍,这里小结一下。 

在OkHttp中,RealCall是Call的实现类,负责执行网络请求。其中,异步请求由Dispatcher进行调度,并放到线程池(一个典型的CachedThreadPool)中执行。 

执行网络请求的过程中,请求会依次经过下列拦截器组成的责任链,最后发送到服务器。

  1. interceptors()
  2. 重试、重定向拦截器RetryAndFollowUpInterceptor
  3. 把用户请求转换为服务器请求、把服务器返响应转换为用户响应的BridgeInterceptor
  4. 读取缓存直接返回、将响应写入到缓存中的CacheInterceptor
  5. 与服务器建立连接的ConnectInterceptor
  6. networkInterceptors()
  7. 真正执行网络请求的CallServerInterceptor

而响应会从CallServerInterceptor开始往前依次经过这些拦截器,最后客户端进行处理。

大致流程如图

image.png