Android重學(xué)系列 OkHttp源碼解析(二)

前言

閱讀過上一篇對(duì)網(wǎng)絡(luò)編程的概述一文后胸完,應(yīng)該對(duì)網(wǎng)絡(luò)編程有一個(gè)大體的概念了公条。從本文開始硼身,將會(huì)開始對(duì)OkHttp的源碼開始進(jìn)行解析。

OkHttp是由square開發(fā)的網(wǎng)絡(luò)請(qǐng)求哭覆享,它是當(dāng)前Android開發(fā)中使用率高達(dá)近100%的網(wǎng)絡(luò)請(qǐng)求庫(kù)佳遂。而且在Android源碼中也內(nèi)置了這個(gè)庫(kù)作為官方的網(wǎng)絡(luò)請(qǐng)求。甚至在一小部分后端也開始使用了撒顿。

關(guān)于前置知識(shí)丑罪,可以閱讀我寫的上篇OKHttp系列解析(一) Okio源碼解析 以及Android重學(xué)系列 Android網(wǎng)絡(luò)編程 總覽

正文

老規(guī)矩,先來看看OkHttp是如何使用的凤壁。

public class GetExample {
  OkHttpClient client = new OkHttpClient();

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

  public static void main(String[] args) throws IOException {
    GetExample example = new GetExample();
    String response = example.run("https://raw.github.com/square/okhttp/master/README.md");
    System.out.println(response);
  }
}

就用官方的例子來看看吩屹,實(shí)際上就是通過Get請(qǐng)求github中README文本。

這個(gè)過程涉及了如下幾個(gè)重要角色:

  • OkHttpClient Okhttp用于請(qǐng)求的執(zhí)行客戶端
  • Request 通過構(gòu)造者設(shè)計(jì)模式拧抖,構(gòu)建的一個(gè)請(qǐng)求對(duì)象
  • Call 是通過 client.newCall 生成的請(qǐng)求執(zhí)行對(duì)象煤搜,當(dāng)執(zhí)行了execute之后才會(huì)真正的開始執(zhí)行網(wǎng)絡(luò)請(qǐng)求
  • Response 是通過網(wǎng)絡(luò)請(qǐng)求后,從服務(wù)器返回的信息都在里面徙鱼。內(nèi)含返回的狀態(tài)碼宅楞,以及代表響應(yīng)消息正文的ResponseBody。

再來看看POST是怎么請(qǐng)求的:

public static final MediaType JSON
    = MediaType.get("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

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

和Get很相似袱吆,只是這個(gè)過程在Request中添加了RequestBody對(duì)象厌衙。這個(gè)RequestBody對(duì)象保存一個(gè)json字符串,并且設(shè)置MediaType為"application/json; charset=utf-8"也就是指正文格式绞绒,可以讓客戶端識(shí)別到應(yīng)該怎么解析這串字符串婶希。

當(dāng)然還有put,delete等蓬衡,和post流程十分相似就沒有必要再展示了喻杈。

我們就以此為鋪墊來看看整個(gè)OkHttp是怎么處理網(wǎng)絡(luò)請(qǐng)求的。下面的原來來自最新的okHttp 4.1版本解析

OkHttp的概況

整個(gè)OkHttp設(shè)計(jì)的很有層次性狰晚,OKHttp把整個(gè)網(wǎng)絡(luò)請(qǐng)求邏輯拆成7個(gè)攔截器筒饰,設(shè)計(jì)成責(zé)任鏈模式的處理。我們可以把Okhttp的網(wǎng)絡(luò)請(qǐng)求大致分為如下幾層壁晒,如下圖:


OkHttp設(shè)計(jì)基礎(chǔ)框架.png
  • 1.retryAndFollowUpInterceptor 重試攔截器
  • 2.BridgeInterceptor 建立網(wǎng)絡(luò)橋梁的攔截器瓷们,主要是為了給網(wǎng)絡(luò)請(qǐng)求時(shí)候,添加各種各種必要參數(shù)秒咐。如Cookie谬晕,Content-type
  • 3.CacheInterceptor 緩存攔截器,主要是為了在網(wǎng)絡(luò)請(qǐng)求時(shí)候携取,根據(jù)返回碼處理緩存攒钳。
  • 4.ConnectInterceptor 鏈接攔截器,主要是為了從鏈接池子中查找可以復(fù)用的socket鏈接雷滋。
  • 5.CallServerInterceptor 真正執(zhí)行網(wǎng)絡(luò)請(qǐng)求的邏輯不撑。
  • 6.Interceptor 用戶定義的攔截器文兢,在重試攔截器之前執(zhí)行
  • 7.networkInterceptors 用戶定義的網(wǎng)絡(luò)攔截器,在CallServerInterceptor(執(zhí)行網(wǎng)絡(luò)請(qǐng)求攔截器)之前運(yùn)行燎孟。

每個(gè)攔截器的處理邏輯可以拆分為兩個(gè)部分禽作,請(qǐng)求部分(處理Request)可應(yīng)答部分(處理Response )。

其中最后兩點(diǎn)是允許用戶進(jìn)行自定義的揩页。本文將會(huì)專門講述前三點(diǎn)的設(shè)計(jì)和思想旷偿。

OkHttp 執(zhí)行流程

我們來看看整個(gè)流程都做了什么?

實(shí)際上OkHttp 執(zhí)行客戶端可以通過建造者模式構(gòu)建出來的爆侣,每一個(gè)成員變量代表了Okhttp中的一種能力,先來看看okhttp.Builder中的成員變量中都有寫什么萍程。

 class Builder constructor() {
    internal var dispatcher: Dispatcher = Dispatcher()
    internal var connectionPool: ConnectionPool = ConnectionPool()
    internal val interceptors: MutableList<Interceptor> = mutableListOf()
    internal val networkInterceptors: MutableList<Interceptor> = mutableListOf()
    internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory()
    internal var retryOnConnectionFailure = true
    internal var authenticator: Authenticator = Authenticator.NONE
    internal var followRedirects = true
    internal var followSslRedirects = true
    internal var cookieJar: CookieJar = CookieJar.NO_COOKIES
    internal var cache: Cache? = null
    internal var dns: Dns = Dns.SYSTEM
    internal var proxy: Proxy? = null
    internal var proxySelector: ProxySelector? = null
    internal var proxyAuthenticator: Authenticator = Authenticator.NONE
    internal var socketFactory: SocketFactory = SocketFactory.getDefault()
    internal var sslSocketFactoryOrNull: SSLSocketFactory? = null
    internal var x509TrustManagerOrNull: X509TrustManager? = null
    internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS
    internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS
    internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier
    internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT
    internal var certificateChainCleaner: CertificateChainCleaner? = null
    internal var callTimeout = 0
    internal var connectTimeout = 10_000
    internal var readTimeout = 10_000
    internal var writeTimeout = 10_000
    internal var pingInterval = 0
    internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE
    internal var routeDatabase: RouteDatabase? = null
...
}
  • Dispatcher Okhttp 請(qǐng)求分發(fā)器,是整個(gè)OkhttpClient的執(zhí)行核心

  • ConnectionPool Okhttp鏈接池兔仰,不過會(huì)把任務(wù)委托給RealConnectionPool處理

  • interceptors: MutableList<Interceptor> 這是代表了okhttp所有的網(wǎng)絡(luò)攔截器

  • networkInterceptors: MutableList<Interceptor> 這種特殊的攔截器茫负,會(huì)生效在CallServerInterceptor真正執(zhí)行網(wǎng)絡(luò)請(qǐng)求的攔截器之前執(zhí)行。

  • eventListenerFactory: EventListener.Factory 這是一個(gè)Event的監(jiān)聽器乎赴,可以監(jiān)聽到如callStart開始執(zhí)行,proxySelectStart 代理選擇開始,proxySelectEnd代理選擇結(jié)束忍法,dnsStartdns開始查找ip映射,dnsEnd查找ip完畢榕吼,connectStart開始鏈接服務(wù)器饿序,secureConnectStart開始安全鏈接服務(wù)器,secureConnectEnd安全鏈接服務(wù)器結(jié)束,connectEnd鏈接結(jié)束等周期

  • authenticator: Authenticator 當(dāng)出現(xiàn)401時(shí)候羹蚣,就會(huì)使用這個(gè)對(duì)象進(jìn)行身份校驗(yàn)原探。如果配置過登錄信息,則會(huì)根據(jù)信息生成新的Request重新請(qǐng)求

  • followRedirects 是否允許重定向

  • cookieJar: CookieJar 這是okhttp 對(duì)cookie持久化的接口

  • cache: Cache 這部分實(shí)際上是okhttp持久化緩存的接口

  • dns: Dns 這部分也就是前文說過的顽素,用于通過URL地址反過來查找ip地址的對(duì)象咽弦,默認(rèn)為DnsSystem.在這里面ip地址是使用java對(duì)象InetAddress來表示。

  • proxy: ProxyProxySelector Okhttp中請(qǐng)求的代理對(duì)象以及自動(dòng)代理選擇器胁出。proxyAuthenticator: Authenticator 代理服務(wù)的401權(quán)限校驗(yàn)處理中心.

  • socketFactory: SocketFactory 默認(rèn)的socket鏈接池

  • sslSocketFactoryOrNull: SSLSocketFactory 用于https的socket鏈接工廠

  • X509TrustManager 用于信任https證書的對(duì)象型型,編碼格式為X.509。換句話說全蝶,就是在TLS握手期間進(jìn)行校驗(yàn)闹蒜,如果校驗(yàn)失敗則鏈接失敗

  • connectionSpecs: List<ConnectionSpec> ConnectionSpec實(shí)際上是用于指定http請(qǐng)求時(shí)候的socket鏈接,如果是Https則是構(gòu)建TLS鏈接時(shí)候向服務(wù)端說明的TLS版本裸诽,密碼套件的類嫂用。

  • protocols: List<Protocol> Okhttp支持的自定義請(qǐng)求協(xié)議集合型凳,內(nèi)含http1.1丈冬,http2.0

  • hostnameVerifier: HostnameVerifier。也是https中的校驗(yàn)甘畅,只是是在握手之后埂蕊,對(duì)host進(jìn)行校驗(yàn)往弓。

  • CertificatePinner 在SSL過程鎖定證書,只允許設(shè)置在這個(gè)類中的證書才能正常的鏈接蓄氧。

  • CertificateChainCleaner 這是一個(gè)證書鏈清理器函似。證書鏈?zhǔn)侵甘褂靡唤M收到信任的證書構(gòu)成的根證書鏈,在TLS握手過程中喉童,會(huì)清理鏈路中的證書撇寞。

  • connectTimeout 鏈接超時(shí)時(shí)間

  • readTimeout 讀取超時(shí)時(shí)間,writeTimeout寫入超時(shí)時(shí)間

  • RealWebSocket管理Okhttp內(nèi)部所有的websocket對(duì)象

  • RouteDatabase 這是okhttp學(xué)習(xí)那些鏈接不上去的黑名單地址堂氯,如果出現(xiàn)請(qǐng)求這些黑名單就會(huì)想辦法蔑担。如果出現(xiàn)了鏈接過程出現(xiàn)異常,之后會(huì)想辦法找到可以替代的路由咽白。

