老生新談,從OkHttp原理看網(wǎng)絡請求

OkHttp作為一個網(wǎng)絡請求框架,地位是不言而喻的,研究它的好處就在于能夠將TCP、HTTP、HTTPS等這些基礎的網(wǎng)絡知識實例化,抽象變?yōu)樾蜗蟆?/p>

讀完這篇文章您將了解到:

  • OkHttp的整體請求結構;
  • 責任鏈模式下各個攔截器的實現(xiàn)細節(jié)與職責;
  • 如何找到可用且健康的連接?即連接池的復用;
  • 如何找到Http1和Http2的編/解碼器?
  • NetworkInterceptor與ApplicationInterceptor攔截器的區(qū)別?
  • 如何建立TCP/TLS連接?

本文源碼為okhttp:4.9.1版本,文中沒有貼大量源碼,結合源碼一起閱讀最佳。

OkHttp整體結構

OkHttp的使用不是本文的主要內容,它只是作為源碼解讀的一個入口。

        val okHttpClient = OkHttpClient()
        val request: Request = Request.Builder()
            .url("https://cn.bing.com/")
            .build()

        okHttpClient.newCall(request).enqueue(object :Callback{
            override fun onFailure(call: Call, e: IOException) {
            }

            override fun onResponse(call: Call, response: Response) {
            }
        })

OkHttp使用起來很簡單,先創(chuàng)建OkHttpClient和Request對象,以Request來創(chuàng)建一個RealCall對象,利用它執(zhí)行異步enqueue或者同步execute操作將請求發(fā)送出去,并監(jiān)聽請求失敗或者成功的反饋Callback。

這里有三個主要的類需要說明一下:OkHttpClient、Request以及RealCall。

  • OkHttpClient: 相當于配置中?,可用于發(fā)送 HTTP 請求并讀取其響應。它的配置有很多,例如connectTimeout:建?連接(TCP 或 TLS)的超時時間,readTimeout :發(fā)起請求到讀到響應數(shù)據(jù)的超時時間,Dispatcher:調度器,?于調度后臺發(fā)起的?絡請求,等等。還有其他配置可查看源碼。
  • Request: 一個主要設置網(wǎng)絡請求Url請求方法(GET、POST......)、請求頭、請求body的請求類。
  • RealCall: RealCall是由newCall(Request)方法返回,是OkHttp執(zhí)行請求最核心的一個類之一,用作連接OkHttp的應用程序層和網(wǎng)絡層,也就是將OkHttpClient和Request結合起來,發(fā)起異步和同步請求。

從上面的使用步驟可以看到,OkHttp最后執(zhí)行的是okHttpClient.newCall(request).enqueue,也就是RealCall的enqueue方法,這是一個異步請求,同樣的,也可以執(zhí)行同步請求RealCall.execute()。

RealCall的同步請求最后其實會調用RealCall.getResponseWithInterceptorChain(),而RealCall的異步請求是使用線程池先將請求放置到后臺處理,但是最后還是會調用RealCall.getResponseWithInterceptorChain()來獲取網(wǎng)絡請求的返回值Response。從這里就基本能嗅到網(wǎng)絡請求的核心其實與getResponseWithInterceptorChain()方法有關,那到底如何與服務器連接進行網(wǎng)絡請求的?這個問題就先拋在這,后面再詳細說。

我們先從異步請求enqueue開始,來看異步請求的主要結構。

  類:Dispatcher

  private fun promoteAndExecute(): Boolean {
   ...
    val executableCalls = mutableListOf<AsyncCall>()
    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()
        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添加到雙向隊列readyAsyncCalls中(即準備執(zhí)行但還沒有執(zhí)行的隊列),做請求的準備動作。接著遍歷準備執(zhí)行隊列readyAsyncCalls,尋找符合條件的請求,并將其加入到一個保存有效請求的列表executableCalls和正在執(zhí)行隊列runningAsyncCalls中,而這個篩選條件主要有兩條:

  • if (runningAsyncCalls.size >= this.maxRequests) break:并發(fā)執(zhí)行的請求數(shù)要小于最大的請求數(shù)64。

  • if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) continue :某個主機的并發(fā)請求數(shù)不能超過最大請求數(shù)5

