OkHttp講解(二)

Android知識總結(jié)

1、攔截器 Interceptor

1.1 okhttp的工作流程圖


可以看出,Interceptor貫穿了整個(gè)請求過程,是在請求執(zhí)行過程中扮演重要角色。
這是okhttp的請求執(zhí)行過程,從應(yīng)用發(fā)出request,到應(yīng)用收到response,期間經(jīng)歷了N個(gè)攔截器。

  • 藍(lán)色塊上方是APPLication Interceptor,也就是應(yīng)用程序攔截器,即開發(fā)者自己自定義的攔截器,代碼中的client.interceptors()獲取的就是這類攔截器
  • 藍(lán)色塊下方是NetWork Interceptor,也就是網(wǎng)絡(luò)攔截器,用來觀察單個(gè)網(wǎng)絡(luò)請求和響應(yīng),只能調(diào)用一次proceed方法
  • 藍(lán)色塊代表的就是OKHttp提供的攔截器,共有5個(gè),也是我們需要關(guān)注的重點(diǎn)

1.2 攔截器的分類

okhttp工作流程圖中,橙色框框內(nèi)的那些攔截器,屬于okhttp庫內(nèi)部定義的,一般情況下不會更改。所以這里只討論開發(fā)者能夠自定義的攔截器。
  分為兩類:
  1)ApplicationInterceptor(應(yīng)用攔截器)
  2)NetworkInterceptor(網(wǎng)絡(luò)攔截器)

相同點(diǎn)

  • 都能對server返回的response進(jìn)行攔截
  • 這兩種攔截器本質(zhì)上都是基于Interceptor接口,由開發(fā)者實(shí)現(xiàn)這個(gè)接口,然后將自定義的Interceptor類的對象設(shè)置到okhttpClient對象中。所以,他們的對象,本質(zhì)上沒什么不同,都是Interceptor的實(shí)現(xiàn)類的對象。
  • 兩者都會被add到OkHttpClient內(nèi)的一個(gè)ArrayList中。當(dāng)請求執(zhí)行的時(shí)候,多個(gè)攔截器會依次執(zhí)行(list本身就是有序的)。

不同點(diǎn)

  • okhttpClient添加兩種攔截器的api不同。添加應(yīng)用攔截器的接口是addInterceptor(),而添加網(wǎng)絡(luò)攔截器的接口是addNetworkInterceptor().
  • 兩者負(fù)責(zé)的區(qū)域不同,從最上方圖中可以看出,應(yīng)用攔截器作用于okhttpCore到Application之間,網(wǎng)絡(luò)攔截器作用于 network和okhttpCore之間
  • 在某種特殊情況下(比如:訪問了一個(gè)url,結(jié)果這個(gè)url發(fā)生了重定向),網(wǎng)絡(luò)攔截器有可能被執(zhí)行多次,但是不論任何情況,application只會被執(zhí)行一次。
  • 在執(zhí)行時(shí)addInterceptor一定會執(zhí)行,addNetworkInterceptor不一定會執(zhí)行,因?yàn)樵赼ddNetworkInterceptor之前的Interceptor發(fā)生異常退出時(shí)addNetworkInterceptor就不會被執(zhí)行。
  • addInterceptor 先拿到的是Request后拿到的是Response,addNetworkInterceptor反之。
  • 打印Log日志時(shí),一把放在 addNetworkInterceptor,因?yàn)槠浯蛴〉氖峭暾?Request。

1.3 okhttp庫內(nèi)部定義的攔截器

1.3.0、RealCall

在okhttp框架中,當(dāng)客戶端通過OkHttpClient發(fā)起同步或異步請求時(shí),okhttp框架將會創(chuàng)建一個(gè)RealCall,這個(gè)實(shí)例將根據(jù)客戶端提供的Request,發(fā)起同步或異步網(wǎng)絡(luò)請求操作,在RealCall被創(chuàng)建時(shí),將會創(chuàng)建一個(gè)Interceptor的具體實(shí)現(xiàn)。我們知道,okhttp框架將網(wǎng)絡(luò)請求的步驟,通過Interceptor接口進(jìn)行了統(tǒng)一的分層式設(shè)計(jì),將每個(gè)環(huán)節(jié)都分成了不同的Interceptor,Interceptor又被稱為攔截器,這是該網(wǎng)絡(luò)框架設(shè)計(jì)的精髓所在,通過不同的攔截器規(guī)則,處理網(wǎng)絡(luò)請求過程中的不同環(huán)節(jié),最終通過鏈?zhǔn)秸{(diào)用,實(shí)現(xiàn)一個(gè)完整的網(wǎng)絡(luò)請求操作。

  Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    //添加開發(fā)者應(yīng)用層自定義的Interceptor
    interceptors.addAll(client.interceptors());
    //這個(gè)Interceptor是處理請求失敗的重試,重定向    
    interceptors.add(retryAndFollowUpInterceptor);
    //這個(gè)Interceptor工作是添加一些請求的頭部或其他信息
    //并對返回的Response做一些友好的處理(有一些信息你可能并不需要)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //這個(gè)Interceptor的職責(zé)是判斷緩存是否存在,讀取緩存,更新緩存等等
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //這個(gè)Interceptor的職責(zé)是建立客戶端和服務(wù)器的連接
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //添加開發(fā)者自定義的網(wǎng)絡(luò)層攔截器
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //一個(gè)包裹這request的chain
    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    //把chain傳遞到第一個(gè)Interceptor手中
    return chain.proceed(originalRequest);
  }

1.3.1、RetryAndFollowUpInterceptor 重試和失敗重定向攔截器