大致上我們需要聊的內(nèi)容都在這里面了啤握。有了對(duì)okhttp的大體的功能之后,我們先來看看Okhttp的運(yùn)行遠(yuǎn)離晶框。

Okhttp 分發(fā)請(qǐng)求入口 newCall

先來看看Okhttp如果需要構(gòu)造一個(gè)可以請(qǐng)求的對(duì)象需要調(diào)用如下方法

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

生成一個(gè)RealCall對(duì)象排抬。其中第三個(gè)參數(shù)說迷宮這個(gè)請(qǐng)求是否是websocket。

得到RealCall對(duì)象之后授段,一般有兩種選擇進(jìn)行網(wǎng)絡(luò)請(qǐng)求:

  • 1.如官方給出的方法蹲蒲,調(diào)用excute方法,把執(zhí)行設(shè)置在RealCall的Request對(duì)象畴蒲。很少使用excute的方式悠鞍,開發(fā)中更多的下面這種方式。

  • 2.使用方法enqueue 把請(qǐng)求分發(fā)按照隊(duì)列方式順序消費(fèi)執(zhí)行模燥。

RealCall excute

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

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

首先回調(diào)上面設(shè)置的EventListenter的callStart方法咖祭,接著調(diào)用Dispatcherexecute方法,把當(dāng)前的RealCall傳入蔫骂,最后調(diào)用getResponseWithInterceptorChain 阻塞獲取從網(wǎng)絡(luò)請(qǐng)求響應(yīng)后的結(jié)果么翰,最后調(diào)用Dispatcherfinished 方法

RealCall enqueue

  override fun enqueue(responseCallback: Callback) {
    check(executed.compareAndSet(false, true)) { "Already Executed" }

    callStart()
    client.dispatcher.enqueue(AsyncCall(responseCallback))
  }

整個(gè)流程就很簡(jiǎn)單了,先給當(dāng)前的RealCall的executed原子類設(shè)置為true后辽旋,回調(diào)callStart方法浩嫌,最后調(diào)用把外面監(jiān)聽響應(yīng)數(shù)據(jù)的接口responseCallback封裝成AsyncCall后作為參數(shù)傳入 Dispatcherenqueue方法。

我們主要來看enqueue方法的設(shè)計(jì)补胚,execute的使用場(chǎng)景確實(shí)不多码耐。

AsyncCall

先來看看AysncCall都做了什么?

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

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

AysncCall是一個(gè)Runnable對(duì)象溶其。一旦線程開始執(zhí)行的時(shí)候就會(huì)運(yùn)行其中的run方法骚腥。而executeOn 方法這是允許任何的線程池執(zhí)行該AsyncCall對(duì)象,接著調(diào)用其中的run方法瓶逃。

在run方法中依次執(zhí)行了如下步驟:

    1. getResponseWithInterceptorChain 執(zhí)行Okhttp中所有的攔截器束铭,并獲得對(duì)象response廓块。
  • 2.調(diào)用responseCallback的onResponse 方法把response對(duì)象回調(diào)出去。
  • 3.如果遇到IOException異常則返回responseCallback.onFailure
  • 4.其他異常則調(diào)用cancel 方法取消請(qǐng)求后契沫,回調(diào)responseCallback.onFailure
  • 5.調(diào)用Dispatcher的finished 方法結(jié)束執(zhí)行带猴。