也就是說,當我們的并發(fā)請求量超過64個或者某個主機的的請求數(shù)超過5,則超過的請求暫時不能執(zhí)行,需要等一等才能再加入執(zhí)行隊列中。

將有效的請求篩選出后并保存,立即開始遍歷請求,一一利用調度器Dispatcher里的ExecutorService進行Runnable任務,也就是遍歷后加入到線程池中執(zhí)行這些有效的網(wǎng)絡請求。

 類:RealCall.AsyncCall
 
 override fun run() {
      threadName("OkHttp ${redactedUrl()}") {
        ...
        try {
          val response = getResponseWithInterceptorChain()
          signalledCallback = true
          responseCallback.onResponse(this@RealCall, response)
        } catch (e: IOException) {
            ...
            responseCallback.onFailure(this@RealCall, e)
          }
      }
    }

上面的代碼就是在線程池中執(zhí)行的請求任務,可以看到try-catch塊中有一句 val response = getResponseWithInterceptorChain() 得到網(wǎng)絡請求結果resonse ,將返回的response或者錯誤,通過callback告知給用戶。這個callback也就是一開始OkHttp使用時所注冊監(jiān)聽的callback。

另外,這個方法是不是很熟悉?因為在上面說明三個主要核心類時提到過,RealCall的同步請求或者異步請求,最后都會走到getResponseWithInterceptorChain()這一步。

網(wǎng)絡請求結果response就是通過這個getResponseWithInterceptorChain()方法返回的,那網(wǎng)絡請求結果到底是如何拿到的? 與服務器又是如何交互的呢? 我們就來剖析這個方法的內部結構。

攔截器內部實現(xiàn)

從上面OkHttp的結構分析知道,所有網(wǎng)絡請求的細節(jié)都封裝在getResponseWithInterceptorChain() 這個核心方法中。那我們就來研究一下它的具體實現(xiàn)。

 類:RealCall
  
 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
    )
      ...
    try {
      val response = chain.proceed(originalRequest)
      ...
      return response
    } 
    ...
  }

getResponseWithInterceptorChain()的內部實現(xiàn)是通過一個責任鏈模式來完成,將網(wǎng)絡請求的各個階段封裝到各個鏈條中(即各個攔截器Interceptor),配置好各個Interceptor后將其放在?個List?,然后作為參數(shù),創(chuàng)建?個RealInterceptorChain對象,并調? chain.proceed(request)來發(fā)起請求和獲取響應。

在每一條攔截器中,會先做一些準備動作,例如對該請求進行是否可用的判斷,或者將請求轉換為服務器解析的格式,等等,接著就對請求執(zhí)行chain.proceed(request)。上面提到getResponseWithInterceptorChain()的內部實現(xiàn)是一個責任鏈模式,而chain.proceed(request)的作用就是責任鏈模式的核心所在,將請求移交給下一個攔截器。

OkHttp中連自定義攔截器包括在內,一共有7種攔截器,在這里,網(wǎng)絡請求的細節(jié)就封裝在各個攔截器中,每個攔截器也都有自己的職責,只要把每個攔截器研究清楚,整個網(wǎng)絡請求也就明了了。下面就來一一分析這些攔截器的職責。

7種攔截器的職責

1、用戶自定義攔截器interceptors

用戶自定義攔截器是在所有其他攔截器之前,開發(fā)者可根據(jù)業(yè)務需求進行網(wǎng)絡攔截器的自定義,例如我們常常自定義Token處理攔截器,日志打印攔截器等。

2、RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor是一個請求失敗和重定向時重試的攔截器。它的內部開啟了一個請求循環(huán),每次循環(huán)都會先做一個準備動作(call.enterNetworkInterceptorExchange(request, newExchangeFinder)),這個準備動作最主要的目的在于創(chuàng)建一個ExchangeFinder,為請求尋找可用的Tcl或者Tsl連接以及設置跟連接相關的一些參數(shù),如連接編碼解碼器等。 ExchangeFinder在后面網(wǎng)絡連接時,會詳細說明。