這個(gè)攔截器它的作用主要是負(fù)責(zé)請求的重定向操作,用于處理網(wǎng)絡(luò)請求中,請求失敗后的重試鏈接。把StreamAllocation對象,傳遞給后面的攔截器。

private static final int MAX_FOLLOW_UPS = 20;

從這個(gè)靜態(tài)變量可以看出 RetryAndFollowUpInterceptor 重定向最多20次。

 @Override public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //創(chuàng)建一個(gè)新的流
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);
    //重定向次數(shù)
    int followUpCount = 0;
     // 上一個(gè)重試得到的響應(yīng)
    Response priorResponse = null;
    while (true) {
      //如果RealCall調(diào)用了cancel,即取消請求,那就釋放資源,拋出異常結(jié)束請求
      if (canceled) {
          //如果取消了則刪除連接上的call請求
        streamAllocation.release();
        throw new IOException("Canceled");
      }
      // 定義請求的響應(yīng)
      Response response = null;
      //// 是否釋放連接,默認(rèn)為true
      boolean releaseConnection = true;
      try {
        //調(diào)用下一個(gè)攔截器 即BridgeInterceptor;進(jìn)行網(wǎng)絡(luò)連接,獲取response
        response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
        // 如果沒有發(fā)送異常,修改標(biāo)志 不需要重試
        releaseConnection = false;
      } catch (RouteException e) {
        //出現(xiàn)路由連接異常,通過recover方法判斷能否恢復(fù)連接,如果不能將拋出異常不再重試
        //recover(...)檢測連接是否還可以繼續(xù)
        if (!recover(e.getLastConnectException(), false, request)) {
          throw e.getLastConnectException();
        }
        //能恢復(fù)連接,修改標(biāo)志 不釋放連接
        releaseConnection = false;
          //回到下一次循環(huán) 繼續(xù)重試 除了finally代碼外,下面的代碼都不會執(zhí)行
        continue;
      } catch (IOException e) {//后續(xù)攔截器在與服務(wù)器通信中拋出IO異常
        //判斷該異常是否是連接關(guān)閉異常
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        ////通過recover方法判斷能否恢復(fù)連接,如果不能將拋出異常不再重試
        if (!recover(e, requestSendStarted, request)) throw e;
        //能恢復(fù)連接, 修改標(biāo)志 不釋放連接
        releaseConnection = false;
        //回到下一次循環(huán) 繼續(xù)重試 除了finally代碼外,下面的代碼都不會執(zhí)行
        continue;
      } finally {
        // 如果releaseConnection為true,說明后續(xù)攔截器拋出了其它異常,那就釋放所有資源,結(jié)束請求
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

      // 走到這里,說明網(wǎng)絡(luò)請求已經(jīng)完成了,但是響應(yīng)碼并不一定是200
      // 可能是其它異常的響應(yīng)碼或者重定向響應(yīng)碼
      
      // 如果priorResponse 不等于null,說明前面已經(jīng)完成了一次請求
      // 那就通過上一次的response構(gòu)建新的response,但是body為null.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
      //對response進(jìn)行響應(yīng)碼的判斷,如果需要進(jìn)行重定向,那就獲取新的Request
      Request followUp = followUpRequest(response);
      // 如果為null,那就沒必要重新請求,說明已經(jīng)有了合適的Response,直接返回
      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }
       //關(guān)閉,忽略任何已檢查的異常
      closeQuietly(response.body());
      //檢測followUp(重定向)次數(shù)是否超過20次,如果超過就拋出異常,避免消耗客戶端太多資源
      if (++followUpCount > MAX_FOLLOW_UPS) {
        streamAllocation.release();
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }
      //如果該請求體被UnrepeatableRequestBody標(biāo)記,則不可重試
      if (followUp.body() instanceof UnrepeatableRequestBody) {
        streamAllocation.release();
        throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
      }
      //判斷重連前的Request與重新構(gòu)建的Request是否有相同的連接,即host、port、scheme是否一致
      if (!sameConnection(response, followUp.url())) {
        // 如果不是相同的url連接,先釋放之間的,再創(chuàng)建新的StreamAllocation
        streamAllocation.release();
        streamAllocation = new StreamAllocation(
            client.connectionPool(), createAddress(followUp.url()), callStackTrace);
      } else if (streamAllocation.codec() != null) {
         // 如果相同,但是本次請求的流沒有關(guān)閉,那就拋出異常
        throw new IllegalStateException("Closing the body of " + response
            + " didn't close its backing stream. Bad interceptor?");
      }
       //把重定向的請求賦值給request,以便再次進(jìn)入循環(huán)執(zhí)行
      request = followUp;
      priorResponse = response;
    }
  }

RetryAndFollowUpInterceptor ,主要就是完成兩件事情:重試重定向。

  • 重試
    重試流程

請求階段發(fā)生了 RouteException 或者 IOException會進(jìn)行判斷是否重新發(fā)起請求。
RouteException

    catch (RouteException e) {
        // 路由異常,連接未成功,請求還沒發(fā)出去
        if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
          throw e.getLastConnectException();
        }
        releaseConnection = false;
        continue;
     } 

IOException

     catch (IOException e) {
        // 請求發(fā)出去了,但是和服務(wù)器通信失敗了。(socket流正在讀寫數(shù)據(jù)的時(shí)候斷開連接)
        //HTTP2才會拋出ConnectionShutdownException。所以對于HTTP1 requestSendStarted一定是true
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
        releaseConnection = false;
        continue;
      } 