Dispatcher enqueue

  private val readyAsyncCalls = ArrayDeque<AsyncCall>()
  private val runningAsyncCalls = ArrayDeque<AsyncCall>()
  private val runningSyncCalls = ArrayDeque<RealCall>()

  internal fun enqueue(call: AsyncCall) {
    synchronized(this) {
      readyAsyncCalls.add(call)

      if (!call.call.forWebSocket) {
        val existingCall = findExistingCallWithHost(call.host)
        if (existingCall != null) call.reuseCallsPerHostFrom(existingCall)
      }
    }
    promoteAndExecute()
  }

  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
  }

這里有三個(gè)隊(duì)列十分重要:

  • 1.readyAsyncCalls 異步執(zhí)行的準(zhǔn)備隊(duì)列
  • 2.runningAsyncCalls 正在異步執(zhí)行隊(duì)列
  • 3.runningSyncCalls 正在同步執(zhí)行隊(duì)列

清楚這三個(gè)隊(duì)列的功能后,下面就很好理解了:

  • 1.首先把當(dāng)前的AsyncCall 添加到readyAsyncCalls 預(yù)備執(zhí)行隊(duì)列中懈万。這是一個(gè) ArrayDeque對(duì)象拴清,這是一個(gè)雙端隊(duì)列,可以作為棧使用会通。這個(gè)隊(duì)列看起來像一個(gè)鏈表贷掖,實(shí)質(zhì)上內(nèi)部是一個(gè)數(shù)組(每一次擴(kuò)容為原來的2倍,并且初始值為8渴语,內(nèi)用head和tail標(biāo)示整個(gè)隊(duì)列的范圍苹威,只允許操作頭部和尾部).

  • 2.其次通過findExistingCallWithHost 查找是否有host相同的AsyncCall 在runningAsyncCallsreadyAsyncCalls,存在則調(diào)用reuseCallsPerHostFrom復(fù)用這個(gè)AsyncCall,也就是復(fù)用這個(gè)請(qǐng)求配置驾凶,沒必要重新構(gòu)建全新的對(duì)象牙甫。

  • 3.promoteAndExecute 開始通過線程池執(zhí)行保存在隊(duì)列中的AsyncCall

promoteAndExecute

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

@get:Synchronized var maxRequests = 64
    set(maxRequests) {
      require(maxRequests >= 1) { "max < 1: $maxRequests" }
      synchronized(this) {
        field = maxRequests
      }
      promoteAndExecute()
    }

  @get:Synchronized var maxRequestsPerHost = 5
    set(maxRequestsPerHost) {
      require(maxRequestsPerHost >= 1) { "max < 1: $maxRequestsPerHost" }
      synchronized(this) {
        field = maxRequestsPerHost
      }
      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.
        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
  }

SynchronousQueue是一個(gè)單對(duì)單的消費(fèi)者生產(chǎn)者模式數(shù)據(jù)結(jié)構(gòu),必須要要有一個(gè)Request對(duì)應(yīng)一個(gè)Data模式的數(shù)據(jù)結(jié)構(gòu)调违。更多的Request會(huì)進(jìn)入阻塞等待窟哺,知道有Data來匹配。

這里面設(shè)置一個(gè)默認(rèn)的線程池技肩,其中線程的調(diào)度隊(duì)列就是通過SynchronousQueue處理成一對(duì)一的消費(fèi)者生產(chǎn)者模式且轨。

而在一次請(qǐng)求中一次性消費(fèi)AsyncCall最大的數(shù)量默認(rèn)為64,且這個(gè)AysncCall復(fù)用Host的次數(shù)要小于5次虚婿。

最后就會(huì)添加到executableCalls和runningAsyncCalls 加入到執(zhí)行隊(duì)列中咧党。最后遍歷一次executableCalls中AsyncCall的executeOn震叮,執(zhí)行其中的run方法啦租。

那么我們需要看看整個(gè)Okhttp執(zhí)行的核心方法getResponseWithInterceptorChain.

getResponseWithInterceptorChain

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

首先構(gòu)造一個(gè)可變的 Interceptor集合农尖。這個(gè)集合的順序其實(shí)就是指代了整個(gè)okhttp攔截器的執(zhí)行順序。

整個(gè)流程如圖:


OkHttp設(shè)計(jì)基礎(chǔ)框架.png

注意如果是websocket的話剧浸,就不會(huì)執(zhí)行用戶自定定義的NetworkInterceptor锹引。

然后使用RealInterceptorChain包裹所有的攔截器后,執(zhí)行RealInterceptorChain.proceed方法執(zhí)行Request唆香。

RealInterceptorChain 攔截器管理器

  internal fun copy(
    index: Int = this.index,
    exchange: Exchange? = this.exchange,
    request: Request = this.request,
    connectTimeoutMillis: Int = this.connectTimeoutMillis,
    readTimeoutMillis: Int = this.readTimeoutMillis,
    writeTimeoutMillis: Int = this.writeTimeoutMillis
  ) = RealInterceptorChain(call, interceptors, index, exchange, request, connectTimeoutMillis,
      readTimeoutMillis, writeTimeoutMillis)

  override fun proceed(request: Request): Response {
    check(index < interceptors.size)

    calls++

...

    // 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")
...
    return response
  }

這個(gè)過程可以看到實(shí)際上又copy可一個(gè)RealInterceptorChain 對(duì)象嫌变,不過index 下標(biāo)增加了1,此時(shí)就會(huì)繼續(xù)執(zhí)行對(duì)應(yīng)下標(biāo)的Interceptor躬它。

換句話說腾啥,每當(dāng)一個(gè)攔截器走完一個(gè)Request的處理流程就會(huì)生成一個(gè)新的RealInterceptorChain并且下標(biāo)+1,時(shí)候下一個(gè)攔截器的intercept方法。不斷的迭代下去碑宴。

這種思路,在我寫的OkRxCache的庫(kù)中有使用桑谍,很實(shí)用延柠。能夠把復(fù)雜且層級(jí)結(jié)構(gòu)分明的邏輯拆分出來,做到可組裝的效果锣披。

那么就來看看第一個(gè)攔截器retryAndFollowUpInterceptor 做了什么贞间?

retryAndFollowUpInterceptor 重試攔截器

整個(gè)攔截器可以劃分為2個(gè)部分進(jìn)行理解,以方法realChain.proceed作為分割線雹仿,上部分為請(qǐng)求邏輯增热,下部分為應(yīng)答處理邏輯

retryAndFollowUpInterceptor 處理請(qǐng)求

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

          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
        }

    ...
    }
  }
  • 1.調(diào)用RealCall的enterNetworkInterceptorExchange方法實(shí)例化一個(gè)ExchangeFinder在RealCall對(duì)象中。
  • 2.執(zhí)行RealCall的proceed 方法胧辽,進(jìn)入下一個(gè)攔截器峻仇,進(jìn)行下一步的請(qǐng)求處理。
  • 3.如果出現(xiàn)路由異常邑商,則通過recover方法校驗(yàn)摄咆,當(dāng)前的鏈接是否可以重試,不能重試則拋出異常人断,離開當(dāng)前的循環(huán)吭从。