準備工作做好后便開始了一個網(wǎng)絡請求(response = realChain.proceed(request)),這句代碼的目的是為了將請求傳遞給下一個攔截器。同時,會判斷當前請求是否會出錯以及是否需要重定向。如果出錯或者需要重定向,那么就又開始新一輪的循環(huán),直到?jīng)]有出錯和需要重定向為止。

這里出錯和重定向的判斷標準也簡單說一下:

  • 判斷出錯的標準: 利用try-catch塊對請求進行異常捕獲,這里會捕獲RouteException和IOException,并且在出錯后都會先判斷當前請求是否能夠進行重試的操作。
  • 重定向標準: 這里判斷是否需要重定向,是對Response的狀態(tài)碼Code進行審查,當狀態(tài)碼為3xx時,則表示需要重定向,而后創(chuàng)建一個新的request,進行重試操作。

3、BridgeInterceptor

BridgeInterceptor是用來連接應用程序代碼和網(wǎng)絡代碼的一個攔截器。也就是說該攔截器會幫用戶準備好服務器請求所需要的一些配置??赡芏x太抽象,我們就先來看一下一個請求Url所對應的服務器請求頭是怎么樣的?

URL: https://wanandroid.com/wxarticle/chapters/json
方法: GET

那它所對應的請求頭如下:

GET /wxarticle/chapters/json HTTP/1.1
Host: wanandroid.com
Accept: application/json, text/plain, /
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0 xxx
......

你可能會問,BridgeInterceptor攔截器和這個有什么關系?其實BridgeInterceptor的作用就是幫用戶處理網(wǎng)絡請求,它會幫助用戶填寫服務器請求所需要的配置信息,如上面所展示的User-Agent、Connection、Host、Accept-Encoding等。同時也會對請求的結果進行相應處理。

BridgeInterceptor的內部實現(xiàn)主要分為以下三步:

  1. 為用戶網(wǎng)絡請求設置Content-Type、Content-Length、Host、Connection、Cookie等參數(shù),也就是將一般請求轉換為適合服務器解析的格式,以適應服務器端;

  2. 通過 chain.proceed(requestBuilder.build())方法,將轉換后的請求移交給下一個攔截器CacheInterceptor,并接收返回的結果Response;

  3. 對結果Response也進行gzip、Content-Type轉換,以適應應用程序端。

所以說BridgeInterceptor是應用程序和服務器端的一個橋梁。

4、CacheInterceptor

CacheInterceptor是一個處理網(wǎng)絡請求緩存的攔截器。它的內部處理和一些圖片緩存的邏輯相似,首先會判斷是否存在可用的緩存,如果存在,則直接返回緩存,反之,調用chain.proceed(networkRequest)方法將請求移交給下一個攔截器,有了結果后,將結果put到cache中。

5、ConnectInterceptor

ConnectInterceptor是建立連接去請求的攔截器。

  internal fun initExchange(chain: RealInterceptorChain): Exchange {
    ...
    val exchangeFinder = this.exchangeFinder!!
    val codec = exchangeFinder.find(client, chain)
    val result = Exchange(this, eventListener, exchangeFinder, codec)
    this.interceptorScopedExchange = result
    this.exchange = result
    ...

    if (canceled) throw IOException("Canceled")
    return result
  }

從它的源碼可以看到,它首先會通過ExchangeFinder查詢到codec,這個ExchangeFinder是不是很熟悉?在上面RetryAndFollowUpInterceptor分析中,每次循環(huán)都會先做創(chuàng)建ExchangeFinder的準備工作。

而這個codec是什么?它是一個編碼解碼器,來確定是用Http1的方式還是以Http2的方式進行請求。