兩個(gè)異常都是根據(jù) recover 方法判斷是否能夠進(jìn)行重試,如果返回 true ,則表示允許重試。

 /**
  * 判斷當(dāng)與服務(wù)器通信失敗時(shí),連接能否進(jìn)行恢復(fù)
  * 返回true,表示可以進(jìn)行恢復(fù)
  * 返回false 表示不能恢復(fù),即不能重連
  */
  private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
    //根據(jù)拋出的異常,做出連接、連接路線的一些處理,并且釋放連接,關(guān)閉連接
    streamAllocation.streamFailed(e);
    // 判斷開發(fā)者是否禁用了失敗重連
    // 在構(gòu)建OKHttpClient的時(shí)候可以通過build進(jìn)行配置
    //如果禁用,那就返回false,不進(jìn)行重連
    if (!client.retryOnConnectionFailure()) return false;
    // 如果不是連接關(guān)閉異常,且請求體被UnrepeatableRequestBody標(biāo)記,那不能恢復(fù)
    //如果是IOException,由于requestSendStarted只在http2的io異常中可能為false
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
    // 判斷是不是屬于重試的異常
    if (!isRecoverable(e, requestSendStarted)) return false;
    // 有沒有可以用來連接的路由路線
    // 如果沒有,返回false
    if (!streamAllocation.hasMoreRoutes()) return false;
    // 走到這里說明可以恢復(fù)連接,嘗試重連
    return true;
  }

所以首先使用者在不禁止重試的前提下,如果出現(xiàn)了某些異常,并且存在更多的路由線路,則會嘗試換條線路進(jìn)行請求的重試。其中某些異常是在 isRecoverable 中進(jìn)行判斷:

  private boolean isRecoverable(IOException e, boolean requestSendStarted) {
    // 出現(xiàn)協(xié)議異常,不能重試
    if (e instanceof ProtocolException) {
      return false;
    }
    // 如果不是超時(shí)異常,不能重試
    if (e instanceof InterruptedIOException) {
      return e instanceof SocketTimeoutException && !requestSendStarted;
    }
    //SSL握手異常中,證書出現(xiàn)問題,不能重試
    if (e instanceof SSLHandshakeException) {
      if (e.getCause() instanceof CertificateException) {
        return false;
      }
    }
    // SSL握手未授權(quán)異常 不能重試
    if (e instanceof SSLPeerUnverifiedException) {
      return false;
    }
    return true;
  }
  • 1、協(xié)議異常:如果是那么直接判定不能重試;(你的請求或者服務(wù)器的響應(yīng)本身就存在問題,沒有按照http協(xié)議來定義數(shù)據(jù),再重試也沒用)
  • 2、超時(shí)異常:可能由于網(wǎng)絡(luò)波動造成了Socket連接的超時(shí),可以使用不同路線重試。
  • 3、SSL證書異常/SSL驗(yàn)證失敗異常:前者是證書驗(yàn)證失敗,后者可能就是壓根就沒證書,或者證書數(shù)據(jù)不正確,那還怎么重試?

經(jīng)過了異常的判定之后,如果仍然允許進(jìn)行重試,就會再檢查當(dāng)前有沒有可用路由路線來進(jìn)行連接。簡單來說,比如 DNS 對域名解析后可能會返回多個(gè) IP,在一個(gè)IP失敗后,嘗試另一個(gè)IP進(jìn)行重試。

  • 重定向

如果請求結(jié)束后沒有發(fā)生異常并不代表當(dāng)前獲得的響應(yīng)就是最終需要交給用戶的,還需要進(jìn)一步來判斷是否需要重定向的判斷。重定向的判斷位于 followUpRequest 方法

  private Request followUpRequest(Response userResponse, Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    int responseCode = userResponse.code();
    final String method = userResponse.request().method();
    switch (responseCode) {
      //407 客戶端使用了HTTP代理服務(wù)器,在請求頭中添加 “Proxy-Authorization”,讓代理服務(wù)器授權(quán)
      case HTTP_PROXY_AUTH:
        Proxy selectedProxy = route != null
            ? route.proxy()
            : client.proxy();
        if (selectedProxy.type() != Proxy.Type.HTTP) {
          throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
        }
        return client.proxyAuthenticator().authenticate(route, userResponse);
      // 401 需要身份驗(yàn)證 有些服務(wù)器接口需要驗(yàn)證使用者身份 在請求頭中添加 “Authorization”
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);
      // 308 永久重定向
      // 307 臨時(shí)重定向
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        // "If the 307 or 308 status code is received in response to a request other than GET
        // 如果請求方式不是GET或者HEAD,框架不會自動重定向請求
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
      // 300 301 302 303
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        // 如果用戶不允許重定向,那就返回null
        if (!client.followRedirects()) return null;
        //從響應(yīng)頭取出location
        String location = userResponse.header("Location");
        if (location == null) return null;
        //根據(jù)location 配置新的請求 url
        HttpUrl url = userResponse.request().url().resolve(location);

        // 如果為null,說明協(xié)議有問題,取不出來HttpUrl,那就返回null,不進(jìn)行重定向
        if (url == null) return null;

        // 如果重定向在http到https之間切換,需要檢查用戶是不是允許(默認(rèn)允許)
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // Most redirects don't include a request body.
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        /**
        * 重定向請求中 只要不是 PROPFIND 請求,無論是POST還是其他的方法都要改為GET請求方式,
        * 即只有 PROPFIND 請求才能有請求體
        */
        //請求不是get與head
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          // 除了 PROPFIND 請求之外都改成GET請求
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          //不是 PROPFIND 的請求,把請求頭中關(guān)于請求體的數(shù)據(jù)刪掉
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }

        // 在跨主機(jī)重定向時(shí),刪除身份驗(yàn)證請求頭
        if (!sameConnection(userResponse, url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
      //408 客戶端請求超時(shí)
      case HTTP_CLIENT_TIMEOUT:
        //408 算是連接失敗了,所以判斷用戶是不是允許重試
        if (!client.retryOnConnectionFailure()) {
          // The application layer has directed us not to retry the request.
          return null;
        }
        // UnrepeatableRequestBody實(shí)際并沒發(fā)現(xiàn)有其他地方用到
        if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
          return null;
        }
        // 如果是本身這次的響應(yīng)就是重新請求的產(chǎn)物同時(shí)上一次之所以重請求還是因?yàn)?08,那我們這次不再重請求了
        if (userResponse.priorResponse() != null
            && userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
          // We attempted to retry and got another timeout. Give up.
          return null;
        }
        //如果服務(wù)器告訴我們了 Retry-After 多久后重試,那框架不管了。
        if (retryAfter(userResponse, 0) > 0) {
          return null;
        }
        return userResponse.request();
      //503 服務(wù)不可用 和408差不多,但是只在服務(wù)器告訴你 Retry-After:0(意思就是立即重試) 才重請求
      case HTTP_UNAVAILABLE:
        if (userResponse.priorResponse() != null
            && userResponse.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;
      default:
        return null;
    }
  }