recover 校驗(yàn)鏈接是否可以重試
  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
  }
  • 1.如果Okhttp 的retryOnConnectionFailure為false,禁止重試則返回false

  • 2.requestIsOneShot 如果requestIsOneShot校驗(yàn)的是RequestBody的isOneShot是否是true, isOneShot默認(rèn)是false恶迈。說明一個(gè)請(qǐng)求正文可以多次請(qǐng)求(多次請(qǐng)求的情況如408 客戶端超時(shí);401和407 權(quán)限異成穑可以通過頭部進(jìn)行滿足;503 服務(wù)端異常,但是頭部的retry-After為0可以進(jìn)行重試)暇仲。

  • 3.isRecoverable 校驗(yàn)當(dāng)前的異常是否是可恢復(fù)的異常步做。ProtocolException 協(xié)議異常返回false;InterruptedIOException io讀寫異常同時(shí)是socket鏈接超時(shí)異衬胃剑可以重試辆床;SSLHandshakeException https握手時(shí)候的異常同時(shí)是校驗(yàn)異常CertificateException會(huì)返回false; SSLPeerUnverifiedException證書校驗(yàn)異常則返回false桅狠。

retryAndFollowUpInterceptor 處理應(yīng)答

   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.每一次循環(huán)都會(huì)獲取上次應(yīng)答數(shù)據(jù)作為本次重定向或者權(quán)限詢問的參數(shù)讼载。
  • 2.followUpRequest 根據(jù)當(dāng)前的響應(yīng)體,更新請(qǐng)求體中的內(nèi)容中跌。
  • 3.如果當(dāng)前的仇視次數(shù)超過了20次咨堤,就會(huì)拋出異常,跳出循環(huán)漩符。
private const val MAX_FOLLOW_UPS = 20

其實(shí)整個(gè)重試攔截器最為核心的內(nèi)容就是followUpRequest方法一喘。

followUpRequest 根據(jù)應(yīng)答重試請(qǐng)求處理

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

這個(gè)過程處理了幾個(gè)HttpCode狀態(tài)碼:

    public static final int HTTP_PROXY_AUTH = 407;
    public static final int HTTP_UNAUTHORIZED = 401;
    public static final int HTTP_CLIENT_TIMEOUT = 408;
    public static final int HTTP_UNAVAILABLE = 503;
    const val HTTP_TEMP_REDIRECT = 307
    const val HTTP_PERM_REDIRECT = 308
    const val HTTP_MISDIRECTED_REQUEST = 421

下面我們一個(gè)個(gè)的解析:

狀態(tài)碼407 代理需要校驗(yàn)身份
        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)

如果狀態(tài)代碼是407,此時(shí)會(huì)校驗(yàn)當(dāng)前路由的代理模式是不是Http協(xié)議,不是則拋出異常凸克,沒有使用代理時(shí)候接受到了407议蟆。如果是,則通過Authenticator對(duì)響應(yīng)體進(jìn)行校驗(yàn)萎战。

此時(shí)默認(rèn)是設(shè)置沒有任何行為的代理權(quán)限校驗(yàn)器咐容。如果設(shè)置了就會(huì)調(diào)用Authenticator進(jìn)行校驗(yàn),這里面可以設(shè)置根據(jù)路由和響應(yīng)體獲取到對(duì)應(yīng)的校驗(yàn)處理蚂维〈亮#可以來看看OkHttp內(nèi)置的一個(gè)JavaNetAuthenticator 做了什么?

  @Throws(IOException::class)
  override fun authenticate(route: Route?, response: Response): Request? {
    val challenges = response.challenges()
    val request = response.request
    val url = request.url
    val proxyAuthorization = response.code == 407
    val proxy = route?.proxy ?: Proxy.NO_PROXY

    for (challenge in challenges) {
      if (!"Basic".equals(challenge.scheme, ignoreCase = true)) {
        continue
      }

      val dns = route?.address?.dns ?: defaultDns
      val auth = if (proxyAuthorization) {
        val proxyAddress = proxy.address() as InetSocketAddress
        Authenticator.requestPasswordAuthentication(
            proxyAddress.hostName,
            proxy.connectToInetAddress(url, dns),
            proxyAddress.port,
            url.scheme,
            challenge.realm,
            challenge.scheme,
            url.toUrl(),
            Authenticator.RequestorType.PROXY
        )
      } else {
        Authenticator.requestPasswordAuthentication(
            url.host,
            proxy.connectToInetAddress(url, dns),
            url.port,
            url.scheme,
            challenge.realm,
            challenge.scheme,
            url.toUrl(),
            Authenticator.RequestorType.SERVER
        )
      }

      if (auth != null) {
        val credentialHeader = if (proxyAuthorization) "Proxy-Authorization" else "Authorization"
        val credential = Credentials.basic(
            auth.userName, String(auth.password), challenge.charset)
        return request.newBuilder()
            .header(credentialHeader, credential)
            .build()
      }
    }

    return null // No challenges were satisfied!
  }

核心其實(shí)很簡(jiǎn)單虫啥,就是通過Authenticator.requestPasswordAuthentication獲得一個(gè)PasswordAuthentication對(duì)象蔚约。從這個(gè)對(duì)象中獲取對(duì)應(yīng)代理設(shè)置的權(quán)限賬號(hào)密碼,并且設(shè)置到Authorization或者Proxy-Authorization頭部key中涂籽,重試時(shí)候會(huì)帶上這個(gè)頭部放到新的請(qǐng)求中苹祟。

狀態(tài)碼 401 請(qǐng)求要求用戶的身份認(rèn)證
return client.authenticator.authenticate(route, userResponse)

說明此時(shí)需要通過Authenticator校驗(yàn)身份,可以發(fā)送自己的用戶名和密碼過去進(jìn)行校驗(yàn)评雌。

狀態(tài)碼300苔咪,301,302柳骄,303团赏,307,308
return buildRedirectRequest(userResponse, method)

狀態(tài)碼30X 系列一般是發(fā)生了資源變動(dòng)處理的行為耐薯。如重定向跳轉(zhuǎn)等舔清。

  • 300 是指有多種選擇。請(qǐng)求的資源包含多個(gè)位置
  • 301 請(qǐng)求的資源已經(jīng)永久移動(dòng)了 會(huì)自動(dòng)重定向
  • 302 臨時(shí)移動(dòng)曲初,資源是臨時(shí)轉(zhuǎn)移了体谒,客戶端可以沿用原來的url
  • 303 查看其他地址,可301類似
  • 307 臨時(shí)重定向臼婆,GET請(qǐng)求的重定向
  • 308 和307類似也是臨時(shí)重定向
  private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
    if (!client.followRedirects) return null

    val location = userResponse.header("Location") ?: return null
    val url = userResponse.request.url.resolve(location) ?: return null

    val sameScheme = url.scheme == userResponse.request.url.scheme
    if (!sameScheme && !client.followSslRedirects) return null

    val requestBuilder = userResponse.request.newBuilder()
    if (HttpMethod.permitsRequestBody(method)) {
      val responseCode = userResponse.code
      val maintainBody = HttpMethod.redirectsWithBody(method) ||
          responseCode == HTTP_PERM_REDIRECT ||
          responseCode == HTTP_TEMP_REDIRECT
      if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
        requestBuilder.method("GET", null)
      } else {
        val requestBody = if (maintainBody) userResponse.request.body else null
        requestBuilder.method(method, requestBody)
      }
      if (!maintainBody) {
        requestBuilder.removeHeader("Transfer-Encoding")
        requestBuilder.removeHeader("Content-Length")
        requestBuilder.removeHeader("Content-Type")
      }
    }

    if (!userResponse.request.url.canReuseConnectionFor(url)) {
      requestBuilder.removeHeader("Authorization")
    }

    return requestBuilder.url(url).build()
  }
  • 1.先從響應(yīng)頭抒痒,取出Locationkey對(duì)應(yīng)的值,而這個(gè)值就是重定向之后的url路徑颁褂。

  • 2.并且校驗(yàn)這個(gè)請(qǐng)求是否是GET或者HEAD.

    • 2.1.如果不是故响,則請(qǐng)求方式不是PROPFIND 且狀態(tài)碼不是307或者308狀態(tài)碼,則強(qiáng)制設(shè)置請(qǐng)求方式為GET
    • 2.2.否則則判斷請(qǐng)求狀態(tài)碼是307或者308,或者請(qǐng)求方式是PROPFIND,那么繼承之前設(shè)置的請(qǐng)求體颁独。
  • 3.不是307或者308且不是PROPFIND彩届,則清掉Header中的Content-Length,Transfer-Encoding,Content-Type

  • 4.如果之前的請(qǐng)求和本次請(qǐng)求的host(主機(jī))和port(端口)一致,則不需要Authorization校驗(yàn)身份誓酒。

