OkHttp源碼解讀第二篇——請求過程

OkHttp的請求過程

上篇文章說到 OkHttp 的請求過程是在getResponseWithInterceptorChain()裏,下面分析下請求和響應過程,先看下這個方法實現:

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

這個核心方法一共分爲三部分,組成了 OkHttp 的核心代碼。

getResponseWithInterceptorChain分析

添加攔截器

    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)

上面代碼很簡單,就是拼裝所有攔截器,第一個攔截器client.interceptors是開發者自定義的攔截器。

組裝

    val chain = RealInterceptorChain(
        call = this,
        interceptors = interceptors,
        index = 0,
        exchange = null,
        request = originalRequest,
        connectTimeoutMillis = client.connectTimeoutMillis,
        readTimeoutMillis = client.readTimeoutMillis,
        writeTimeoutMillis = client.writeTimeoutMillis
    )

在這一步,把配置信息和攔截器組裝成一個RealInterceptorChain

處理

下面這行代碼,是真正處理的地方:

      val response = chain.proceed(originalRequest)

繼續點進去,看方法實現:

 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
  }

RealInterceptorChainproceed方法主要做兩件事情,取出對應index的攔截器,index+1組成另一個RealInterceptorChain,把新組成的RealInterceptorChain傳給攔截器執行攔截代碼。

val interceptor = interceptors[index]這裏是傳入的index是 0 ,所以當前RealInterceptorChain對應的proceed方法中的攔截器第一個攔截器,val next = copy(index = index + 1, request = request)是一個配置信息一樣但是index是 1 的新RealInterceptorChain。注意這裏不是攔截器,是一個chain

val response = interceptor.intercept(next)這裏是當前的攔截器開始工作,這是一個接口,它的實現裏有一句代碼是上面見過的,隨便點進一個攔截器查看,這裏點的是RetryAndFollowUpInterceptor

          response = realChain.proceed(request)

而這個realChain就是上面interceptor.intercept(next)傳入的next,根據index取出的是第二個攔截器。是不是恍然大悟,這是俄羅斯套娃,一個接一個,並不是一個方法到底的。在第一個攔截器的攔截實現方法裏,代碼執行到中間,會去執行第二個攔截器,然後去執行第三個、第四個。等返回後再一個個返回。

override fun intercept(chain: Interceptor.Chain): Response {
    val realChain = chain as RealInterceptorChain
    var request = chain.request
    val call = realChain.call
    var followUpCount = 0
    var priorResponse: Response? = null
    var newExchangeFinder = true
    var recoveredFailures = listOf<IOException>()
    while (true) {
      call.enterNetworkInterceptorExchange(request, newExchangeFinder)

      var response: Response
      var closeActiveExchange = true
      try {
        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
          }
          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
        }

        // 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
        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
      } finally {
        call.exitNetworkInterceptorExchange(closeActiveExchange)
      }
    }
  }

這裏也分爲三部分:

  1. 配置信息,執行自己的攔截操作。
  2. 調用下一個攔截器執行
  3. 下一個攔截器返回後的操作

整個流程如上圖,像一個流水線一樣,從左向右執行,然後在返回執行。

攔截器分析

RetryAndFollowUpInterceptor

請求失敗重試和跟從重定向攔截器。

 while (true) {}

循環裏的第一行代碼:

      call.enterNetworkInterceptorExchange(request, newExchangeFinder)

這是爲接下來的連接做準備,爲當前call創建一個能尋找可用連接的對象ExchangeFinder

  this.exchangeFinder = ExchangeFinder(
          connectionPool,
          createAddress(request.url),
          this,
          eventListener
      )

這個循環裏面如果出現 RouteException 連接異常,或者 IOException 異常,如果可以重試,就會continue重試。能否重試的判斷,在recover裏:

  if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
            throw e.firstConnectException.withSuppressed(recoveredFailures)
          } else {
            recoveredFailures += e.firstConnectException
          }

下面是判斷是否要執行重定向:

 val exchange = call.interceptorScopedExchange
 val followUp = followUpRequest(response, exchange)

會根據返回的狀態碼判斷是否是重定向,並執行重定向:

   HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
        return buildRedirectRequest(userResponse, method)
      }

直到沒有重試和重定向,return response退出循環。

分析完這個攔截器,總結下它的作用:

  1. 準備連接
  2. 調用下一個攔截器
  3. 重試和重定向

BridgeInterceptor

橋接攔截器,主要功能是爲當前請求添加header。默認添加requestBuilder.header("Accept-Encoding", "gzip")壓縮,同時響應數據自動解壓。

分析完這個攔截器,總結下它的作用:

  1. 添加header
  2. 調用下一個攔截器
  3. 解壓

CacheInterceptor

緩存攔截器。如果有緩存直接返回:

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

這裏利用緩存構建一個Response

如果沒有去執行下一個攔截器做網絡請求,這行代碼已經出現很多次了:

      networkResponse = chain.proceed(networkRequest)

然後緩存結果:

if (cacheResponse != null) {
      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 strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()

ConnectInterceptor

最核心的攔截器,請求的建立就是這裏發生的。

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

這行代碼和上面的不太一樣:

    return connectedChain.proceed(realChain.request)

這裏直接return,而其他的攔截器都是再做一些後置工作,比如緩存攔截器會解壓縮。這是因爲這裏是真正建立連接的地方,至此請求建立完成,並沒有需要的後續操作。

下面分析這個攔截器的攔截操作:

 /** Finds a new or pooled connection to carry a forthcoming request and response. */
  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
  }

`val codec = exchangeFinder.find(client, chain)` 這一行找到可以連接:
private fun findHealthyConnection(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    doExtensiveHealthChecks: Boolean
  ): RealConnection {
    while (true) {
      val candidate = findConnection(
          connectTimeout = connectTimeout,
          readTimeout = readTimeout,
          writeTimeout = writeTimeout,
          pingIntervalMillis = pingIntervalMillis,
          connectionRetryEnabled = connectionRetryEnabled
      )

      // Confirm that the connection is good.
      if (candidate.isHealthy(doExtensiveHealthChecks)) {
        return candidate
      }

      // If it isn't, take it out of the pool.
      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拿到可用連接,驗證是否健康(連接正常,心跳正常等),健康就返回,一共有五種方式,分爲初始狀態獲取和再次進入獲取。

初始狀態三次獲取連接方式

  1. 初始狀態去連接池尋找連接:

      if (connectionPool.callAcquirePooledConnection(address, call, null, false)) {
          val result = call.connection!!
          eventListener.connectionAcquired(call, result)
          return result
        }
    

    這裏會判斷連接數量是否超限。

  2. 再次去連接池尋找鏈接,參數不同,傳入了路由:

      if (connectionPool.callAcquirePooledConnection(address, call, routes, false)) {
            val result = call.connection!!
            eventListener.connectionAcquired(call, result)
            return result
          }
    

    獲取路由配置,所謂路由其實就是代理、ip地址等參數的一個組合。拿到路由後嘗試重新從連接池中獲取連接,這裏主要針對http2的多路複用。

  1. 沒有可用連接,新建連接:
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
      。。。
      connectionPool.put(newConnection)

有了連接並沒有結束,而是再去連接池取一次,目的是如果同時2個請求建立連接,符合多路複用,就會丟掉一個,節省資源,最後一個參數是說只拿多路複用的連接:

    if (connectionPool.callAcquirePooledConnection(address, call, routes, true)) {
      val result = call.connection!!
      nextRouteToTry = route
      newConnection.socket().closeQuietly()
      eventListener.connectionAcquired(call, result)
      return result
    }

然後把連接放入連接池:

      connectionPool.put(newConnection)

總結:

  • 獲取無多路複用的連接
  • 獲取所有連接
  • 創建連接,只獲取多路複用的連接

再次進入獲取連接

上面是初次獲取的時候,如果重定向等第二次獲取連接的時候,也有兩種方式。

  1. 連接不可用,關閉連接進入初始狀態,但是複用路由信息,不需要再次尋找路由:
  if (callConnection != null) {
      var toClose: Socket? = null
      synchronized(callConnection) {
        if (callConnection.noNewExchanges || !sameHostAndPort(callConnection.route().address.url)) {
          toClose = call.releaseConnectionNoEvents()
        }
      }

如果這個連接不接受新的連接,不能複用,或者 Http 重定向到 Https,就會釋放掉這個連接。

  1. 如果這個連接可用,會直接重用這個連接:
   if (call.connection != null) {
        check(toClose == null)
        return callConnection
      }

建立連接

點進newConnection.connect,查看如何建立連接。

    while (true) {
      try {
        if (route.requiresTunnel()) {
          connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener)
          if (rawSocket == null) {
            // We were unable to connect the tunnel but properly closed down our resources.
            break
          }
        } else {
          connectSocket(connectTimeout, readTimeout, call, eventListener)
        }
        establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)
        eventListener.connectEnd(call, route.socketAddress, route.proxy, protocol)
        break
      } catch (e: IOException) {
        socket?.closeQuietly()
        rawSocket?.closeQuietly()
        socket = null
        rawSocket = null
        source = null
        sink = null
        handshake = null
        protocol = null
        http2Connection = null
        allocationLimit = 1

        eventListener.connectFailed(call, route.socketAddress, route.proxy, null, e)

        if (routeException == null) {
          routeException = RouteException(e)
        } else {
          routeException.addConnectException(e)
        }

        if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
          throw routeException
        }
      }
    }

route.requiresTunnel(),如果代理是 Http ,目標是 Https ,那麼創建socket之後會創建一個createTunnelRequest

connectSocket(connectTimeout, readTimeout, call, eventListener)
tunnelRequest = createTunnel(readTimeout, writeTimeout, tunnelRequest, url)
    ?: break // T

如果是正常的 Http 請求,那麼久創建一個socket

connectSocket(connectTimeout, readTimeout, call, eventListener)

其實現如下:

val rawSocket = when (proxy.type()) {
  Proxy.Type.DIRECT, Proxy.Type.HTTP -> address.socketFactory.createSocket()!!
  else -> Socket(proxy)
}

socket建立之後,就去創建 Http 連接:

establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener)

這裏會根據支持創建不同版本的 Http 連接和加密連接。

CallServerInterceptor

這個攔截是向服務器發送數據並接受數據的攔截器,代碼比較簡單。

總結:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章