整個(gè)是否需要重定向的判斷內(nèi)容很多,記不住,這很正常,關(guān)鍵在于理解他們的意思。如果此方法返回空,那就表示不需要再重定向了,直接返回響應(yīng);但是如果返回非空,那就要重新請求返回的 Request ,但是需要注意的是,我們的 followup 在攔截器中定義的最大次數(shù)為20次。

總結(jié)

本攔截器是整個(gè)責(zé)任鏈中的第一個(gè),這意味著它會是首次接觸到 Request 與最后接收到 Response 的角色,在這個(gè)攔截器中主要功能就是判斷是否需要重試重定向。
重試的前提是出現(xiàn)了 RouteException 或者 IOException 。一但在后續(xù)的攔截器執(zhí)行過程中出現(xiàn)這兩個(gè)異常,就會通過 recover 方法進(jìn)行判斷是否進(jìn)行連接重試。
重定向發(fā)生在重試的判定之后,如果不滿足重試的條件,還需要進(jìn)一步調(diào)用 followUpRequest 根據(jù) Response 的響應(yīng)碼(當(dāng)然,如果直接請求失敗, Response 都不存在就會拋出異常)。 followup 最大發(fā)生20次。

1.3.2、BridgeInterceptor 橋接攔截器

主要是補(bǔ)充用戶創(chuàng)建請求當(dāng)中缺少的一些必要的請求頭。BridgeInterceptor 為用戶構(gòu)建的一個(gè) Request 請求轉(zhuǎn)化為能夠進(jìn)行網(wǎng)絡(luò)訪問的請求,同時(shí)將網(wǎng)絡(luò)請求回來的響應(yīng) Response 轉(zhuǎn)化為用戶可用的 Response。比如設(shè)置請求內(nèi)容長度,編碼,gzip壓縮,cookie等,獲取響應(yīng)后保存Cookie等操作。這個(gè)攔截器相對比較簡單。

得到響應(yīng):

  • 1、讀取Set-Cookie響應(yīng)頭并調(diào)用接口告知用戶,在下次請求則會讀取對應(yīng)的數(shù)據(jù)設(shè)置進(jìn)入請求頭, 默認(rèn)CookieJar無實(shí)現(xiàn);
  • 2、響應(yīng)頭Content-Encoding為gzip,使用GzipSource包裝解析。
@Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    //組織Request Header包括這是keep-alive, Cookie添加,gzip等
    ....
    //傳遞
    Response networkResponse = chain.proceed(requestBuilder.build());
    //組織Response Header 包括cookie保存更新,Gzip解壓等
    ....
    return responseBuilder.build();
  }

請求頭

  • 1、Content-Type:請求體類型,如: application/x-www-form-urlencoded
  • 2、Content-Length / Transfer-Encoding :請求體解析方式
  • 3、Host:請求的主機(jī)站點(diǎn)
  • 4、Connection: Keep-Alive :保持長連接
  • 5、Accept-Encoding: gzip:接受響應(yīng)支持gzip壓縮
  • 6、Cookie:cookie身份辨別
  • 7、User-Agen:請求的用戶信息,如:操作系統(tǒng)、瀏覽器等

在補(bǔ)全了請求頭后交給下一個(gè)攔截器處理,得到響應(yīng)后,主要干兩件事情

  • 1、保存cookie,在下次請求則會讀取對應(yīng)的數(shù)據(jù)設(shè)置進(jìn)入請求頭,默認(rèn)的 CookieJar 不提供實(shí)現(xiàn)
  • 2、如果使用gzip返回的數(shù)據(jù),則使用 GzipSource 包裝便于解析。

總結(jié)

橋接攔截器的執(zhí)行邏輯主要就是以下幾點(diǎn):
對用戶構(gòu)建的 Request 進(jìn)行添加或者刪除相關(guān)頭部信息,以轉(zhuǎn)化成能夠真正進(jìn)行網(wǎng)絡(luò)請求的 Request 將符合網(wǎng)絡(luò)請求規(guī)范的Request交給下一個(gè)攔截器處理,并獲取 Response 如果響應(yīng)體經(jīng)過了GZIP壓縮,那就需要解壓,再構(gòu)建成用戶可用的 Response 并返回。

1.3.3、CacheInterceptor 緩存攔截器