在找到合適的codec后,作為參數(shù)創(chuàng)建Exchange。Exchange內部涉及了很多網(wǎng)絡連接的實現(xiàn),這個后面再詳細說,我們先看看是如何找到合適的codec?

如何找到可用連接?

找到合適的codec,就必須先找到一個可用的網(wǎng)絡連接,再利用這個可用的連接創(chuàng)建一個新的codec。
為了找到可用的連接,內部使用了大概5種方式進行篩選。

第一種:從連接池中查找

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

嘗試在連接池中查找可用的連接,在遍歷連接池中的連接時,就會判斷每個連接是否可用,而判斷連接是否可用的條件如下:

  1. 請求數(shù)要小于該連接最大能承受的請求數(shù),Http2以下,最大請求數(shù)為1個,并且此連接上可創(chuàng)建新的交換;
  2. 該連接的主機和請求的主機一致;

如果從連接池中拿到了合格的連接connection,則直接返回。

如果沒有拿到,那就進行第二種拿可用連接的方式。

第二種:傳入Route,從連接池中查找

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

第二種依然是從連接池中拿,但是這次不同的是,參數(shù)里傳入了routes,這個routes是包含路由Route的一個List集合,而Route其實指的是連接的IP地址、TCP端口以及代理模式。

而這次從連接池中拿,主要是針對Http2,路由必須共用一個IP地址,此連接的服務器證書必須包含新主機且證書必須與主機匹配。

第三種:自己創(chuàng)建連接

如果前兩次從連接池里都沒有拿到可用連接,那么就自己創(chuàng)建連接。

 val newConnection = RealConnection(connectionPool, route)
    call.connectionToCancel = newConnection
    try {
      newConnection.connect(
          connectTimeout,
          readTimeout,
          writeTimeout,
          pingIntervalMillis,
          connectionRetryEnabled,
          call,
          eventListener
      )
    } 

創(chuàng)建連接其實是內部自己在進行socket,tls的連接,這里拋出一個問題在后面解答:TCP/TLS連接是如何實現(xiàn)的?

自己創(chuàng)建好連接后,又做了一次從連接池中查找的操作。

第四種:多路復用置為true,依然從連接池中查找

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

這次從連接池中查找,requireMultiplexed置為了true,只查找支持多路復用的連接。并且在建立連接后,將新的連接保存到連接池中。

如何找到Http1和Http2的編/解碼器?

上面已經(jīng)分析出尋找可用且健康的連接的幾種方式,那對于codec的創(chuàng)建則需要根據(jù)這些連接進行Http1和Http2的區(qū)分。如果http2Connection不為null,則創(chuàng)建Http2ExchangeCodec,反之創(chuàng)建Http1ExchangeCodec。

找到編解碼器后,我們就回到ConnectInterceptor的一開始,利用編解碼器codec創(chuàng)建了一個Exchange,而這個Exchange的內部其實是利用Http1解碼器或者Http2解碼器,分別進行請求頭的編寫writeRequestHeaders,或者創(chuàng)建Request Body,發(fā)送給服務器。

Exchange初始化成功后,就又將請求移交給了下一個攔截器CallServerInterceptor。

6、CallServerInterceptor

CallServerInterceptor是鏈中最后一個攔截器,主要用于向服務器發(fā)送內容,主要傳輸http的頭部和body信息。

其內部利用上面創(chuàng)建的Exchange進行請求頭編寫,創(chuàng)建Request body,發(fā)送請求,得到結果后,對結果進行解析并回傳。

7、NetworkInterceptor

networkInterceptor也是屬于用戶自定義的一種攔截器,它的位置在ConnectInterceptor之后,CallServerInterceptor之前。我們知道第一個攔截器便是用戶自定義,那和這個有什么區(qū)別呢?

networkInterceptor前面已經(jīng)存在有多個攔截器的使用,在請求到達該攔截器時,請求信息已經(jīng)相當復雜了,其中就包括RetryAndFollowUpInterceptor重試攔截器,經(jīng)過分析知道,每當重試一次,其后面的攔截器也都會被調用一次,這樣就導致networkInterceptor也會被調用多次,而第一個自定義攔截器只會調用一次。當我們需要自定義攔截器時,如token、log,為了資源消耗這一點,一般都是使用第一個。