狀態(tài)碼408 服務(wù)器等待客戶端發(fā)送請(qǐng)求超時(shí)處理
        if (!client.retryOnConnectionFailure) {
          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) {
          return null
        }

        if (retryAfter(userResponse, 0) > 0) {
          return null
        }

        return userResponse.request

狀態(tài)碼408不常見樟蠕,但是在HAProxy中會(huì)比較常見。這種情況說明我們可以重復(fù)的進(jìn)行沒有修改過的請(qǐng)求(甚至是非冪等請(qǐng)求)。

HAProxy 是一種高可用寨辩,負(fù)載均衡的吓懈,基于TCP和HTTP的應(yīng)用程序代理。十分合適負(fù)載十分大的服務(wù)器靡狞。如github耻警,stackflow等都集成了。它實(shí)現(xiàn)了事件驅(qū)動(dòng)耍攘,單一進(jìn)程模型支持十分大的(超越一個(gè)進(jìn)程因內(nèi)存模型而限制的線程數(shù)目)。

實(shí)際上還是那老一套的畔勤,基于系統(tǒng)調(diào)用epoll蕾各,select,poll等實(shí)現(xiàn)。

這一段代碼的邏輯校驗(yàn)了如下邏輯:

  • 1.當(dāng)前okhttp是否允許重試
  • 2.請(qǐng)求體是否允許重復(fù)發(fā)送
  • 3.是否已經(jīng)重試了庆揪,且重試的狀態(tài)是否還是408
  • 4.通過retryAfter獲取響應(yīng)頭部信息Retry-After(頭部存在該key式曲,則設(shè)置為key的內(nèi)容否則設(shè)置為0.不存在該key設(shè)置為INT的最大數(shù)值)。拿到重試時(shí)間后判斷是否大于0缸榛,大于0說明此時(shí)返回一個(gè)空的請(qǐng)求對(duì)象吝羞,Okhttp將不會(huì)處理拋給業(yè)務(wù)層自己處理。
狀態(tài)碼 503 由于服務(wù)器的異常導(dǎo)致無法完成客戶端的請(qǐng)求
        val priorResponse = userResponse.priorResponse
        if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
          return null
        }

        if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
          return userResponse.request
        }
        return null

如果上一次的請(qǐng)求已經(jīng)是503了内颗,就沒必要重復(fù)請(qǐng)求了钧排。且如果Retry-After 設(shè)置為0,說明需要立即重復(fù)請(qǐng)求均澳,才會(huì)重新請(qǐng)求恨溜,其他情況下只會(huì)放棄請(qǐng)求。

狀態(tài)碼421 超出了服務(wù)器最大連接數(shù)找前,需要重新請(qǐng)求
        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
      }

這種情況下糟袁,只要requestBody允許重復(fù)發(fā)送,且host發(fā)生了變化躺盛,則重新返回請(qǐng)求體重新請(qǐng)求项戴。這種情況下,一般是指Http 2.0協(xié)議槽惫。這種情況下周叮,只要鏈接鏈接的得失同一個(gè)服務(wù)器,且RealConntection是合法的界斜,如果服務(wù)器返回了421狀態(tài)碼则吟,可以復(fù)用這個(gè)流。

BridgeInterceptor 橋接攔截器

BridgeInterceptor 處理請(qǐng)求體頭部

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

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

在請(qǐng)求到下一個(gè)攔截器之前锄蹂,做了如下的事情:

  • 1.設(shè)置頭部的Content-Type.說明內(nèi)容類型是什么
  • 2.如果contentLength大于等于0氓仲,則設(shè)置頭部的Content-Length(說明內(nèi)容大小是多少);否則設(shè)置頭部的Transfer-Encodingchunked(說明傳輸編碼為分塊傳輸)
  • 3.如果Host不存在,設(shè)置頭部的Host(在Http 1.1之后出現(xiàn)敬扛,可以通過同一個(gè)URL訪問到不同主機(jī)晰洒,從而實(shí)現(xiàn)服務(wù)器虛擬服務(wù)器的負(fù)載均衡。如果1.1之后不設(shè)置就會(huì)返回404)啥箭。
  • 4.如果Connection不存在谍珊,設(shè)置頭部的ConnectionKeep-Alive(代表鏈接狀態(tài)需要保持活躍)
  • 5.如果Accept-EncodingRange為空,則強(qiáng)制設(shè)置Accept-Encodinggzip(說明請(qǐng)求將會(huì)以gzip方式壓縮)
  • 6.從CookieJar的緩存中取出cookie設(shè)置到頭部的Cookie
  • 7.如果User-Agent為空急侥,則設(shè)置User-Agent到頭部

Cookie是什么砌滞?Cookie是http協(xié)議中用于追蹤用戶會(huì)話的機(jī)制。注意Http協(xié)議是無狀態(tài)的(但是底層構(gòu)成http協(xié)議的tcp協(xié)議是有狀態(tài)用于控制數(shù)據(jù)的正確性)坏怪。一個(gè)用戶所有的請(qǐng)求都是同屬一個(gè)會(huì)話贝润。在http協(xié)議中,服務(wù)器為了得知客戶端的身份會(huì)給每一個(gè)客戶端分配一個(gè)cookie铝宵,同時(shí)在服務(wù)器有一個(gè)sessionID進(jìn)行對(duì)應(yīng)打掘。都會(huì)保存在服務(wù)器的Map中。正是有這個(gè)上下文才會(huì)正確的知道該用戶的的請(qǐng)求狀態(tài)鹏秋。

BridgeInterceptor 處理響應(yīng)體


    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.讀取響應(yīng)頭上的信息尊蚁,把cookie'保存到CookieJar中
  • 2.如果此時(shí)是Content-Encoding是gzip的內(nèi)容壓縮格式,則獲取當(dāng)前的響應(yīng)體的內(nèi)容侣夷,并根據(jù) Content-Type把壓縮格式還原横朋,重新設(shè)置到響應(yīng)體中。

CacheInterceptor 緩存攔截器