如果當(dāng)前未使用網(wǎng)絡(luò),并且緩存不可以使用,通過構(gòu)建者模式創(chuàng)建一個(gè)Response響應(yīng),拋出504錯(cuò)誤。如果有緩存 但是不能使用網(wǎng)絡(luò) ,直接返回緩存結(jié)果。這是在進(jìn)行網(wǎng)絡(luò)請求之前所做的事情,當(dāng)網(wǎng)絡(luò)請求完成,得到下一個(gè)攔截器返回的response之后,判斷response的響應(yīng)碼是否是HTTP_NOT_MODIFIED = 304,(未改變)是則從緩存中讀取數(shù)據(jù)。

 @Override 
  public Response intercept(Chain chain) throws IOException {
    //通過request從緩存中獲取響應(yīng)(只會存在Get請求的緩存)
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
  /**
    * CacheStrategy 是一個(gè)緩存策略類 比如強(qiáng)制緩存 對比緩存等 它決定是使用緩存還是進(jìn)行網(wǎng)絡(luò)請求
    * 其內(nèi)部維護(hù)了Request、Response
    * 如果Request為null表示不使用網(wǎng)絡(luò)
    * 如果Response為null表示不使用緩存
    */
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //根據(jù)緩存策略獲取緩存Request和Response
    Request networkRequest = strategy.networkRequest;
    Response cacheResponse = strategy.cacheResponse;
    //根據(jù)緩存策略,更新統(tǒng)計(jì)指標(biāo):請求次數(shù)、使用網(wǎng)絡(luò)請求次數(shù)、使用緩存次數(shù)
    if (cache != null) {
      cache.trackResponse(strategy);
    }
     // 能從緩存中獲取響應(yīng)但是緩存策略是不使用緩存,那就關(guān)閉獲取的緩存
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    // 根據(jù)策略,不使用網(wǎng)絡(luò),又沒有緩存的直接報(bào)錯(cuò),并返回錯(cuò)誤碼504
    if (networkRequest == null && cacheResponse == null) {
      return new Response.Builder()
          .request(chain.request())
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(Util.EMPTY_RESPONSE)
          .sentRequestAtMillis(-1L)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();
    }

    // 如果緩存策略是不使用網(wǎng)絡(luò)但是可以使用緩存,那就通過緩存策略的緩存構(gòu)建響應(yīng)并返回
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
      //緩存不可用或者緩存過期,網(wǎng)絡(luò)獲取
    Response networkResponse = null;
    try {
      //前面兩個(gè)都沒有返回,繼續(xù)執(zhí)行下一個(gè)Interceptor,即ConnectInterceptor
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 如果發(fā)生了IO異常或者其它異常,關(guān)閉緩存避免內(nèi)存泄漏
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    // 如果緩存策略是可以使用緩存
    if (cacheResponse != null) {
      // 且網(wǎng)絡(luò)響應(yīng)碼是304 HTTP_NOT_MODIFIED說明本地緩存可以使用
      // 且網(wǎng)絡(luò)響應(yīng)是沒有響應(yīng)體的
      // 這時(shí)候就合并緩存響應(yīng)和網(wǎng)絡(luò)響應(yīng)并構(gòu)建新的響應(yīng)
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        Response 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();
        // 在合并標(biāo)頭之后但在剝離Content-Encoding標(biāo)頭之前更新緩存
        cache.trackConditionalCacheHit();
        cache.update(cacheResponse, response);
        return response;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    // 走到這里說明緩存策略是不可以使用緩存或本地緩存不可用
    // 那就通過網(wǎng)絡(luò)響應(yīng)構(gòu)建響應(yīng)對象
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();
    //對數(shù)據(jù)進(jìn)行緩存
    if (cache != null) {
      // 如果響應(yīng)有響應(yīng)體且響應(yīng)可以緩存 那就將響應(yīng)寫入到緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // 緩存響應(yīng)的部分信息
        CacheRequest cacheRequest = cache.put(response);
        // 緩存響應(yīng)體并返回響應(yīng)
        return cacheWritingResponse(cacheRequest, response);
      }
      //通過請求方法判斷需不需要進(jìn)行緩存
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          // 不合法就刪除緩存
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }
    //更新緩存
    return response;
  }

CacheInterceptor ,在發(fā)出請求前,判斷是否命中緩存。如果命中則可以不請求,直接使用緩存的響應(yīng)。 (只會存在Get請求的緩存)

步驟為:

  • 1、從緩存中獲得對應(yīng)請求的響應(yīng)緩存
  • 2、創(chuàng)建 CacheStrategy ,創(chuàng)建時(shí)會判斷是否能夠使用緩存,在 CacheStrategy 中存在兩個(gè)成員: networkRequest與 cacheResponse 。他們的組合如下:

攔截器通過CacheStrategy判斷使用緩存或發(fā)起網(wǎng)絡(luò)請求。此對象中的networkRequest與cacheResponse分別代表 需要發(fā)起請求或者直接使用緩存



即:networkRequest存在則優(yōu)先發(fā)起網(wǎng)絡(luò)請求,否則使用cacheResponse緩存,若都不存在則請求失?。?/p>

  • 3、交給下一個(gè)責(zé)任鏈繼續(xù)處理
  • 4、后續(xù)工作,返回304則用緩存的響應(yīng);否則使用網(wǎng)絡(luò)響應(yīng)并緩存本次響應(yīng)(只緩存Get請求的響應(yīng))

緩存攔截器的工作說起來比較簡單,但是具體的實(shí)現(xiàn),需要處理的內(nèi)容很多。在緩存攔截器中判斷是否可以使用緩存,或是請求服務(wù)器都是通過 CacheStrategy 判斷。

緩存策略
CacheStrategy 。首先需要認(rèn)識幾個(gè)請求頭與響應(yīng)頭

其中 Cache-Control 可以在請求頭存在,也能在響應(yīng)頭存在,對應(yīng)的value可以設(shè)置多種組合

  • 1、max-age=[秒] :資源最大有效時(shí)間;
  • 2、public :表明該資源可以被任何用戶緩存,比如客戶端,代理服務(wù)器等都可以緩存資源;
  • 3、private :表明該資源只能被單個(gè)用戶緩存,默認(rèn)是private
  • 4、no-store :資源不允許被緩存
  • 5、no-cache :(請求)不使用緩存
  • 6、immutable :(響應(yīng))資源不會改變
  • 7、min-fresh=[秒] :(請求)用戶認(rèn)為這個(gè)緩存有效的時(shí)長
  • 8、must-revalidate :(響應(yīng))不允許使用過期緩存
  • 9、max-stale=[秒] :(請求)緩存過期后多久內(nèi)仍然有效

假設(shè)存在max-age=100,min-fresh=20。這代表了用戶認(rèn)為這個(gè)緩存的響應(yīng),從服務(wù)器創(chuàng)建響應(yīng) 到 能夠緩存使用的時(shí)間為100-20=80s。但是如果max-stale=100。這代表了緩存有效時(shí)間80s過后,仍然允許使用100s,可以看成緩存有效時(shí)長為180s。

緩存檢測過程

1.3.4、ConnectInterceptor 連接攔截器

鏈接流程

在 okhttp底層是通過 socket 的方式于服務(wù)端進(jìn)行連接的,并且在連接建立之后會通過 okio 獲取通向 server 端的輸入流 Source 和輸出流 Sink。

@Override 
public Response intercept(Chain chain) throws IOException {
   RealInterceptorChain realChain = (RealInterceptorChain) chain;
   Request request = realChain.request();
    //獲取可復(fù)用流
   StreamAllocation streamAllocation = realChain.streamAllocation();
   boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //創(chuàng)建輸出流
   HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
   //根據(jù)HTTP/1.x(keep-alive)和HTTP/2(流復(fù)用)的復(fù)用機(jī)制,發(fā)起連接
   RealConnection connection = streamAllocation.connection();
   return realChain.proceed(request, streamAllocation, httpCodec, connection);
 }

雖然代碼量很少,實(shí)際上大部分功能都封裝到其它類去了,這里只是調(diào)用而已。
首先我們看到的 StreamAllocation 這個(gè)對象是在第一個(gè)攔截器:重定向攔截器創(chuàng)建的,但是真正使用的地方卻在這里。
"當(dāng)一個(gè)請求發(fā)出,需要建立連接,連接建立后需要使用流用來讀寫數(shù)據(jù)";而這個(gè)StreamAllocation就是協(xié)調(diào)請求、連接與數(shù)據(jù)流三者之間的關(guān)系,它負(fù)責(zé)為一次請求尋找連接,然后獲得流來實(shí)現(xiàn)網(wǎng)絡(luò)通信。
這里使用的 newStream 方法實(shí)際上就是去查找或者建立一個(gè)與請求主機(jī)有效的連接,返回的 HttpCodec 中包含了輸入輸出流,并且封裝了對HTTP請求報(bào)文的編碼與解碼,直接使用它就能夠與請求主機(jī)完成HTTP通信。
StreamAllocation 中簡單來說就是維護(hù)連接: RealConnection ——封裝了Socket與一個(gè)Socket連接池。可復(fù)用的 RealConnection 需要

  public boolean isEligible(Address address, @Nullable Route route) {
    // If this connection is not accepting new streams, we're done.
    if (allocations.size() >= allocationLimit || noNewStreams) return false;

    // If the non-host fields of the address don't overlap, we're done.
    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;

    // If the host exactly matches, we're done: this connection can carry the address.
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }
    // 1. This connection must be HTTP/2.
    if (http2Connection == null) return false;

    // 2. The routes must share an IP address. This requires us to have a DNS address for both
    // hosts, which only happens after route planning. We can't coalesce connections that use a
    // proxy, since proxies don't tell us the origin server's IP address.
    if (route == null) return false;
    if (route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (this.route.proxy().type() != Proxy.Type.DIRECT) return false;
    if (!this.route.socketAddress().equals(route.socketAddress())) return false;

    // 3. This connection's server certificate's must cover the new host.
    if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
    if (!supportsUrl(address.url())) return false;

    // 4. Certificate pinning must match the host.
    try {
      address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
    } catch (SSLPeerUnverifiedException e) {
      return false;
    }
    return true; // The caller's address can be carried by this connection.
  }

以上代碼解析:

 if (allocations.size() >= allocationLimit || noNewStreams) return false;

連接到達(dá)最大并發(fā)流或者連接不允許建立新的流;如http1.x正在使用的連接不能給其他人用(最大并發(fā)流為:1)或者連接被關(guān)閉;那就不允許復(fù)用;

    if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
    if (address.url().host().equals(this.route().address().url().host())) {
      return true; // This connection is a perfect match.
    }

DNS、代理、SSL證書、服務(wù)器域名、端口完全相同則可復(fù)用;
如果上述條件都不滿足,在HTTP/2的某些場景下可能仍可以復(fù)用(http2先不管)。
所以綜上,如果在連接池中找到個(gè)連接參數(shù)一致并且未被關(guān)閉沒被占用的連接,則可以復(fù)用。

總結(jié)

這個(gè)攔截器中的所有實(shí)現(xiàn)都是為了獲得一份與目標(biāo)服務(wù)器的連接,在這個(gè)連接上進(jìn)行HTTP數(shù)據(jù)的收發(fā)。

1.3.5、CallServerInterceptor 請求服務(wù)器攔截器(OkHttp核心攔截器,網(wǎng)絡(luò)交互的關(guān)鍵)

主要負(fù)責(zé)將請求寫入到 IO 流當(dāng)中,并且從 IO 流當(dāng)中獲取服務(wù)端返回給客服端的響應(yīng)數(shù)據(jù)。
CallServerInterceptor 在 ConnectInterceptor 攔截器的功能就是負(fù)責(zé)與服務(wù)器建立 Socket 連接,并且創(chuàng)建了一個(gè) HttpStream 它包括通向服務(wù)器的輸入流和輸出流。而接下來的 CallServerInterceptor 攔截器的功能使用 HttpStream 與服務(wù)器進(jìn)行數(shù)據(jù)的讀寫操作的
okhttp的攔截器就是在intercept(Chain chain)的回調(diào)中對Request和Response進(jìn)行修改,然后直接返回了response 而不是進(jìn)行繼續(xù)遞歸,具體執(zhí)行RealConnection里面是通過OKio實(shí)現(xiàn)的。在okhttp中,網(wǎng)絡(luò)連接也是一個(gè)攔截器(CallServerInterceptor),他是最后一個(gè)被調(diào)用的,負(fù)責(zé)將request寫入網(wǎng)絡(luò)流中,并從網(wǎng)絡(luò)流中讀取服務(wù)器返回的信息寫入Response中返回給客戶端。

@Override public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    HttpCodec httpCodec = realChain.httpStream();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    RealConnection connection = (RealConnection) realChain.connection();
    Request request = realChain.request();
    //發(fā)送請求的時(shí)間戳
    long sentRequestMillis = System.currentTimeMillis();
    //寫入請求頭信息
    httpCodec.writeRequestHeaders(request);
     //發(fā)送header數(shù)據(jù)
    httpCodec.writeRequestHeaders(request);
    Response.Builder responseBuilder = null;
    //根據(jù)是否支持100-continue,發(fā)送body數(shù)據(jù)
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // HTTP2多路復(fù)用,不需要關(guān)閉socket,不管!.
        streamAllocation.noNewStreams();
      }
    }
    //結(jié)束請求
    httpCodec.finishRequest();
    if (responseBuilder == null) {
     realChain.eventListener().responseHeadersStart(realChain.call());
      //讀取響應(yīng)頭信息
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        //發(fā)送請求的時(shí)間
        .sentRequestAtMillis(sentRequestMillis)
        //接收到響應(yīng)的時(shí)間
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

      int code = response.code();
      //response處理
      if (code == 100) {
      responseBuilder = httpCodec.readResponseHeaders(false);
      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }
    realChain.eventListener()
            .responseHeadersEnd(realChain.call(), response);
    if (forWebSocket && code == 101) {
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;
  }

上面代碼解析流程:
CallServerInterceptor ,利用 HttpCodec 發(fā)出請求到服務(wù)器并且解析生成 Response 。
首先調(diào)用 httpCodec.writeRequestHeaders(request); 將請求頭寫入到緩存中(直到調(diào)用 flushRequest() 才真正發(fā)送給服務(wù)器)。然后馬上進(jìn)行第一個(gè)邏輯判斷。

    //根據(jù)是否支持100-continue,發(fā)送body數(shù)據(jù)
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        httpCodec.flushRequest();
        realChain.eventListener().responseHeadersStart(realChain.call());
        responseBuilder = httpCodec.readResponseHeaders(true);
      }

      if (responseBuilder == null) {
        // Write the request body if the "Expect: 100-continue" expectation was met.
        realChain.eventListener().requestBodyStart(realChain.call());
        long contentLength = request.body().contentLength();
        CountingSink requestBodyOut =
            new CountingSink(httpCodec.createRequestBody(request, contentLength));
        BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);

        request.body().writeTo(bufferedRequestBody);
        bufferedRequestBody.close();
        realChain.eventListener()
            .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
      } else if (!connection.isMultiplexed()) {
        // HTTP2多路復(fù)用,不需要關(guān)閉socket,不管!.
        streamAllocation.noNewStreams();
      }
    }