到這里為止,7種攔截器都分析完成。在分析ConnectInterceptor時拋出了一個問題:TCP/TLS連接是如何實現(xiàn)的?

如何建立TCP/TLS連接?

TCP連接

fun connect(
    connectTimeout: Int,
    readTimeout: Int,
    writeTimeout: Int,
    pingIntervalMillis: Int,
    connectionRetryEnabled: Boolean,
    call: Call,
    eventListener: EventListener
  ) {
    ...

    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) {
        ...
  }
  1. 在connect的內部開啟了一個while循環(huán),可以看到第一步就是route.requiresTunnel()判斷,這個requiresTunnel()方法表示該請求是否使用了Proxy.Type.HTTP代理且目標是Https連接;
  2. 如果是,則創(chuàng)建一個代理隧道連接Tunnel(connectTunnel)。創(chuàng)建這個隧道的目的在于利用Http來代理請求Https;
  3. 如果不是,則直接建立一個TCP連接(connectSocket);
  4. 建立請求協(xié)議。

代理隧道是如何創(chuàng)建的?它的內部會先通過Http代理創(chuàng)建一個TLS的請求,也就是在地址url上增加Host、Proxy-Connection、User-Agent首部。接著最多21次的嘗試,利用connectSocket開啟TCP連接且利用TLS請求創(chuàng)建一個代理隧道。

從這里可以看見,不管是否需要代理隧道,都會開始建立一個TCP連接(connectSocket),那又是如何建立TCP連接的?

 private fun connectSocket(
    connectTimeout: Int,
    readTimeout: Int,
    call: Call,
    eventListener: EventListener
  ) {
    val proxy = route.proxy
    val address = route.address

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

    eventListener.connectStart(call, route.socketAddress, proxy)
    rawSocket.soTimeout = readTimeout
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress, connectTimeout)
    } catch (e: ConnectException) {
      throw ConnectException("Failed to connect to ${route.socketAddress}").apply {
        initCause(e)
      }
    }

   ...
  }

從源碼上看,如果代理類型為直連或者HTTP/FTP代理,則直接創(chuàng)建一個socket,反之,則指定代理類型進行創(chuàng)建。我們看到創(chuàng)建后返回了一個rawSocket,這個就代表著TCP連接。在最后 調用Platform.get().connectSocket,而這實際就是調用socket的connect方法來打開一個TCP連接。

TLS連接

在建立TCP連接或者創(chuàng)建Http代理隧道后,就會開始建立連接協(xié)議(establishProtocol)。

  private fun establishProtocol(
    connectionSpecSelector: ConnectionSpecSelector,
    pingIntervalMillis: Int,
    call: Call,
    eventListener: EventListener
  ) {
    if (route.address.sslSocketFactory == null) {
      if (Protocol.H2_PRIOR_KNOWLEDGE in route.address.protocols) {
        socket = rawSocket
        protocol = Protocol.H2_PRIOR_KNOWLEDGE
        startHttp2(pingIntervalMillis)
        return
      }

      socket = rawSocket
      protocol = Protocol.HTTP_1_1
      return
    }

    eventListener.secureConnectStart(call)
    connectTls(connectionSpecSelector)
    eventListener.secureConnectEnd(call, handshake)

    if (protocol === Protocol.HTTP_2) {
      startHttp2(pingIntervalMillis)
    }
  }
  1. 判斷當前地址是否是HTTPS;
  2. 如果不是HTTPS,則判斷當前協(xié)議是否是明文HTTP2,如果是的則調用startHttp2,開始Http2的握手動作,如果是Http/1.1則直接return返回;
  3. 如果是HTTPS,就開始建立TLS安全協(xié)議連接了(connectTls);
  4. 如果是HTTPS且為HTTP2,除了建立TLS連接外,還會調用startHttp2,開始Http2的握手動作。