CacheInterceptor 緩存攔截器處理請(qǐng)求

  override fun intercept(chain: Interceptor.Chain): Response {
    val call = chain.call()
    val cacheCandidate = cache?.get(chain.request())

    val now = System.currentTimeMillis()

    val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
    val networkRequest = strategy.networkRequest
    val cacheResponse = strategy.cacheResponse

    cache?.trackResponse(strategy)
    val listener = (call as? RealCall)?.eventListener ?: EventListener.NONE

    if (cacheCandidate != null && cacheResponse == null) {
      // The cache candidate wasn't applicable. Close it.
      cacheCandidate.body?.closeQuietly()
    }

    // If we're forbidden from using the network and the cache is insufficient, fail.
    if (networkRequest == null && cacheResponse == null) {
      return Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(HTTP_GATEWAY_TIMEOUT)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build().also {
            listener.satisfactionFailure(call, it)
          }
    }

    // If we don't need the network, we're done.
    if (networkRequest == null) {
      return cacheResponse!!.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build().also {
            listener.cacheHit(call, it)
          }
    }

    if (cacheResponse != null) {
      listener.cacheConditionalHit(call, cacheResponse)
    } else if (cache != null) {
      listener.cacheMiss(call)
    }

    var networkResponse: Response? = null
    try {
      networkResponse = chain.proceed(networkRequest)
    } finally {
      // If we're crashing on I/O or otherwise, don't leak the cache body.
      if (networkResponse == null && cacheCandidate != null) {
        cacheCandidate.body?.closeQuietly()
      }
    }

...
  }
  • 1.先根據(jù)請(qǐng)求體的狀態(tài)通過CacheStrategy獲取到是否需要緩存百拓,從而獲得本次網(wǎng)絡(luò)請(qǐng)求的請(qǐng)求體以及緩存的響應(yīng)體叶撒,關(guān)鍵就是
val cacheCandidate = cache?.get(chain.request())
 CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
  • 2.如果兩者都獲取不到,此時(shí)會(huì)返回協(xié)議http 1.1耐版,錯(cuò)誤代碼為504祠够,消息是禁止了網(wǎng)絡(luò)請(qǐng)求但是緩存響應(yīng)不存在的應(yīng)答消息體。

  • 3.如果只是網(wǎng)絡(luò)請(qǐng)求不存在粪牲,而緩存的響應(yīng)體存在古瓤,則根據(jù)緩存的響應(yīng)構(gòu)造出響應(yīng)體對(duì)象,返回cacheHit會(huì)愛到并返回腺阳。

  • 4.如果網(wǎng)絡(luò)請(qǐng)求存在落君,則正常的進(jìn)入下一個(gè)攔截器中。

Cache get 從LRUCache中獲取緩存
@JvmStatic
    fun key(url: HttpUrl): String = url.toString().encodeUtf8().md5().hex()

  internal fun get(request: Request): Response? {
    val key = key(request.url)
    val snapshot: DiskLruCache.Snapshot = try {
      cache[key] ?: return null
    } catch (_: IOException) {
      return null // Give up because the cache cannot be read.
    }

    val entry: Entry = try {
      Entry(snapshot.getSource(ENTRY_METADATA))
    } catch (_: IOException) {
      snapshot.closeQuietly()
      return null
    }

    val response = entry.response(snapshot)
    if (!entry.matches(request, response)) {
      response.body?.closeQuietly()
      return null
    }

    return response
  }
  • 1.首先把url路徑轉(zhuǎn)化成utf-8亭引,并且md5一下拿到摘要绎速,并調(diào)用hex獲取摘要的16進(jìn)制的字符串,這個(gè)字符串就是LRUCache的key

  • 2.通過key拿到Cache中DiskLruCache.Snapshot對(duì)象焙蚓,每一個(gè)Snapshot就是LRUCache的緩存單位纹冤。每一個(gè)緩存單位中都緩存一個(gè)文件洒宝,在cache的get操作方法重寫中,會(huì)讀取數(shù)據(jù)為Okio的Source

  operator fun get(key: String): Snapshot? {
    initialize()

    checkNotClosed()
    validateKey(key)
    val entry = lruEntries[key] ?: return null
    val snapshot = entry.snapshot() ?: return null

    redundantOpCount++
    journalWriter!!.writeUtf8(READ)
        .writeByte(' '.toInt())
        .writeUtf8(key)
        .writeByte('\n'.toInt())
    if (journalRebuildRequired()) {
      cleanupQueue.schedule(cleanupTask)
    }

    return snapshot
  }
  • 3.把數(shù)據(jù)轉(zhuǎn)化為Respone 響應(yīng)體