整個(gè)if都和一個(gè)請求頭有關(guān): Expect: 100-continue 。這個(gè)請求頭代表了在發(fā)送請求體之前需要和服務(wù)器確定是否愿意接受客戶端發(fā)送的請求體。所以 permitsRequestBody 判斷為是否會攜帶請求體的方式(POST),如果命中 if,則會先給服務(wù)器發(fā)起一次查詢是否愿意接收請求體,這時(shí)候如果服務(wù)器愿意會響應(yīng)100(沒有響應(yīng)體,responseBuilder 即為nul)。這時(shí)候才能夠繼續(xù)發(fā)送剩余請求數(shù)據(jù)。
但是如果服務(wù)器不同意接受請求體,那么我們就需要標(biāo)記該連接不能再被復(fù)用,調(diào)用 noNewStreams() 關(guān)閉相關(guān)的 Socket。

    if (responseBuilder == null) {
     realChain.eventListener().responseHeadersStart(realChain.call());
      //讀取響應(yīng)頭信息
      responseBuilder = httpCodec.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(streamAllocation.connection().handshake())
        //發(fā)送請求的時(shí)間
        .sentRequestAtMillis(sentRequestMillis)
        //接收到響應(yīng)的時(shí)間
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

這時(shí) responseBuilder 的情況即為:

  • 1、POST方式請求,請求頭中包含 Expect ,服務(wù)器允許接受請求體,并且已經(jīng)發(fā)出了請求體, responseBuilder為null;
  • 2、POST方式請求,請求頭中包含 Expect ,服務(wù)器不允許接受請求體, responseBuilder 不為null
  • 3、POST方式請求,未包含 Expect ,直接發(fā)出請求體, responseBuilder 為null;
  • 4、POST方式請求,沒有請求體, responseBuilder 為null;
  • 5、GET方式請求, responseBuilder 為null;

對應(yīng)上面的5種情況,讀取響應(yīng)頭并且組成響應(yīng) Response ,注意:此 Response 沒有響應(yīng)體。同時(shí)需要注意的是,如果服務(wù)器接受 Expect: 100-continue 這是不是意味著我們發(fā)起了兩次 Request ?那此時(shí)的響應(yīng)頭是第一次查詢服務(wù)器是否支持接受請求體的,而不是真正的請求對應(yīng)的結(jié)果響應(yīng)。所以緊接著:

      int code = response.code();
      //response處理
      if (code == 100) {
      responseBuilder = httpCodec.readResponseHeaders(false);
      response = responseBuilder
              .request(request)
              .handshake(streamAllocation.connection().handshake())
              .sentRequestAtMillis(sentRequestMillis)
              .receivedResponseAtMillis(System.currentTimeMillis())
              .build();

      code = response.code();
    }

如果響應(yīng)是100,這代表了是請求 Expect: 100-continue 成功的響應(yīng),需要馬上再次讀取一份響應(yīng)頭,這才是真正的請求對應(yīng)結(jié)果響應(yīng)頭。

    if (forWebSocket && code == 101) {
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(httpCodec.openResponseBody(response))
          .build();
    }

    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      streamAllocation.noNewStreams();
    }

    if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
      throw new ProtocolException(
          "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
    }
    return response;

