OKhttp源碼學習(四)—— 攔截器_RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor攔截器分析

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

前面已經對整體流程以及幾個類做了瞭解,這裏就開始對第一個攔截器RetryAndFollowUpInterceptor的分析了。

整體結構

首先通過一張圖瞭解一下這個攔截器的整體結構:

整體結構

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

  1. 供外部調用的:cancle相關的 , intercept等 ;
  2. 內部使用的,主要服務於intercept方法。

那麼接下來我們就從兩部分對這個類進行分析:

外部調用方法


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

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

//設置調用堆棧跟蹤,在創建StreamAllocation對象的時候作爲參數傳入
  public void setCallStackTrace(Object callStackTrace) {
    this.callStackTrace = callStackTrace;
  }

//獲取StreamAllocation,一個流分配的類,處理連接,數據流,Call請求的關係
  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

這些方法提供給外部調用,你跟蹤一下,會發現其實都是給RealCall調用使用的,而Cancle相關的方法,RealCall 就直接提供給外部使用了。也就是我們取消一個連接的操作。而另外兩個方法,則都是提供給內部使用。

這個幾個方法中,你會發現,大部分都與這個StreamAllocation類有關。這個類到底是什麼?到底做了什麼操作?在後面還會用到嗎?帶着這些疑問,在這一節之後再對這個類進行學習分析。

intercept方法

每個攔截器,最重要的方法就是這個intercept方法了。
而對於RetryAndFollowUpInterceptor這個攔截器都做了些什麼?

  1. 新建StreamAllocation類,傳入okhttpClicent中創建的連接池,傳入通過url創建Address類,傳入callStackTrace,調用堆棧跟蹤。

  2. 開啓一個while循環;

  3. 判讀是否用戶已經cancle,是-拋出異常,否-繼續走下去;

  4. 通過RealInterceptorChain調用下一個攔截器(request, streamAllocation),並等待下一個攔截器返回結果。

  5. 獲取返回結果做兩個捕獲異常,處理後面步驟拋出的異常, 判斷是否繼續請求。

  6. 判讀結果是否符合要求,返回或者進行重定向。

  7. 關閉響應結果

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

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

  10. 循環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循環
    while (true) {
   //3.cancle判讀
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
       //4.調用下一個攔截器
        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.判斷是否繼續請求
        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. 關閉響應結果
      closeQuietly(response.body());

     // 8.判斷是否重定向數目
      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;
    }
  }

內部調用方法

提供內部的方法都是服務於interceptor(),有三個:

  1. createAddress,通過url的host,port, dns 以及一系列ohHttpClient的協議連接參數,簡歷一個Address類。而這個Address對象是提供給StreamAllocation,建立連接使用的。

  2. recover, 連接異常判斷,是否繼續。(含isRecoverable)

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

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

    //請求的Request本身出錯,不能繼續再連接
    // We can't send the request body again.
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
   
    // 是否可以恢復的,主要判斷了,是否協議異常,中斷異常,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, 通過結果的http code碼,來判斷是否可以重定向,可以正常重定向會返回對應的Request,不然就返回null。

總結:
RetryAndFollowUpInterceptor 的主要做了三件事:

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

  2. 通過RealInterceptorChain,再調用下一個攔截器;

  3. 收到結果之後,做異常處理,判斷是否重連或者重定向,或者返回結果。(後置攔截)

作爲第一個攔截器,功能也是相對比較簡單的。留下一個疑問就是StreamAllocation 這個類,這個類的學習在以後會專門提出來。

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

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