CacheStrategy compute 計(jì)算獲取請(qǐng)求對(duì)應(yīng)的緩存
    fun compute(): CacheStrategy {
      val candidate = computeCandidate()

      // We're forbidden from using the network and the cache is insufficient.
      if (candidate.networkRequest != null && request.cacheControl.onlyIfCached) {
        return CacheStrategy(null, null)
      }

      return candidate
    }

    /** Returns a strategy to use assuming the request can use the network. */
    private fun computeCandidate(): CacheStrategy {
      // No cached response.
      if (cacheResponse == null) {
        return CacheStrategy(request, null)
      }

      // Drop the cached response if it's missing a required handshake.
      if (request.isHttps && cacheResponse.handshake == null) {
        return CacheStrategy(request, null)
      }

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

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

從LRUCache中獲取到緩存的應(yīng)答數(shù)據(jù)萌京,將會(huì)做如下的Header的校驗(yàn)雁歌。

先來看看CacheStagty的初始化:

    init {
      if (cacheResponse != null) {
        this.sentRequestMillis = cacheResponse.sentRequestAtMillis
        this.receivedResponseMillis = cacheResponse.receivedResponseAtMillis
        val headers = cacheResponse.headers
        for (i in 0 until headers.size) {
          val fieldName = headers.name(i)
          val value = headers.value(i)
          when {
            fieldName.equals("Date", ignoreCase = true) -> {
              servedDate = value.toHttpDateOrNull()
              servedDateString = value
            }
            fieldName.equals("Expires", ignoreCase = true) -> {
              expires = value.toHttpDateOrNull()
            }
            fieldName.equals("Last-Modified", ignoreCase = true) -> {
              lastModified = value.toHttpDateOrNull()
              lastModifiedString = value
            }
            fieldName.equals("ETag", ignoreCase = true) -> {
              etag = value
            }
            fieldName.equals("Age", ignoreCase = true) -> {
              ageSeconds = value.toNonNegativeInt(-1)
            }
          }
        }
      }
    }

分別取出了緩存頭部中的:

  • 1.Date 緩存應(yīng)答的發(fā)送到客戶端的時(shí)間
  • 2.Expires 代表應(yīng)答可以存活時(shí)間
  • 3.Last-Modified 代表服務(wù)器用來校驗(yàn)客戶端的請(qǐng)求是否是最新的時(shí)間,不是則返回304
  • 4.ETag 用于記錄當(dāng)前請(qǐng)求頁(yè)面狀態(tài)時(shí)效的token
  • 5.Age 代理服務(wù)器用自己去緩存應(yīng)答的時(shí)候知残,該頭部代表從誕生到現(xiàn)在多長(zhǎng)時(shí)間

獲得這些基礎(chǔ)數(shù)據(jù)后靠瞎,上面的compute就是根據(jù)這5個(gè)標(biāo)志位進(jìn)行計(jì)算。

  • 1.cacheResponseAge 計(jì)算這個(gè)緩存真正緩存時(shí)間方式:

接受消耗的時(shí)間 = max(Response抵達(dá)客戶端的時(shí)間 - Response從服務(wù)端發(fā)出的時(shí)間(Date字段),Age字段,0)
Response來回時(shí)間 = Response抵達(dá)客戶端的時(shí)間 - 客戶端發(fā)送的時(shí)間
緩存時(shí)間 = 當(dāng)前時(shí)間 - Response抵達(dá)客戶端的時(shí)間
當(dāng)前應(yīng)答真實(shí)緩存時(shí)間 = 接受消耗的時(shí)間 + Response來回時(shí)間 + 緩存時(shí)間

  • 2.computeFreshnessLifetime 遵循如下的邏輯計(jì)算緩存有效性時(shí)間段

優(yōu)先取出CacheControl的maxAge 字段求妹,存在則返回
其次取出expires 字段乏盐,expires - (Date字段 或者 Response抵達(dá)客戶端時(shí)間)
最后取出lastModified字段, (Date字段 或者 Response抵達(dá)客戶端時(shí)間) - lastModified(上次修改)

這樣計(jì)算差值就能大致獲得這個(gè)緩存比較精確的有效時(shí)間制恍。

  • 3.cacheResponseAge 獲取到的緩存的時(shí)間和 computeFreshnessLifetime計(jì)算出來的緩存時(shí)效性父能,以及在okhttp設(shè)置的最小緩存minFreshSeconds時(shí)效,通過下面簡(jiǎn)單的計(jì)算:

cacheResponseAge + minFreshSeconds > computeFreshnessLifetime

就能知道當(dāng)前緩存是否有效吧趣,從而決定是否返回一個(gè)CacheResponse上去法竞。

CacheInterceptor 緩存攔截器進(jìn)行網(wǎng)絡(luò)請(qǐng)求后的處理

    // If we have a cache response too, then we're doing a conditional get.
    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 response = networkResponse!!.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build()

    if (cache != null) {
      if (response.promisesBody() && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        val cacheRequest = cache.put(response)
        return cacheWritingResponse(cacheRequest, response).also {
          if (cacheResponse != null) {
            // This will log a conditional cache miss only.
            listener.cacheMiss(call)
          }
        }
      }

      if (HttpMethod.invalidatesCache(networkRequest.method)) {
        try {
          cache.remove(networkRequest)
        } catch (_: IOException) {
          // The cache cannot be written.
        }
      }
    }

    return response
  • 1.如果當(dāng)前緩存響應(yīng)體存在耙厚,且當(dāng)前網(wǎng)絡(luò)請(qǐng)求返回的狀態(tài)代碼為304强挫。在http協(xié)議中說明此時(shí)請(qǐng)求沒有發(fā)生變化,那么就會(huì)根據(jù)緩存響應(yīng)體構(gòu)造一個(gè)全新的響應(yīng)體薛躬,并更新當(dāng)前的是時(shí)間戳俯渤,最后更新了到緩存中,并返回到上層型宝。

  • 2.不為304情況八匠,且緩存策略是允許刷新的,還是會(huì)把當(dāng)前成功的響應(yīng)體保存到Cache中最后返回趴酣。

來看看如何判斷那些響應(yīng)體可以進(jìn)行緩存:

    fun isCacheable(response: Response, request: Request): Boolean {
      // Always go to network for uncacheable response codes (RFC 7231 section 6.1), This
      // implementation doesn't support caching partial content.
      when (response.code) {
        HTTP_OK,
        HTTP_NOT_AUTHORITATIVE,
        HTTP_NO_CONTENT,
        HTTP_MULT_CHOICE,
        HTTP_MOVED_PERM,
        HTTP_NOT_FOUND,
        HTTP_BAD_METHOD,
        HTTP_GONE,
        HTTP_REQ_TOO_LONG,
        HTTP_NOT_IMPLEMENTED,
        StatusLine.HTTP_PERM_REDIRECT -> {
          // These codes can be cached unless headers forbid it.
        }

        HTTP_MOVED_TEMP,
        StatusLine.HTTP_TEMP_REDIRECT -> {
          // These codes can only be cached with the right response headers.
          // http://tools.ietf.org/html/rfc7234#section-3
          // s-maxage is not checked because OkHttp is a private cache that should ignore s-maxage.
          if (response.header("Expires") == null &&
              response.cacheControl.maxAgeSeconds == -1 &&
              !response.cacheControl.isPublic &&
              !response.cacheControl.isPrivate) {
            return false
          }
        }

        else -> {
          // All other codes cannot be cached.
          return false
        }
      }

      // A 'no-store' directive on request or response prevents the response from being cached.
      return !response.cacheControl.noStore && !request.cacheControl.noStore
    }
  }
  • 1.302,307狀態(tài)碼狀態(tài)下梨树,需要判斷獲取頭部中的Expires數(shù)值,獲取不存在岖寞。并且Cache-Control頭部中max-age為-1抡四,且publicprivate都存在的時(shí)候。

Expires和max-age 說明了有效期為無限長(zhǎng)仗谆,public說明http通信的過程中指巡,包括請(qǐng)求的發(fā)起方、代理緩存服務(wù)器都可以進(jìn)行緩存隶垮。private說明http通信過程中只有請(qǐng)求方可以緩存藻雪。

  • 2.如果狀態(tài)碼為200,203狸吞,204勉耀,300指煎,301,404瑰排,405贯要,410,414椭住,501崇渗,308都可以緩存。

  • 2.其他情況都返回false京郑,不允許緩存

總結(jié)

到這里就完成了對(duì)Okhttp頭三層協(xié)議的解析宅广,能看到實(shí)際上頭三層主要處理的是Http協(xié)議中狀態(tài)碼所對(duì)應(yīng)的行為。

Http響應(yīng)狀態(tài)碼大致上可以分為如下幾種情況:

2XX 代表請(qǐng)求成功

200些举,203跟狱,204 代表請(qǐng)求成功,可以對(duì)響應(yīng)數(shù)據(jù)進(jìn)行緩存

30X 代表資源發(fā)生變動(dòng)或者沒有變動(dòng)

  • 300 是指有多種選擇户魏。請(qǐng)求的資源包含多個(gè)位置驶臊,此時(shí)請(qǐng)求也可以看作成功,此時(shí)也會(huì)進(jìn)行緩存起來叼丑。此時(shí)也會(huì)記錄下需要跳轉(zhuǎn)Header中的Location关翎,并重新設(shè)置為全新的跳轉(zhuǎn)url。記住這個(gè)過程是先執(zhí)行了緩存攔截器后鸠信,再執(zhí)行跳轉(zhuǎn)攔截器纵寝。

  • 301 請(qǐng)求的資源已經(jīng)永久移動(dòng)了 會(huì)自動(dòng)重定向。此時(shí)還是一樣會(huì)緩存當(dāng)前的結(jié)果后星立,嘗試獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)爽茴,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)

  • 302 代表臨時(shí)移動(dòng)的資源,所以沒有特殊處理并不會(huì)緩存結(jié)果绰垂,因?yàn)檫@個(gè)響應(yīng)數(shù)據(jù)很可能時(shí)效性很短室奏;但是如果設(shè)置了Cache-ControlExpires這些緩存時(shí)效頭部就會(huì)進(jìn)行緩存劲装,接著會(huì)獲取Location的url 進(jìn)行重定向(Http 1.0內(nèi)容)胧沫,不允許重定向時(shí)候改變請(qǐng)求方式(如get轉(zhuǎn)化成post)

  • 303 代表查看其他資源,而這個(gè)過程可以不視作一個(gè)正常的響應(yīng)結(jié)果酱畅,也因?yàn)樵试S改變請(qǐng)求方式琳袄;因此也不會(huì)進(jìn)行緩存,接著會(huì)獲取Location的url 進(jìn)行重定向.

  • 304 代表資源沒有發(fā)生變動(dòng)纺酸,且緩存策略是允許刷新的窖逗。那么就說明服務(wù)器這段時(shí)間內(nèi)對(duì)這個(gè)請(qǐng)求的應(yīng)答沒有變化,客戶端直接從緩存獲取即可餐蔬。此時(shí)客戶端就會(huì)從緩存攔截器中的緩存對(duì)象獲取緩存好的響應(yīng)信息碎紊。

  • 307 同302 也是一個(gè)臨時(shí)移動(dòng)資源的標(biāo)志位佑附,不同的是這是來自Http 1.1協(xié)議。為什么出現(xiàn)一個(gè)一樣的呢仗考?因?yàn)?02在很多瀏覽器的實(shí)現(xiàn)是允許改變請(qǐng)求方式音同,因此307強(qiáng)制規(guī)定不允許改變

  • 308 同301 是一個(gè)永久移動(dòng)的資源路徑,來自Http 1.1.原因也是因?yàn)閺?qiáng)制規(guī)范不允許改變請(qǐng)求方式秃嗜,但是允許進(jìn)行緩存权均。