forWebSocket 代表websocket的請求,我們直接進(jìn)入else,這里就是讀取響應(yīng)體數(shù)據(jù)。然后判斷請求和服務(wù)器是不是都希望長連接,一旦有一方指明 close ,那么就需要關(guān)閉 socket 。而如果服務(wù)器返回204/205,一般情況而言不會存在這些返回碼,但是一旦出現(xiàn)這意味著沒有響應(yīng)體,但是解析到的響應(yīng)頭中包含 Content-Lenght 且不為 0,這表響應(yīng)體的數(shù)據(jù)字節(jié)長度。此時(shí)出現(xiàn)了沖突,直接拋出協(xié)議異常

二、 RealInterceptorChain 攔截器鏈

  • 當(dāng)發(fā)送一個(gè)請求的時(shí)候,實(shí)質(zhì)OkHttp會通過一個(gè)攔截器的鏈來執(zhí)行OkHttp的請求。
  • 這就是所謂的攔截器鏈,執(zhí)行 RetryAndFollowUpInterceptor => 執(zhí)行 BridgeInterceptor => 執(zhí)行 CacheInterceptor => 執(zhí)行 ConnectInterceptor => 執(zhí)行 CallServerInterceptor => 響應(yīng)到
    ConnectInterceptor => 響應(yīng)到 CacheInterceptor => 響應(yīng)到 BridgeInterceptor => 響應(yīng)到 RetryAndFollowUpInterceptor