在上述第3步時就提到了TLS的連接(connectTls),那我們就來看一下它的內部實現(xiàn):

private fun connectTls(connectionSpecSelector: ConnectionSpecSelector) {
    val address = route.address
    val sslSocketFactory = address.sslSocketFactory
    var success = false
    var sslSocket: SSLSocket? = null
    try {
      // Create the wrapper over the connected socket.
      sslSocket = sslSocketFactory!!.createSocket(
          rawSocket, address.url.host, address.url.port, true /* autoClose */) as SSLSocket

      // Configure the socket's ciphers, TLS versions, and extensions.
      val connectionSpec = connectionSpecSelector.configureSecureSocket(sslSocket)
      if (connectionSpec.supportsTlsExtensions) {
        Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols)
      }

      // Force handshake. This can throw!
      sslSocket.startHandshake()
      // block for session establishment
      val sslSocketSession = sslSocket.session
      val unverifiedHandshake = sslSocketSession.handshake()

      // Verify that the socket's certificates are acceptable for the target host.
      if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) {
        val peerCertificates = unverifiedHandshake.peerCertificates
        if (peerCertificates.isNotEmpty()) {
          val cert = peerCertificates[0] as X509Certificate
          throw SSLPeerUnverifiedException("""
              |Hostname ${address.url.host} not verified:
              |    certificate: ${CertificatePinner.pin(cert)}
              |    DN: ${cert.subjectDN.name}
              |    subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)}
              """.trimMargin())
        } else {
          throw SSLPeerUnverifiedException(
              "Hostname ${address.url.host} not verified (no certificates)")
        }
      }

      val certificatePinner = address.certificatePinner!!

      handshake = Handshake(unverifiedHandshake.tlsVersion, unverifiedHandshake.cipherSuite,
          unverifiedHandshake.localCertificates) {
        certificatePinner.certificateChainCleaner!!.clean(unverifiedHandshake.peerCertificates,
            address.url.host)
      }

      // Check that the certificate pinner is satisfied by the certificates presented.
      certificatePinner.check(address.url.host) {
        handshake!!.peerCertificates.map { it as X509Certificate }
      }

      // Success! Save the handshake and the ALPN protocol.
      val maybeProtocol = if (connectionSpec.supportsTlsExtensions) {
        Platform.get().getSelectedProtocol(sslSocket)
      } else {
        null
      }
      socket = sslSocket
      source = sslSocket.source().buffer()
      sink = sslSocket.sink().buffer()
      protocol = if (maybeProtocol != null) Protocol.get(maybeProtocol) else Protocol.HTTP_1_1
      success = true
    } finally {
      ...
    }
  }

這段代碼很長,具體邏輯我就源碼總結了以下幾點:

  1. 利用請求地址host,端口以及TCP socket共同創(chuàng)建sslSocket;
  2. 為Socket 配置加密算法,TLS版本等;
  3. 調用startHandshake()進行強制握手;
  4. 驗證服務器證書的合法性;
  5. 利用握手記錄進行證書鎖定校驗(Pinner);
  6. 連接成功則保存握手記錄和ALPN協(xié)議。

Tsl加密連接的源碼內容其實與HTTPS所定義的客戶端與服務器通信的規(guī)則一致。創(chuàng)建好sslSocket后就會開始進行client和server的通信操作。

總結

OkHttp大致的請求實現(xiàn)如上面解析,跟著源碼走完了一個請求到處理再到返回結果的整個流程,期間OkHttp做了很多細節(jié)封裝,也使用了很多設計模式,如做核心的責任鏈模式、建造者模式、工廠模式以及策略模式等,都值得我們學習。

以上便是OkHttp的解析,希望這篇文章能幫到您,感謝閱讀。

參考資料

OkHttp源碼深度解析-OPPO互聯(lián)網(wǎng)技術

推薦閱讀

【網(wǎng)絡篇】開發(fā)必備知識點:UDP/TCP協(xié)議

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容