4XX 客戶端異常或者客戶端需要特殊處理

  • 401 請(qǐng)求要求用戶的身份認(rèn)證锅锨。這個(gè)過程就會(huì)獲取設(shè)置在Authenticator 中的賬號(hào)密碼叽赊,添加到頭部中重試這個(gè)請(qǐng)求。

  • 403 代表拒絕訪問必搞,okhttp不會(huì)做任何處理直接返回

  • 404 代表客戶端請(qǐng)求異常必指,說明這個(gè)url的請(qǐng)求狀態(tài)有問題,okhttp也會(huì)進(jìn)行緩存學(xué)習(xí)恕洲,下一次再一次訪問的時(shí)候就會(huì)直接返回異常塔橡。

  • 405 代表當(dāng)前請(qǐng)求的方式出錯(cuò)了,這個(gè)請(qǐng)求不支持這種請(qǐng)求方式

  • 407 和401類似 不過在這里面代表的是使用代理的Authenticator. authenticate 進(jìn)行賬號(hào)密碼的校驗(yàn)

  • 408 服務(wù)器等待客戶端發(fā)送請(qǐng)求超時(shí)處理 狀態(tài)碼408不常見霜第,但是在HAProxy中會(huì)比較常見葛家。這種情況說明我們可以重復(fù)的進(jìn)行沒有修改過的請(qǐng)求(甚至是非冪等請(qǐng)求),從頭部中獲取對(duì)應(yīng)的key,從而決定是否立即重試

  • 410 代表資源已經(jīng)不可用了庶诡,此時(shí)okhttp也會(huì)學(xué)習(xí)惦银,緩存這個(gè)結(jié)果直到超過緩存時(shí)效咆课。

  • 414 代表請(qǐng)求的URL長(zhǎng)度超出了服務(wù)器可以處理的長(zhǎng)度末誓。很少見這種情況,這種也是數(shù)據(jù)一種異常书蚪,所以okhttp也會(huì)獲取摘要學(xué)習(xí)

  • 421 代表客戶端所在的ip地址到服務(wù)器的連接數(shù)超過了服務(wù)器最大的連接數(shù)喇澡。此時(shí)還是有機(jī)會(huì)進(jìn)行重新請(qǐng)求,因?yàn)樵贖ttp 2.0協(xié)議中允許流的復(fù)用殊校。

5XX 服務(wù)端異常

  • 500 服務(wù)端出現(xiàn)了無法處理的錯(cuò)誤晴玖,直接報(bào)錯(cuò)了。 這種情況不會(huì)做處理为流,直接拋出錯(cuò)誤即可

  • 501 服務(wù)端此時(shí)不支持請(qǐng)求所需要的功能呕屎,服務(wù)器無法識(shí)別請(qǐng)求的方法,并且無法支持對(duì)任何資源的請(qǐng)求敬察。 這種錯(cuò)誤okhhtp可以緩存學(xué)習(xí)秀睛,因?yàn)槭欠?wù)器的web系統(tǒng)需要升級(jí)了。

  • 503 服務(wù)器過載莲祸,暫時(shí)不處理蹂安。一般會(huì)帶上Retry-After 告訴客戶端延時(shí)多少時(shí)間之后再次請(qǐng)求椭迎。然而okhttp不會(huì)做延時(shí)處理,而是交給開發(fā)者處理田盈,他只會(huì)處理Retry-After 為0的情況畜号,也就是立即處理

  • 504 一般是指網(wǎng)關(guān)超時(shí),注意如果okhttp禁止了網(wǎng)絡(luò)請(qǐng)求和緩存也會(huì)返回504

retryAndFollowUpInterceptor

主要處理了如下幾個(gè)方向的問題:

  • 1.異常允瞧,或者協(xié)議重試(408客戶端超時(shí)简软,權(quán)限問題,503服務(wù)暫時(shí)不處理述暂,retry-after為0)
  • 2.重定向
  • 3.重試的次數(shù)不能超過20次替饿。

BridgeInterceptor

主要是把Cookie,Content-type設(shè)置到頭部中贸典。很多時(shí)候视卢,初學(xué)者會(huì)疑惑為什么自己加的頭部會(huì)失效,就是因?yàn)樵贏pplication攔截器中處理后廊驼,又被BridgeInterceptor 覆蓋了据过。需要使用networkInterceptor

CacheInterceptor

主要是處理304等響應(yīng)體的緩存。通過DiskLruCache緩存起來妒挎。

到這里前三層屬于對(duì)Http協(xié)議處理的攔截器就完成了绳锅,接下來幾層就是okhttp如何管理鏈接的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酝掩,一起剝皮案震驚了整個(gè)濱河市鳞芙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌期虾,老刑警劉巖原朝,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異镶苞,居然都是意外死亡喳坠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門茂蚓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壕鹉,“玉大人,你說我怎么就攤上這事聋涨×涝。” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵牍白,是天一觀的道長(zhǎng)脊凰。 經(jīng)常有香客問我,道長(zhǎng)淹朋,這世上最難降的妖魔是什么笙各? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任钉答,我火速辦了婚禮,結(jié)果婚禮上杈抢,老公的妹妹穿的比我還像新娘数尿。我一直安慰自己,他們只是感情好惶楼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布右蹦。 她就那樣靜靜地躺著,像睡著了一般歼捐。 火紅的嫁衣襯著肌膚如雪何陆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天豹储,我揣著相機(jī)與錄音贷盲,去河邊找鬼。 笑死剥扣,一個(gè)胖子當(dāng)著我的面吹牛巩剖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钠怯,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼佳魔,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了晦炊?” 一聲冷哼從身側(cè)響起鞠鲜,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎断国,沒想到半個(gè)月后贤姆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡并思,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年庐氮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了语稠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宋彼。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖仙畦,靈堂內(nèi)的尸體忽然破棺而出输涕,到底是詐尸還是另有隱情,我是刑警寧澤慨畸,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布莱坎,位于F島的核電站,受9級(jí)特大地震影響寸士,放射性物質(zhì)發(fā)生泄漏檐什。R本人自食惡果不足惜碴卧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乃正。 院中可真熱鬧住册,春花似錦、人聲如沸瓮具。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)名党。三九已至叹阔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間传睹,已是汗流浹背耳幢。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留欧啤,地道東北人帅掘。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像堂油,于是被迫代替她去往敵國(guó)和親修档。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容