OKhttp源碼學(xué)習(xí)(四)—— RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor攔截器分析

源碼地址:https://github.com/square/okhttp

前面已經(jīng)對整體流程以及幾個類做了了解遵湖,這里就開始對第一個攔截器RetryAndFollowUpInterceptor的分析了囱桨。

整體結(jié)構(gòu)

首先通過一張圖了解一下這個攔截器的整體結(jié)構(gòu):

整體結(jié)構(gòu)

縱觀整個類,方法分為了兩部分:

  1. 供外部調(diào)用的:cancle相關(guān)的 , intercept等 ;
  2. 內(nèi)部使用的记靡,主要服務(wù)于intercept方法。

那么接下來我們就從兩部分對這個類進(jìn)行分析:

外部調(diào)用方法


//進(jìn)行取消連接的操作
  public void cancel() {
    canceled = true;
  //通過StreamAllocation進(jìn)行cancle操作
    StreamAllocation streamAllocation = this.streamAllocation;
    if (streamAllocation != null) streamAllocation.cancel();
  }

//獲取cancle狀態(tài)
  public boolean isCanceled() {
    return canceled;
  }

//設(shè)置調(diào)用堆棧跟蹤盯捌,在創(chuàng)建StreamAllocation對象的時候作為參數(shù)傳入
  public void setCallStackTrace(Object callStackTrace) {
    this.callStackTrace = callStackTrace;
  }

//獲取StreamAllocation蓖柔,一個流分配的類,處理連接谓媒,數(shù)據(jù)流,Call請求的關(guān)系
  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

這些方法提供給外部調(diào)用何乎,你跟蹤一下句惯,會發(fā)現(xiàn)其實(shí)都是給RealCall調(diào)用使用的,而Cancle相關(guān)的方法支救,RealCall 就直接提供給外部使用了抢野。也就是我們?nèi)∠粋€連接的操作。而另外兩個方法各墨,則都是提供給內(nèi)部使用指孤。

這個幾個方法中,你會發(fā)現(xiàn),大部分都與這個StreamAllocation類有關(guān)恃轩。這個類到底是什么结洼?到底做了什么操作?在后面還會用到嗎叉跛?帶著這些疑問松忍,在這一節(jié)之后再對這個類進(jìn)行學(xué)習(xí)分析。

intercept方法

每個攔截器筷厘,最重要的方法就是這個intercept方法了鸣峭。
而對于RetryAndFollowUpInterceptor這個攔截器都做了些什么?

  1. 新建StreamAllocation類敞掘,傳入okhttpClicent中創(chuàng)建的連接池,傳入通過url創(chuàng)建Address類楣铁,傳入callStackTrace玖雁,調(diào)用堆棧跟蹤。

  2. 開啟一個while循環(huán)盖腕;

  3. 判讀是否用戶已經(jīng)cancle,是-拋出異常赫冬,否-繼續(xù)走下去;

  4. 通過RealInterceptorChain調(diào)用下一個攔截器(request, streamAllocation)溃列,并等待下一個攔截器返回結(jié)果劲厌。

  5. 獲取返回結(jié)果做兩個捕獲異常,處理后面步驟拋出的異常, 判斷是否繼續(xù)請求听隐。

  6. 判讀結(jié)果是否符合要求补鼻,返回或者進(jìn)行重定向。

  7. 關(guān)閉響應(yīng)結(jié)果

  8. 判斷重定向數(shù)目是否超過 MAX_FOLLOW_UPS(20)

  9. 判斷重新連接是否問相同的連接雅任,否-新建风范,是-釋放。

  10. 循環(huán)2以后的步驟沪么。

源碼:

  @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

   //1. 新建StreamAllocation類
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
   //2.開啟一個while循環(huán)
    while (true) {
   //3.cancle判讀
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
       //4.調(diào)用下一個攔截器
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
       //5.判斷是否繼續(xù)請求
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

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

     //6.判斷是否重定向或者超時重試
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      // 7. 關(guān)閉響應(yīng)結(jié)果
      closeQuietly(response.body());

     // 8.判斷是否重定向數(shù)目
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }

     //判斷是否相同的連接
      if (!sameConnection(response, followUp.url())) {
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }

      request = followUp;
      priorResponse = response;
    }
  }