public final class RealInterceptorChain implements Interceptor.Chain {
  private final List<Interceptor> interceptors;
  //在RetryAndFollowUpInterceptor中new的
  private final StreamAllocation streamAllocation;
  //在ConnectInterceptor中new的
  private final HttpCodec httpCodec;
  //在ConnectInterceptor中new的
  private final RealConnection connection;
  //標(biāo)識應(yīng)該取攔截器鏈表里面的第幾個(gè)攔截器
  private final int index;  //通過index + 1
  private int calls;  //通過call++
  private final Request request;

  public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
      HttpCodec httpCodec, RealConnection connection, int index, Request request) {
    this.interceptors = interceptors;
    this.connection = connection;
    this.streamAllocation = streamAllocation;
    this.httpCodec = httpCodec;
    this.index = index;
    this.request = request;
  }

  @Override public Connection connection() {
    return connection;
  }

  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

  public HttpCodec httpStream() {
    return httpCodec;
  }

  @Override public Request request() {
    return request;
  }
  //執(zhí)行繼續(xù)攔截操作
  @Override public Response proceed(Request request) throws IOException {
    return proceed(request, streamAllocation, httpCodec, connection);
  }
/**
  * 依次取出攔截器鏈表中的每個(gè)攔截器去獲取Response
  */
  public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
      RealConnection connection) throws IOException {
     // 1、迭代攔截器集合
    if (index >= interceptors.size()) throw new AssertionError();
     //2、記錄本方法調(diào)用次數(shù),創(chuàng)建一次實(shí)例,call加1
    calls++;

    //如果已經(jīng)為該Request創(chuàng)建了stream,就不再繼續(xù)創(chuàng)建了
    if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must retain the same host and port");
    }

    //  如果已經(jīng)為該Request創(chuàng)建了stream,那該方法只能調(diào)用一次
    if (this.httpCodec != null && calls > 1) {
      throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
          + " must call proceed() exactly once");
    }

   //創(chuàng)建新的攔截器鏈對象, 并將計(jì)數(shù)器+1
    RealInterceptorChain next = new RealInterceptorChain(
        interceptors, streamAllocation, httpCodec, connection, index + 1, request);
    //取出下一個(gè)攔截器
    Interceptor interceptor = interceptors.get(index);
    //執(zhí)行攔截器的intercept方法獲取結(jié)果,并將新的攔截器鏈對象傳入
    Response response = interceptor.intercept(next);

    // 確保該方法只能調(diào)用一次
    if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
      throw new IllegalStateException("network interceptor " + interceptor
          + " must call proceed() exactly once");
    }

    // Confirm that the intercepted response isn't null.
    if (response == null) {
      throw new NullPointerException("interceptor " + interceptor + " returned null");
    }

    return response;
  }
}

三、總結(jié)

在這個(gè)攔截器中就是完成HTTP協(xié)議報(bào)文的封裝與解析。

OkHttp網(wǎng)絡(luò)請求的整體接口圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容