okhttp源碼學習(四) RetryAndFollowUpInterceptor

核心功能

1.連接失敗重試(Retry)

在發生 RouteException 或者 IOException 後,會捕獲建聯或者讀取的一些異常,根據一定的策略判斷是否是可恢復的,如果可恢復會重新創建 StreamAllocation 開始新的一輪請求

2.繼續發起請求(Follow up)

主要有這幾種類型
3xx 重定向
401,407 未授權,調用 Authenticator 進行授權後繼續發起新的請求
408 客戶端請求超時,如果 Request 的請求體沒有被 UnrepeatableRequestBody 標記,會繼續發起新的請求
其中 Follow up 的次數受到MAX_FOLLOW_UP 約束,在 OkHttp 中爲 20 次,這樣可以防止重定向死循環

流程圖

源碼分析

  @Override public Response intercept(Chain chain) throws IOException {
    // 1. 獲取 Transmitter對象
    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      transmitter.prepareToConnect(request);

      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        // 2. 執行下一個攔截器,即BridgeInterceptor
        response = realChain.proceed(request, transmitter, null);
        success = true;
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        // 3. 如果有異常,判斷是否要恢復
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        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, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        // The network call threw an exception. Release any resources.
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

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

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      // 檢查是否符合重定向要求
      Request followUp = followUpRequest(response, route);

      if (followUp == null) {
        if (exchange != null && exchange.isDuplex()) {
          transmitter.timeoutEarlyExit();
        }
//        返回結果
        return response;
      }

      RequestBody followUpBody = followUp.body();
      if (followUpBody != null && followUpBody.isOneShot()) {
        return response;
      }
      // 5.關閉響應流
      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      //最大限制判斷
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      request = followUp;
      priorResponse = response;
    }
  }

這就是RetryAndFollowUpInterceptor的核心代碼 我們來一步步的分析

1.獲取一個Transmitter 對象

    Request request = chain.request();
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Transmitter transmitter = realChain.transmitter();

他是網絡請求的調度器,作用在整個網絡請求生命週期 它用於協調Connection,Stream,Call。他的創建是在newRealCall的時候

  static RealCall newRealCall(OkHttpClient client, Request originalRequest, boolean forWebSocket) {
    // Safely publish the Call instance to the EventListener.
    RealCall call = new RealCall(client, originalRequest, forWebSocket);
    call.transmitter = new Transmitter(client, call);
    return call;
  }

2.開啓循環,執行下一個調用鏈(攔截器),等待返回結果(Response)
response = realChain.proceed(request, streamAllocation, null, null);
3.如果catch了進入recover 判斷收可以重新請求

  private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // The application layer has forbidden retries.
    //如果OkHttpClient直接配置拒絕失敗重連,return false
    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 (!transmitter.canRetry()) return false;

    // For failure recovery, use the same route selector with a new connection.
    return true;
  }

無法重試的isRecoverable()原因

  private boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // If there was a protocol problem, don't recover.
    //如果是協議問題,不要在重試了
    if (e instanceof ProtocolException) {
      return false;
    }

    // If there was an interruption don't recover, but if there was a timeout connecting to a route
    // we should try the next route (if there is one).
    if (e instanceof InterruptedIOException) {
      //超時問題,並且請求還沒有被髮送,可以重試
      //其他就不要重試了
      return e instanceof SocketTimeoutException && !requestSendStarted;
    }

    // Look for known client-side or negotiation errors that are unlikely to be fixed by trying
    // again with a different route.
    if (e instanceof SSLHandshakeException) {
      // If the problem was a CertificateException from the X509TrustManager,
      // do not retry.
      //安全證書異常
      if (e.getCause() instanceof CertificateException) {
        return false;
      }
    }
    if (e instanceof SSLPeerUnverifiedException) {
      // e.g. a certificate pinning error.
      //安全原因
      return false;
    }

4.關閉響應流

      closeQuietly(response.body());

5.增加重定向次數

  if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
```、
6.繼續循環知道返回response或者 拋出異常


到這裏RetryAndFollowUpInterceptor的分析就結束了進入下一個。

最後獻上一份添加了註釋的源碼 [https://github.com/525642022/okhttpTest/blob/master/README.md](https://github.com/525642022/okhttpTest/blob/master/README.md)
哈哈
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章