內(nèi)部調(diào)用方法

提供內(nèi)部的方法都是服務(wù)于interceptor()硼婿,有三個:

  1. createAddress,通過url的host禽车,port, dns 以及一系列ohHttpClient的協(xié)議連接參數(shù)寇漫,簡歷一個Address類。而這個Address對象是提供給StreamAllocation殉摔,建立連接使用的州胳。

  2. recover, 連接異常判斷逸月,是否繼續(xù)陋葡。(含isRecoverable)

  private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);

    //調(diào)用層面,是否禁止了重連重試
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure()) return false;

    //請求的Request本身出錯彻采,不能繼續(xù)再連接
    // We can't send the request body again.
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
   
    // 是否可以恢復(fù)的腐缤,主要判斷了捌归,是否協(xié)議異常,中斷異常岭粤,SSL的握手異常惜索,SSL通道未許可異常。
    // This exception is fatal.
    if (!isRecoverable(e, requestSendStarted)) return false;

    //沒有更多的線路提供了
    // No more routes to attempt.
    if (!streamAllocation.hasMoreRoutes()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
  }
  1. followUpRequest剃浇, 通過結(jié)果的http code碼巾兆,來判斷是否可以重定向,可以正常重定向會返回對應(yīng)的Request,不然就返回null虎囚。

總結(jié):
RetryAndFollowUpInterceptor 的主要做了三件事:

  1. 初始化了連接的對象(StreamAllocation角塑,但是比沒有真正建立連接,只是初始化了對象)(前置攔截)淘讥;

  2. 通過RealInterceptorChain圃伶,再調(diào)用下一個攔截器;

  3. 收到結(jié)果之后蒲列,做異常處理窒朋,判斷是否重連或者重定向,或者返回結(jié)果蝗岖。(后置攔截)

作為第一個攔截器侥猩,功能也是相對比較簡單的。留下一個疑問就是StreamAllocation 這個類抵赢,這個類的學(xué)習(xí)在以后會專門提出來欺劳。

系列:
OKhttp源碼學(xué)習(xí)(一)—— 基本請求流程
OKhttp源碼學(xué)習(xí)(二)—— OkHttpClient
OKhttp源碼學(xué)習(xí)(三)—— Request, RealCall
OKhttp源碼學(xué)習(xí)(五)—— BridgeInterceptor
OKhttp源碼學(xué)習(xí)(六)—— CacheInterceptor攔截器
OKhttp源碼學(xué)習(xí)(七)—— ConnectInterceptor攔截器
OKhttp源碼學(xué)習(xí)(八)——CallServerInterceptor攔截器
OKhttp源碼學(xué)習(xí)(九)—— 任務(wù)管理(Dispatcher)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铅鲤,隨后出現(xiàn)的幾起案子杰标,更是在濱河造成了極大的恐慌,老刑警劉巖彩匕,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腔剂,死亡現(xiàn)場離奇詭異,居然都是意外死亡驼仪,警方通過查閱死者的電腦和手機(jī)掸犬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绪爸,“玉大人湾碎,你說我怎么就攤上這事〉旎酰” “怎么了介褥?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我柔滔,道長溢陪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任睛廊,我火速辦了婚禮形真,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘超全。我一直安慰自己咆霜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布嘶朱。 她就那樣靜靜地躺著蛾坯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疏遏。 梳的紋絲不亂的頭發(fā)上脉课,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機(jī)與錄音改览,去河邊找鬼下翎。 笑死缤言,一個胖子當(dāng)著我的面吹牛宝当,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胆萧,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼庆揩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了跌穗?” 一聲冷哼從身側(cè)響起订晌,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚌吸,沒想到半個月后锈拨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡羹唠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年奕枢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佩微。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡缝彬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哺眯,到底是詐尸還是另有隱情谷浅,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站一疯,受9級特大地震影響撼玄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜违施,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一互纯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磕蒲,春花似錦留潦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至站削,卻和暖如春坊萝,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背许起。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工十偶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人园细。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓惦积,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猛频。 傳聞我的和親對象是個殘疾皇子狮崩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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