OkHttp原理解析2(攔截器篇)

image.png

Hello小伙伴們,現(xiàn)在公司已經(jīng)恢復了正常辦公散罕,但是疫情依舊還在繼續(xù)分歇。最近工作實在是有點小忙,導致更新有點緩慢欧漱,實在抱歉职抡,本文是OkHttp原理解析的第二篇, 主要針對OkHttp中幾個 默認攔截器 的具體實現(xiàn)邏輯進行分析误甚。

因為OkHttp的很大一部分邏輯都在攔截器中繁调,因此本文會比較長,同時采用連載更新的方式進行描述靶草,每完成一個攔截器的邏輯分析都會進行更新。

如有對OkHttp的框架流程不太了解的可優(yōu)先閱讀網(wǎng)我上篇博客 OkHttp原理解析1(框架流程篇)


我又要開始表演了~~~

但為了方便后續(xù)描述岳遥,我還是簡單對上文做了個總結(jié)奕翔。大概分為以下幾步。

    1. 創(chuàng)建OkHttpClient(可通過OkHttpClient.Builder創(chuàng)建浩蓉,內(nèi)部設(shè)置一些基礎(chǔ)參數(shù))
    1. 創(chuàng)建 Request 對象設(shè)置 url派继,請求類型等參數(shù)
    1. 通過OkHttpClient.newCall()創(chuàng)建RealCall對象,同時創(chuàng)建了Transmitter對象捻艳。
    1. 通過RealCall.enqueue()或者RealCall.execute()觸發(fā) 異步或同步 請求驾窟。
    1. 調(diào)用OkHttpClient.dispatcher().enqueue(new AsyncCall(responseCallback))做請求準備,循環(huán)runningAsyncCalls和readyAsyncCalls 隊列找出host相同的AsyncCall進行重用认轨,并將readyAsyncCallsAsyncCall轉(zhuǎn)移到runningAsyncCalls中绅络,如果runningAsyncCalls超過64則終止轉(zhuǎn)移,如相同主機計數(shù)器>5則終止轉(zhuǎn)移本AsyncCall。
    1. 循環(huán)runningAsyncCalls調(diào)用AsyncCall.executeOn(executorService())
    • 6.1. AsyncCall為Runnable恩急,執(zhí)行run()方法杉畜,調(diào)用AsyncCall.execute()連帶調(diào)用AsyncCall.getResponseWithInterceptorChain()設(shè)置攔截器List,首先設(shè)置用戶自定義的攔截器衷恭。最后通過RealInterceptorChain.proceed()啟動攔截器此叠。

而攔截器的啟動與運行依賴 責任鏈 模式,大概分為以下3步随珠。

    1. 首先創(chuàng)建RealInterceptorChain對象灭袁,通過procee()判斷各種異常,并獲取當前Interceptor對象窗看。
    1. 然后 通過Interceptor.intercept(RealInterceptorChain)啟動當前攔截器邏輯茸歧,并且觸發(fā)下一個攔截器啟動。
    1. 如果當前攔截器出現(xiàn)異常等錯誤烤芦,則終止責任鏈举娩。

我們從添加攔截的的位置開始本文介紹,其實上文已經(jīng)做了描述构罗,源碼如下所示铜涉。

//RealCall.getResponseWithInterceptorChain();
  Response getResponseWithInterceptorChain() throws IOException {
    // 建立一個完整的攔截器堆棧
    List<Interceptor> interceptors = new ArrayList<>();
     //將創(chuàng)建okhttpclient時的攔截器添加到interceptors
    interceptors.addAll(client.interceptors());
    //重試攔截器,負責處理失敗后的重試與重定向
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    //請求轉(zhuǎn)化攔截器(用戶請求轉(zhuǎn)為服務(wù)器請求遂唧,服務(wù)器響應轉(zhuǎn)為用戶響應)
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //緩存攔截器芙代。負責
    //1.根據(jù)條件,緩存配置盖彭,有效期等返回緩存響應纹烹,也可增加到緩存。
    //2.設(shè)置請求頭(If-None-Match召边、If-Modified-Since等) 服務(wù)器可能返回304(未修改)
    //3.可配置自定義的緩存攔截器铺呵。
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //網(wǎng)絡(luò)連接攔截器,主要負責和服務(wù)器建立連接隧熙。
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      //創(chuàng)建okhttpclient時設(shè)置的networkInterceptors
      interceptors.addAll(client.networkInterceptors());
    }
    //數(shù)據(jù)流攔截器片挂,主要負責像服務(wù)器發(fā)送和讀取數(shù)據(jù),請求報文封裝和解析贞盯。
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //責任鏈模式的創(chuàng)建音念。
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, transmitter, null, 0,
        originalRequest, this, client.connectTimeoutMillis(),
        client.readTimeoutMillis(), client.writeTimeoutMillis());

    boolean calledNoMoreExchanges = false;
    try {
      //啟動責任鏈
      Response response = chain.proceed(originalRequest);
      if (transmitter.isCanceled()) {
        closeQuietly(response);
        throw new IOException("Canceled");
      }
      return response;
    } catch (IOException e) {
      calledNoMoreExchanges = true;
      throw transmitter.noMoreExchanges(e);
    } finally {
      if (!calledNoMoreExchanges) {
        transmitter.noMoreExchanges(null);
      }
    }
  }

主要看我注釋的部分,OkHttp主要涉及了以下幾個默認攔截器

    1. client.interceptors();//用戶自定義攔截器
    1. RetryAndFollowUpInterceptor(client)//失敗重試攔截器
    1. BridgeInterceptor(client.cookieJar())//請求轉(zhuǎn)化攔截器
    1. CacheInterceptor(client.internalCache())//緩存攔截器
    1. ConnectInterceptor(client)//網(wǎng)絡(luò)連接攔截器
    1. CallServerInterceptor(forWebSocket)//數(shù)據(jù)流攔截器

OkHttp會把用戶自定義的攔截器默認放到攔截器列表的頭部躏敢,以方面優(yōu)先執(zhí)行闷愤,然后通過創(chuàng)建RealInterceptorChain對象,并調(diào)用RealInterceptorChain.proceed()啟動第一個攔截器件余,然后調(diào)用攔截器的interceptor.intercept(next)執(zhí)行第一個攔截器的邏輯并將下一個攔截器RealInterceptorChain對象傳入依此類推讥脐。接下來我們就一個個進行分析遭居。


1. client.interceptors() 連接失敗重試攔截器


用戶自定義的攔截器具有優(yōu)先執(zhí)行的特權(quán),具體執(zhí)行用戶自定義的邏輯攘烛,不過多介紹魏滚。


2. RetryAndFollowUpInterceptor(client) 連接失敗重試攔截器


首先開始的是RetryAndFollowUpInterceptor失敗重試攔截器,這個攔截器是可以在OkHttpClient.Builder對象中通過retryOnConnectionFailure(boolean retryOnConnectionFailure)設(shè)置是否開啟坟漱,默認構(gòu)建的OkHttpClient對象是開啟的鼠次。
該攔截器主要的職責官方注釋解釋了這么幾點。

    1. 無法訪問ip地址芋齿,如果URL的主機有多個IP地址腥寇,則無法訪問任何單個IP地址不會使整個請求失敗。這可以提高多宿服務(wù)的可用性觅捆。
    1. 過時的池連接赦役,通過ConnectionPool重用套接字以減少請求延遲,但這些連接偶爾會超時栅炒。
    1. 無法訪問的代理服務(wù)器掂摔,可以使用ProxySelector,最終返回到直接連接赢赊。

說實話乙漓,這描述略顯抽象,要不是我用過释移,我真不知道他在扯啥叭披,咱們還是通過源碼看下真像吧。

//我們應該嘗試多少重定向和驗證挑戰(zhàn)玩讳?Chrome遵循21個重定向涩蜘;Firefox、curl和wget遵循20熏纯;Safari遵循16同诫;HTTP/1.0建議5。
private static final int MAX_FOLLOW_UPS = 20;

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

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      //通過transmitter創(chuàng)建ExchangeFinder樟澜,Address误窖,RouteSelector三個對象
      transmitter.prepareToConnect(request);
      //判斷如果當前請求結(jié)束了則拋出異常,可通過transmitter終止請求。
      if (transmitter.isCanceled()) {
        throw new IOException("Canceled");
      }

      Response response;
      boolean success = false;
      try {
        //啟動下一個攔截器
        response = realChain.proceed(request, transmitter, null);
        //執(zhí)行順利則通過該字段退出循環(huán)往扔。
        success = true;
      } catch (RouteException e) {
        // 如果是路由異常。請求還沒發(fā)送熊户。
        if (!recover(e.getLastConnectException(), transmitter, false, request)) {
          throw e.getFirstConnectException();
        }
        continue;
      } catch (IOException e) {
        // 嘗試與服務(wù)器通信失敗萍膛。請求可能已發(fā)送。
        boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
        if (!recover(e, transmitter, requestSendStarted, request)) throw e;
        continue;
      } finally {
        //網(wǎng)絡(luò)調(diào)用引發(fā)異常嚷堡。釋放所有資源蝗罗。
        if (!success) {
          transmitter.exchangeDoneDueToException();
        }
      }

      // 如果body不為空
      if (priorResponse != null) {
        //獲得新的response
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

      Exchange exchange = Internal.instance.exchange(response);
      Route route = exchange != null ? exchange.connection().route() : null;
      // 調(diào)用followUpRequest()查看響應是否需要重定向艇棕,不需要就返回當前請求,如果需要返回新的請求
      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;
      }

      closeQuietly(response.body());
      if (transmitter.hasExchange()) {
        exchange.detachWithViolence();
      }
      //如果重定向次數(shù)超過最大次數(shù)拋出異常
      if (++followUpCount > MAX_FOLLOW_UPS) {
        throw new ProtocolException("Too many follow-up requests: " + followUpCount);
      }

      request = followUp;
      priorResponse = response;
    }
  }

  //RetryAndFollowUpInterceptor.recover()
  //報告并嘗試從與服務(wù)器通信失敗中恢復串塑。如果{@code e}可恢復沼琉,則返回true;
  //如果失敗是永久性的桩匪,則返回false打瘪。只有在緩沖了正文或在發(fā)送請求之前發(fā)生故障時,才可以恢復具有正文的請求傻昙。
 private boolean recover(IOException e, Transmitter transmitter,
      boolean requestSendStarted, Request userRequest) {
    // 用戶設(shè)置的禁止重試
    if (!client.retryOnConnectionFailure()) return false;

    // 不能再發(fā)送請求體
    if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;

    // 異常是致命的
    if (!isRecoverable(e, requestSendStarted)) return false;

    // 沒有更多的路線可以嘗試焕刮。
    if (!transmitter.canRetry()) return false;

    //對于故障恢復同规,請對新連接使用同一路由選擇器
    return true;
  }

  /**
   * 查看響應是否需要重定向,不需要就返回當前請求,如果需要返回新的請求邑闲。
   * 找出接收{(diào)@code userResponse}時要發(fā)出的HTTP請求。這將添加身份驗證頭癣诱、遵循重定向或處理客戶端請求超時匿沛。
   * 如果后續(xù)操作不必要或不適用,則返回null须板。
   */
  private Request followUpRequest(Response userResponse, @Nullable Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    //獲取響應碼
    int responseCode = userResponse.code();
    //請求方法
    final String method = userResponse.request().method();
    //響應碼分類處理
    switch (responseCode) {
      //407 代理需要身份認證
      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 需要身份認證
      case HTTP_UNAUTHORIZED:
        return client.authenticator().authenticate(route, userResponse);

      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
        // or HEAD, the user agent MUST NOT automatically redirect the request"
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
        // fall-through
       // 300多種選擇碰镜。請求的資源可包括多個位置,相應可返回一個資源特征與地址的列表用于用戶終端(例如:瀏覽器)選擇
      case HTTP_MULT_CHOICE:
      // 301永久移動逼纸。請求的資源已被永久的移動到新URI洋措,返回信息會包括新的URI,瀏覽器會自動定向到新URI杰刽。
      case HTTP_MOVED_PERM:
      // 302臨時移動菠发。與301類似。但資源只是臨時被移動贺嫂。
      case HTTP_MOVED_TEMP:
      // 303查看其它地址滓鸠。與301類似。使用GET和POST請求查看
      case HTTP_SEE_OTHER:
        // Does the client allow redirects?
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        // 不要遵循重定向到不支持的協(xié)議第喳。
        if (url == null) return null;

        //如果已配置糜俗,請不要遵循SSL和非SSL之間的重定向。
        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        if (!sameScheme && !client.followSslRedirects()) return null;

        // 大多數(shù)重定向不包括請求正文曲饱。
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        }

        // 跨主機重定向時悠抹,刪除所有身份驗證頭。這對應用程序?qū)觼碚f可能很煩人扩淀,因為它們無法保留它們楔敌。
        if (!sameConnection(userResponse.request().url(), url)) {
          requestBuilder.removeHeader("Authorization");
        }

        return requestBuilder.url(url).build();
      //408 超時
      case 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()) {
          // 應用層指示我們不要重試請求
          return null;
        }

        RequestBody requestBody = userResponse.request().body();
        if (requestBody != null && requestBody.isOneShot()) {
          return null;
        }

        if (userResponse.priorResponse() != null
            && userResponse.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();
      // 503 由于超載或系統(tǒng)維護,服務(wù)器暫時的無法處理客戶端的請求驻谆。延時的長度可包含在服務(wù)器的Retry-After頭信息中
      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;
    }
  }


//Transmitter.prepareToConnect()
public void prepareToConnect(Request request) {
    if (this.request != null) {
      //判斷是不是相同的連接卵凑,如果是則用之前的庆聘。
      if (sameConnection(this.request.url(), request.url()) && exchangeFinder.hasRouteToTry()) {
        return; // Already ready.
      }
      if (exchange != null) throw new IllegalStateException();
      //exchangeFinder
      if (exchangeFinder != null) {
        maybeReleaseConnection(null, true);
        exchangeFinder = null;
      }
    }

    this.request = request;
    this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
        call, eventListener);
  }

//Transmitter.createAddress()
 private Address createAddress(HttpUrl url) {
    SSLSocketFactory sslSocketFactory = null;
    HostnameVerifier hostnameVerifier = null;
    CertificatePinner certificatePinner = null;
    if (url.isHttps()) {
      //https的設(shè)置
      sslSocketFactory = client.sslSocketFactory();
      hostnameVerifier = client.hostnameVerifier();
      certificatePinner = client.certificatePinner();
    }

    return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
        sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
        client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector());
  }

//ExchangeFinder.ExchangeFinder()
  ExchangeFinder(Transmitter transmitter, RealConnectionPool connectionPool,
      Address address, Call call, EventListener eventListener) {
    this.transmitter = transmitter;
    this.connectionPool = connectionPool;
    this.address = address;
    this.call = call;
    this.eventListener = eventListener;
    //創(chuàng)建路由選擇器
    this.routeSelector = new RouteSelector(
        address, connectionPool.routeDatabase, call, eventListener);
  }
//RouteSelector.RouteSelector()
  RouteSelector(Address address, RouteDatabase routeDatabase, Call call,
      EventListener eventListener) {
    this.address = address;
    this.routeDatabase = routeDatabase;
    this.call = call;
    this.eventListener = eventListener;

    resetNextProxy(address.url(), address.proxy());
  }

該攔截器的邏輯很多,但其實主要做兩件事勺卢,一個是重試伙判,一個是重定向,邏輯流程大概是這樣的黑忱。

    1. 通過realChain.transmitter獲取了transmitter對象宴抚,并啟用一個while死循環(huán),
    1. 然后通過transmitter.prepareToConnect(request)transmitter創(chuàng)建ExchangeFinder杨何,Address酱塔,RouteSelector三個對象,并判斷了是否是相同的連接,是否需要maybeReleaseConnection(),重置ExchangeFinder危虱。
    • 2.1 . ExchangeFinder此攔截器只是創(chuàng)建ExchangeFinder羊娃,但ExchangeFinder中有個find()方法主要通過內(nèi)部的 findHealthyConnection() 從 connectionPool 中找到一個可用的連接,這個連接可能是復用的埃跷,并 connect(),從而得到 輸入/輸出 流 (source/sink) 蕊玷,返回一個 Exchange 給 CallServerIntercepter , 通過這個 Exchange 就可以添加請求頭和請求體,并讀取響應頭和響應體弥雹,來交給上面的 Intercepter垃帅,層層向上傳遞。
    • 2.2 . Address為請求參數(shù)的封裝類剪勿,包含url贸诚,端口,DNS厕吉,SSL酱固,Proxy,ProxySelector头朱,SocketFactory运悲,主機名驗證,證書校驗等邏輯项钮。
    • 2.3 . RouteSelector主要來選擇路由班眯,主要做三件事。1.收集所有的可用路由烁巫。2.選擇可用路由署隘。3.借助RouteDatabase內(nèi)的Set對象來維護連接失敗的路由信息,防止去連接失敗路由浪費時間亚隙。
    1. 啟動攔截器列表的下一個攔截器磁餐。
    1. 判斷全局變量priorResponse是否為null,如果不為空則代表請求成功了恃鞋。
    1. 執(zhí)行 followUpRequest()查看響應是否需要重定向崖媚,如果不需要重定向則返回當前請求
    1. 判斷重定向次數(shù),如果超過最大值則退出恤浪。
    1. 重置request畅哑,并把當前的Response保存到priorResponse,進入下一次的while循環(huán)水由。

總結(jié)來說:
通過while死循環(huán)來獲取response荠呐,每次循環(huán)開始都根據(jù)條件獲取下一個request,如果沒有request砂客,則返回response泥张,退出循環(huán)。而獲取的request 的條件是根據(jù)上一次請求的response 狀態(tài)碼確定的鞠值,在循環(huán)體中同時創(chuàng)建了一些后續(xù)需要的對象


3. BridgeInterceptor(client.cookieJar())//請求轉(zhuǎn)化攔截器


  • 咱先不看邏輯彤恶,就瞅這個名字。
    Bridge:橋本刽, Interceptor:攔截器世囊。 連起來翻譯: 橋攔截器蝙寨? 這是 堵橋 的意思么?那得先給我把98K才行啊虹菲,最好能配個手雷毕源。

  • 在我簡單看了下源碼的注釋以及邏輯后發(fā)現(xiàn) 這其實是將用戶定義的對象Request轉(zhuǎn)換為網(wǎng)絡(luò)請求以及將網(wǎng)絡(luò)返回轉(zhuǎn)回用戶Response對象的橋梁址愿,那具體是怎么充當橋梁角色的呢,我們看下源代碼。

 public BridgeInterceptor(CookieJar cookieJar) {
    this.cookieJar = cookieJar;
  }
  • 首先是BridgeInterceptor的構(gòu)造函數(shù),在創(chuàng)建BridgeInterceptor對象的時候需要傳入CookieJar,CookieJar獲取的是OkHttpClient對象中的cookieJar,如果用戶沒有設(shè)置則默認使用OkHttpClient.Builder中的cookieJar,而OkHttpClient.Builder.cookieJar默認為CookieJar.NO_COOKIES白魂。
 @Override public Response intercept(Chain chain) throws IOException {
    Request userRequest = chain.request();
    Request.Builder requestBuilder = userRequest.newBuilder();

    RequestBody body = userRequest.body();
    //通過用戶Request對象對請求頭進行包裝上岗。
    if (body != null) {
      MediaType contentType = body.contentType();
      if (contentType != null) {
        requestBuilder.header("Content-Type", contentType.toString());
      }

      long contentLength = body.contentLength();
      if (contentLength != -1) {
        requestBuilder.header("Content-Length", Long.toString(contentLength));
        requestBuilder.removeHeader("Transfer-Encoding");
      } else {
        requestBuilder.header("Transfer-Encoding", "chunked");
        requestBuilder.removeHeader("Content-Length");
      }
    }

    if (userRequest.header("Host") == null) {
      requestBuilder.header("Host", hostHeader(userRequest.url(), false));
    }

    if (userRequest.header("Connection") == null) {
      requestBuilder.header("Connection", "Keep-Alive");
    }

    // If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
    // the transfer stream.
    // 如果用戶沒有設(shè)置Accept-Encoding字段肴掷,okhttp默認使用Gzip進行壓縮,
    //設(shè)置Accept-Encoding為Gzip的目的就是告訴服務(wù)器客戶端能接受的數(shù)據(jù)編碼類型呆瞻。
    boolean transparentGzip = false;
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true;
      requestBuilder.header("Accept-Encoding", "gzip");
    }
    //添加cookie的請求頭
    List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
    if (!cookies.isEmpty()) {
      requestBuilder.header("Cookie", cookieHeader(cookies));
    }

    if (userRequest.header("User-Agent") == null) {
      //Version.userAgent()返回的是okhttp的版本號台夺。
      requestBuilder.header("User-Agent", Version.userAgent());
    }
    //啟用責任鏈的下一個攔截器痴脾,并接收下一個攔截器的處理結(jié)果冤灾。
    Response networkResponse = chain.proceed(requestBuilder.build());
    //獲取服務(wù)器返回的 Cookie瞳购,設(shè)置接收頭
    HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());
    
    Response.Builder responseBuilder = networkResponse.newBuilder()
        .request(userRequest);
    //判斷服務(wù)器是否支持gzip壓縮年堆,如果支持吞杭,則將壓縮提交給Okio庫來處理
    if (transparentGzip
        && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
        && HttpHeaders.hasBody(networkResponse)) {
      GzipSource responseBody = new GzipSource(networkResponse.body().source());
      Headers strippedHeaders = networkResponse.headers().newBuilder()
          .removeAll("Content-Encoding")
          .removeAll("Content-Length")
          .build();
      responseBuilder.headers(strippedHeaders);
      String contentType = networkResponse.header("Content-Type");
      responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
    }

    return responseBuilder.build();
  }

總結(jié)
BridgeInterceptor的代碼也是一片,但是功能相對單一变丧,主要可以分為兩個部分來理解芽狗。

    1. 將用戶的請求Request包裝成網(wǎng)絡(luò)請求。
    1. 將網(wǎng)絡(luò)返回Response 包裝成用戶需要的返回對象痒蓬。

包裝網(wǎng)絡(luò)請求主要對請求頭做了特殊的處理童擎,具體可以看以上注釋。比較特殊的是Accept-Encoding攻晒。
如果用戶沒有自定義添加Accept-Encoding顾复,那框架會默認添加gzip,同時返回的數(shù)據(jù)也通過gzip解碼鲁捏。
自動解壓時需要注意芯砸,會移除Content-Encoding和Content-Length,所以java上層調(diào)用contentLength時為-1给梅。
如果用戶手動添加了Accept-Encoding則transparentGzip 為false假丧,返回的數(shù)據(jù)框架不負責解壓縮。


4. CacheInterceptor(client.internalCache())//緩存攔截器


  • 這個攔截器看名字大概能知道是用來處理緩存的动羽,至于什么緩存包帚?我用一段話簡短的描述下。
  • http請求后會有緩存生成在客戶端运吓,在有緩存的前提下渴邦,后續(xù)再發(fā)起請求前可以配置兩種策略
    • 1.對比緩存
    • 2.強制緩存

如果使用對比緩存則通過數(shù)據(jù)tag對比或時間對比,然后通過對比差異智能選擇是否使用緩存拘哨,如果使用強制緩存策略則直接使用緩存數(shù)據(jù)几莽。除此之外不同的是對比緩存必然會發(fā)起網(wǎng)絡(luò)請求,只是數(shù)據(jù)沒有變化會通過304狀態(tài)碼告訴客戶端通過緩存獲取數(shù)據(jù)宅静。而強制緩存無需網(wǎng)絡(luò)請求章蚣,直接緩存讀取。

  • 其實上邊這段話大家都能看明白,但就是映射不到具體的場景中纤垂,接下來我寫一個簡單的例子來描述矾策。
        //創(chuàng)建緩存對象,同時設(shè)置緩存位置峭沦,以及緩存的大小贾虽。
        Cache cache = new Cache(new File("mnt/sdcard/package/cache"), 1024 * 1024);
        OkHttpClient okHttpClient = new OkHttpClient
                .Builder()
                //設(shè)置緩存對象
                .cache(cache)
                .build();
        Request request = new Request
                .Builder()
                .url("")
                 //noCache()代表不使用緩存,通過網(wǎng)絡(luò)獲取吼鱼,此處如果是noStore()代表不緩存也不使用緩存蓬豁,如果不寫則默認緩存和使用緩存。
                .cacheControl(new CacheControl.Builder().noCache().build())
                .build();
        Call call = okHttpClient.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {

            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {

            }
        });
  • 以上為自定義緩存的使用方式菇肃,但是具體的內(nèi)部原理是通過責任鏈模式實現(xiàn)的地粪,具體得到CacheInterceptor類中詳細分析,并且只要是責任鏈優(yōu)先看intercept()方法琐谤。
@Override 
public Response intercept(Chain chain) throws IOException {
    //通過本次請求的request對象獲取請求的緩存對象Response 作為候選緩存蟆技,此處如果配置了則cache不為空,沒有配置默認為空斗忌。
    Response cacheCandidate = cache != null
        ? cache.get(chain.request())
        : null;

    long now = System.currentTimeMillis();
    //根據(jù)時間戳质礼,請求request對象,本地候選緩存對象 生成一個緩存策略织阳,以此判斷使用網(wǎng)絡(luò)緩存眶蕉,本地緩存,還是都用唧躲。
    CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
    //不為空則使用網(wǎng)絡(luò)緩存
    Request networkRequest = strategy.networkRequest;
   //不為空使用本地緩存
    Response cacheResponse = strategy.cacheResponse;

    if (cache != null) {
      //跟蹤滿足strategy策略的http響應
      cache.trackResponse(strategy);
    }
     //如果候選緩存為空妻坝,但是本地緩存為空,證明本地緩存失效了惊窖,則關(guān)閉備選緩存cacheCandidate
    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
    }

    //如果禁止使用網(wǎng)絡(luò)緩存且本地緩存也未空則直接返回fial刽宪,code 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ò)請求緩存,則直接返回本地緩存數(shù)據(jù)界酒。
    if (networkRequest == null) {
      return cacheResponse.newBuilder()
          .cacheResponse(stripBody(cacheResponse))
          .build();
    }
    //邏輯至此代表使用網(wǎng)絡(luò)請求圣拄。
    Response networkResponse = null;
    try {
      //責任鏈開始繼續(xù)往下調(diào)用了。
      networkResponse = chain.proceed(networkRequest);
    } finally {
      // 如果網(wǎng)絡(luò)返回對象為空毁欣,但本地候選緩存不為空則關(guān)閉候選緩存庇谆。
      if (networkResponse == null && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }

    //  如果本地緩存存在,通過本地緩存和網(wǎng)絡(luò)返回的響應組合構(gòu)建一個reponse對象
    if (cacheResponse != null) {
      if (networkResponse.code() == HTTP_NOT_MODIFIED) {
        //如果網(wǎng)絡(luò)狀態(tài)碼為304則從本地緩存中獲取
        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();

        // 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;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }
    //邏輯至此說明本地緩存為空凭疮,通過網(wǎng)絡(luò)緩存構(gòu)建Response 對象
    Response response = networkResponse.newBuilder()
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (cache != null) {
      //將網(wǎng)絡(luò)的response添加到緩存
      if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
        // Offer this request to the cache.
        CacheRequest cacheRequest = cache.put(response);
        return cacheWritingResponse(cacheRequest, response);
      }
      //判斷下是否為post請求饭耳,如果是則移除緩存,只保留get請求
      if (HttpMethod.invalidatesCache(networkRequest.method())) {
        try {
          cache.remove(networkRequest);
        } catch (IOException ignored) {
          // The cache cannot be written.
        }
      }
    }

    return response;
  }

總結(jié)
整體來說我把上述的一大段代碼總結(jié)為以下6個步驟执解,可輔助快速了解該攔截器的原理寞肖。

  • 1.首先通過用戶配置的cache對象結(jié)合request請求對象獲取response緩存對象。
  • 2.根據(jù)時間戳,request請求對象新蟆,以及本地的response緩存對象聯(lián)合生成本次請求的緩存策略觅赊,以此策略決定使用本地緩存,網(wǎng)絡(luò)緩存還是都用琼稻。
  • 3.異常情況判斷吮螺,如果用戶配置緩存已經(jīng)獲取到,但本次緩存策略為不使用本地緩存帕翻,則關(guān)閉釋放本地獲取到的緩存數(shù)據(jù)鸠补,如果本次既不使用網(wǎng)絡(luò),也不使用本地則直接返回504錯誤嘀掸。
  • 4.如果不可使用網(wǎng)絡(luò)緩存紫岩,可使用本地緩存的情況下直接return本次緩存。
  • 5.如果使用網(wǎng)絡(luò)緩存則通過責任鏈chain.proceed(networkRequest);繼續(xù)往下一個攔截器傳遞横殴。
  • 6.如果本地緩存不為空,且網(wǎng)絡(luò)請求返回了304狀態(tài)碼卿拴,則通過本地緩存和網(wǎng)絡(luò)返回共同構(gòu)建一個response返回衫仑,且更新到本地緩存。
  • 7.如果本地緩存為空堕花,則將網(wǎng)絡(luò)返回的response返回并將response存在緩存文件中文狱,如果是post請求則再移除response(此處本質(zhì)意思是只緩存get請求,其實可以先判斷在存儲防止添加進去再移除缘挽。)

注:
緩存策略生成涉及幾個規(guī)則

  • 1.用戶創(chuàng)建Request手動配置了noCache()代表不使用本地緩存瞄崇。
  • 2.用戶創(chuàng)建Request手動配置了noStore()()代表不使用本地緩存同時網(wǎng)絡(luò)請求的數(shù)據(jù)也不緩存到本地。
  • 3.通過“If-Modified-Since”字段判斷服務(wù)器的數(shù)據(jù)是否修改過壕曼,如果修改過則返回服務(wù)器的最新數(shù)據(jù)苏研,如果沒修改過可使用本地的緩存數(shù)據(jù)。(判斷規(guī)則通過時間判斷腮郊,如果上次請求的時間>服務(wù)器的最新更新時間則證明此數(shù)據(jù)沒有更新)
  • 4.通過“If-None-Match”字段判斷服務(wù)器數(shù)據(jù)是否修改過摹蘑,如果與服務(wù)器資源的ETag不同則代表數(shù)據(jù)已經(jīng)更改,由服務(wù)器返回轧飞,如果相同則返回304從本地獲取衅鹿。(判斷規(guī)則需要ETag配合,ETag是資源的一個唯一標識过咬,每次請求前通過If-None-Match帶著ETag標識去對比大渤。)

5. ConnectInterceptor(client)//網(wǎng)絡(luò)連接攔截器


該攔截器主要用于處理網(wǎng)絡(luò)連接相關(guān)的攔截器,也是以責任鏈的模式進行調(diào)用的掸绞,因此我們首先看ConnectInterceptor中的intercept()方法泵三。

@Override 
public Response intercept(Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    //獲取到Request請求對象
    Request request = realChain.request();
    //通過realChain獲取到Transmitter對象,至于這個對象是干啥,先不用管
    Transmitter transmitter = realChain.transmitter();

    // 我們需要網(wǎng)絡(luò)來滿足這個要求切黔≡壹梗可能用于驗證條件GET。(前邊是直譯的纬霞,簡單來說是判斷當前請求的類型是post還是get凌埂,如果是post則后續(xù)需要網(wǎng)絡(luò)檢測)
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
    //關(guān)鍵邏輯,通過transmitter.newExchange()創(chuàng)建了Exchange 對象然后調(diào)用realChain.proceed()啟動下一個攔截器傳遞了诗芜。
    Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
    return realChain.proceed(request, transmitter, exchange);
  }
  • 對瞳抓,沒看錯,該攔截器只有以上幾行代碼伏恐。但是連帶邏輯會比較深入孩哑,我們一步一步進行分析,先看下transmitter.newExchange(chain, doExtensiveHealthChecks);內(nèi)部邏輯翠桦。
 /** 返回一個新的Exchange對象横蜒,并且攜帶 request 和 response. */
  Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    synchronized (connectionPool) {
      if (noMoreExchanges) {
        throw new IllegalStateException("released");
      }
      if (exchange != null) {
        throw new IllegalStateException("cannot make a new request because the previous response "
            + "is still open: please call response.close()");
      }
    }
    //只有這句是核心,我們需要繼續(xù)跟蹤
    ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
    Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);

    synchronized (connectionPool) {
      this.exchange = result;
      this.exchangeRequestDone = false;
      this.exchangeResponseDone = false;
      return result;
    }
  }
  • 以上邏輯中只有ExchangeCodec 對象的生成邏輯不太明確销凑,因此我們需要繼續(xù)跟蹤
    exchangeFinder.find(client, chain, doExtensiveHealthChecks);方法丛晌。
  public ExchangeCodec find(OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
    int connectTimeout = chain.connectTimeoutMillis();
    int readTimeout = chain.readTimeoutMillis();
    int writeTimeout = chain.writeTimeoutMillis();
    int pingIntervalMillis = client.pingIntervalMillis();
    boolean connectionRetryEnabled = client.retryOnConnectionFailure();

    try {
      RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
              writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
      return resultConnection.newCodec(client, chain);
    } catch (RouteException e) {
      trackFailure();
      throw e;
    } catch (IOException e) {
      trackFailure();
      throw new RouteException(e);
    }
  }
  • 看到這個find()方法是不是有點熟悉的感覺?開始我還以為是我代碼功力可以跟OKHTTP的作者睥睨了斗幼,后來才想明白原來第一個攔截器RetryAndFollowUpInterceptor中分析過相關(guān)的邏輯澎蛛。
  • 此時我們使用exchangeFinder對象即是通過RetryAndFollowUpInterceptor攔截器中的transmitter.prepareToConnect()創(chuàng)建出來的,我粘貼一下上邊描述的一段話蜕窿。
    • ExchangeFinder中有個find()方法主要通過內(nèi)部的 findHealthyConnection() 從 connectionPool 中找到一個可用的連接谋逻,這個連接可能是復用的,并調(diào)用RealConnection.connect(),從而得到 輸入/輸出 流 (source/sink) 和對應的Http2Connection對象桐经。
  • 最后返回的ExchangeCodec 對象是通過RealConnection .newCodec(client, chain)生成的毁兆,我們繼續(xù)跟進。
  ExchangeCodec newCodec(OkHttpClient client, Interceptor.Chain chain) throws SocketException {
    if (http2Connection != null) {
      return new Http2ExchangeCodec(client, this, chain, http2Connection);
    } else {
      socket.setSoTimeout(chain.readTimeoutMillis());
      source.timeout().timeout(chain.readTimeoutMillis(), MILLISECONDS);
      sink.timeout().timeout(chain.writeTimeoutMillis(), MILLISECONDS);
      return new Http1ExchangeCodec(client, this, source, sink);
    }
  }
  • 至此邏輯已經(jīng)比較清晰了阴挣,RealConnection.newCodec()方法的返回值為ExchangeCodec 荧恍,而ExchangeCodec 為一個接口件舵,其實現(xiàn)類只有兩個唤反,分別為Http2ExchangeCodec和Http1ExchangeCodec。如果http2Connection不為空則直接創(chuàng)建并返回Http2ExchangeCodec對象茉唉,然后返回到transmitter.newExchange()方法中盒卸,最后返回到最初的ConnectInterceptor.intercept()方法中骗爆,從而得到Exchange 對象然后通過 realChain.proceed(request, transmitter, exchange)傳到下一個攔截器中。

其實該攔截器是OKHTTP中最核心的攔截器之一蔽介,以上的描述并沒有最深層次的展開代碼分析摘投,為了更清晰的表述ConnectInterceptor的完整邏輯煮寡,我總結(jié)了成了以下幾個步驟,可最快速度了解內(nèi)部原理與邏輯犀呼。

攔截器intercept()方法有兩步邏輯

  • 1.首先通過realChain.transmitter()獲取到Transmitter對象幸撕,該對象是應用層和網(wǎng)絡(luò)層通信的中轉(zhuǎn)站,每一個請求都對應一個transmitter外臂。
  • 2.通過transmitter.newExchange(.., ...)獲取Exchange對象并通過realChain.proceed(..., transmitter, exchange)傳遞到下一個攔截器中坐儿,核心在于獲取Exchange的過程

獲取Exchange分為以下幾步

  • 1.跟進transmitter.newExchange()后通過exchangeFinder.find(client, chain, doExtensiveHealthChecks)獲取ExchangeCodec宋光。
  • 2.通過new Exchange(...)且將ExchangeCodec等參數(shù)傳入獲取到Exchange對象貌矿。

問題轉(zhuǎn)換為ExchangeCodec對象的獲取過程

  • 注意:而ExchangeCodec 為一個接口,其實現(xiàn)類只有兩個罪佳,分別為Http2ExchangeCodec和Http1ExchangeCodec逛漫,而獲取ExchangeCodec需要通過exchangeFinder.find(...)獲取.
  • 1.exchangeFinder對象是在RetryAndFollowUpInterceptor攔截器邏輯中初始化的,具體通過exchangeFinder.repareToConnect()進行的初始化赘艳。
  • 2.exchangeFinder.find()中連帶調(diào)用了exchangeFinder.findHealthyConnection(...)獲取到了RealConnection對象(此時的邏輯是在找可用的健康連接)酌毡。
  • 3.通過RealConnection.newCodec(...)獲取到了ExchangeCodec對象

問題再次轉(zhuǎn)換為獲取RealConnection的過程

  • 1.exchangeFinder.findHealthyConnection(...)死循環(huán)通過exchangeFinder.findConnection()找可用的連接蕾管,同時不可用的做好標記枷踏,方便后續(xù)移除。
  • 2.第一次獲取連接過程首先會嘗試從transmitter.connection中獲取娇掏。
  • 3.第二次獲取連接通過address呕寝,host勋眯,port婴梧,代理等信息去連接池匹配,如果未匹配到則取下一個代理的路由信息(多個Route客蹋,即多個IP地址)塞蹭,再次嘗試從連接池獲取。(transmitterAcquirePooledConnection()方法是嘗試從線程池中獲取連接的方法)
  • 4.第三次通過創(chuàng)建RealConnection實例讶坯,進行TCP + TLS 握手番电,與服務(wù)端建立連接。
  • 5.創(chuàng)建RealConnection實例的過程中還需要從連接池繼續(xù)匹配辆琅,如果匹配到則釋放剛剛創(chuàng)建的連接漱办,如果沒匹配到則將RealConnection加入連接池。

至此逐步返回則得到了Exchange實體婉烟,同時需要注意一個問題娩井,怎么判斷RealConnection是不是監(jiān)控的連接?

  • 1.通過socket.isClosed()似袁,isInputShutdown()洞辣,isOutputShutdown()判斷socket是否可用咐刨。
  • 2.Http2的連接通過http2Connection.isShutdown()
  • 3.socket通過setSoTimeout()設(shè)置一秒延時,檢測Stream是否可用扬霜,若可用則該連接不可用定鸟。

  1. CallServerInterceptor(forWebSocket)//數(shù)據(jù)流攔截器

誒呀媽呀,終于到了最后一個攔截器了著瓶,這篇博客寫的太久了联予,終于要完結(jié)了。
CallServerInterceptor攔截器的主要用于做網(wǎng)絡(luò)連接蟹但,收發(fā)轉(zhuǎn)換數(shù)據(jù)使用的躯泰,前邊的一切準備都是為了最后這一步請求。我們直接看代碼华糖,還是和之前的攔截器相同麦向,直接看intercept()方法。

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

    long sentRequestMillis = System.currentTimeMillis();
    //一看這名字就知道是寫請求Header的
    exchange.writeRequestHeaders(request);

    boolean responseHeadersStarted = false;
    Response.Builder responseBuilder = null;
    //如果本次請求既不是get請求客叉,也不是Head诵竭,并且請求的body不為空(換句話說是post請求,且有請求body)
    if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
      // If there's a "Expect: 100-continue" header on the request, wait for a "HTTP/1.1 100
      // Continue" response before transmitting the request body. If we don't get that, return
      // what we did get (such as a 4xx response) without ever transmitting the request body.
      //如果header中添加了(Expect: 100-continue)兼搏,則需要等待服務(wù)端返回含有 "HTTP/1.1 100 Continue"的響應卵慰,然后再根據(jù)響應內(nèi)容確定是否發(fā)送body,如果服務(wù)器可以接收body會返回null佛呻。
      if ("100-continue".equalsIgnoreCase(request.header("Expect"))) {
        //詢問服務(wù)器是否愿意接受body
        exchange.flushRequest();
        responseHeadersStarted = true;
        exchange.responseHeadersStart();
        //此處為服務(wù)器返回結(jié)果
        responseBuilder = exchange.readResponseHeaders(true);
      }
      //responseBuilder 為null代表服務(wù)器接收RequestBody
      if (responseBuilder == null) {
         //除非子類重寫了isDuplex()方法,否則這個方法默認返回值為false
        //以下的邏輯是寫body的操作吓著。
        if (request.body().isDuplex()) {
          // Prepare a duplex body so that the application can send a request body later.
          exchange.flushRequest();
          BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, true));
           //寫入請求主體信息
          request.body().writeTo(bufferedRequestBody);
        } else {
          // Write the request body if the "Expect: 100-continue" expectation was met.
          BufferedSink bufferedRequestBody = Okio.buffer(exchange.createRequestBody(request, false));
           //寫入請求主體信息
          request.body().writeTo(bufferedRequestBody);
          bufferedRequestBody.close();
        }
      } else {
        //此處代表服務(wù)器不接收body鲤嫡,首先這個方法把資源釋放了。
        exchange.noRequestBody();
        //這個判斷是用于判斷RealConnection對象中的http2Connection是否為空
        if (!exchange.connection().isMultiplexed()) {

          // If the "Expect: 100-continue" expectation wasn't met, prevent the HTTP/1 connection
          // from being reused. Otherwise we're still obligated to transmit the request body to
          // leave the connection in a consistent state.
          //如果為空绑莺,證明是http1.0暖眼,且服務(wù)器不接收body,這時應該關(guān)閉連接纺裁,防止http1.0被重用诫肠,因為只有http2.0才能被復用。
          exchange.noNewExchangesOnConnection();
        }
      }
    } else {
      //沒有body欺缘,直接請求結(jié)束栋豫。
      exchange.noRequestBody();
    }

    if (request.body() == null || !request.body().isDuplex()) {
       //請求結(jié)束
      exchange.finishRequest();
    }

    if (!responseHeadersStarted) {
      //讀取響應頭開始回調(diào)
      exchange.responseHeadersStart();
    }

    if (responseBuilder == null) {
      //讀取響應頭
      responseBuilder = exchange.readResponseHeaders(false);
    }

    Response response = responseBuilder
        .request(request)
        .handshake(exchange.connection().handshake())
        .sentRequestAtMillis(sentRequestMillis)
        .receivedResponseAtMillis(System.currentTimeMillis())
        .build();

    int code = response.code();
    if (code == 100) {
       //如果服務(wù)器返回了100就再次嘗試獲取真正的響應。
      // server sent a 100-continue even though we did not request one.
      // try again to read the actual response
      response = exchange.readResponseHeaders(false)
          .request(request)
          .handshake(exchange.connection().handshake())
          .sentRequestAtMillis(sentRequestMillis)
          .receivedResponseAtMillis(System.currentTimeMillis())
          .build();

      code = response.code();
    }
    //回調(diào)讀取響應頭結(jié)束
    exchange.responseHeadersEnd(response);
    //如果狀態(tài)碼為101谚殊,獲取響應body
    if (forWebSocket && code == 101) {
      // Connection is upgrading, but we need to ensure interceptors see a non-null response body.
      response = response.newBuilder()
          .body(Util.EMPTY_RESPONSE)
          .build();
    } else {
      response = response.newBuilder()
          .body(exchange.openResponseBody(response))
          .build();
    }
    //如果請求的Header中key為“Connection”的值為"close"丧鸯,則請求完后需要關(guān)閉連接。
    if ("close".equalsIgnoreCase(response.request().header("Connection"))
        || "close".equalsIgnoreCase(response.header("Connection"))) {
      exchange.noNewExchangesOnConnection();
    }
    //如果狀態(tài)碼是204/205络凿,那返回數(shù)據(jù)應該是空骡送,如果返回長度大于0則報異常(ProtocolException)
    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攔截器的源碼就分析結(jié)束了昂羡,其實這個攔截器是整個OKHTTP中網(wǎng)絡(luò)io的核心,因此為了方便理解摔踱,我總結(jié)下這個攔截器內(nèi)都干了什么事虐先。

  • 1.首先通過責任鏈對象獲取RealInterceptorChain對象,并獲取到Exchange對象派敷,通過Exchange.writeRequestHeaders()將header寫入蛹批。
  • 2.如果Request中包含body,還需要判斷請求頭中是否包含"Expect: 100-continue"鍵值對篮愉,如果有則需要等待服務(wù)器返回“HTTP/1.1 100 Continue”的結(jié)果腐芍,以此結(jié)果決定是否發(fā)送body。(如果返回null則代表返回了100狀態(tài)碼试躏,需要發(fā)送body猪勇,如返回4**狀態(tài)碼,則代表不發(fā)送body)
  • 3.通過okio.buffer()配合exchange.createRequestBody(request, true)颠蕴,構(gòu)造出BufferedSink對象泣刹,然后通過 request.body().writeTo(BufferedSink)寫請求body。
  • 4.請求發(fā)送結(jié)束后犀被,通過exchange的responseHeadersStart()發(fā)送讀取響應頭回調(diào)椅您,并通過readResponseHeaders(false)開始讀取響應頭(注意,如果服務(wù)器返回100狀態(tài)碼寡键,需要再次讀取一次響應掀泳。),讀取完后需要調(diào)用exchange.responseHeadersEnd(response)回調(diào)告知讀取響應頭結(jié)束西轩。
  • 5.開始通過 response.newBuilder().body(exchange.openResponseBody(response)).build();讀取響應body员舵。(具體的讀取都在Http2ExchangeCodec類中進行)
  • 6.最后如果header中的鍵值對Connection是close,則需要在請求結(jié)束關(guān)閉連接遭商。

這么久固灵,終于寫完了捅伤,相信看到這的你也對OKHTTP的流程有了更深入的了解劫流,如果有錯誤希望各位指正。如果覺得有點收獲丛忆,可以幫忙點點贊祠汇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市熄诡,隨后出現(xiàn)的幾起案子可很,更是在濱河造成了極大的恐慌,老刑警劉巖凰浮,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件我抠,死亡現(xiàn)場離奇詭異苇本,居然都是意外死亡,警方通過查閱死者的電腦和手機菜拓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門瓣窄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人纳鼎,你說我怎么就攤上這事俺夕。” “怎么了贱鄙?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵劝贸,是天一觀的道長。 經(jīng)常有香客問我逗宁,道長映九,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任瞎颗,我火速辦了婚禮氯迂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘言缤。我一直安慰自己嚼蚀,他們只是感情好,可當我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布管挟。 她就那樣靜靜地躺著轿曙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪僻孝。 梳的紋絲不亂的頭發(fā)上导帝,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音穿铆,去河邊找鬼您单。 笑死,一個胖子當著我的面吹牛荞雏,可吹牛的內(nèi)容都是我干的虐秦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼凤优,長吁一口氣:“原來是場噩夢啊……” “哼悦陋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起筑辨,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤俺驶,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棍辕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暮现,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡还绘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了栖袋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚕甥。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栋荸,靈堂內(nèi)的尸體忽然破棺而出菇怀,到底是詐尸還是另有隱情,我是刑警寧澤晌块,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布爱沟,位于F島的核電站,受9級特大地震影響匆背,放射性物質(zhì)發(fā)生泄漏呼伸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一钝尸、第九天 我趴在偏房一處隱蔽的房頂上張望括享。 院中可真熱鬧,春花似錦珍促、人聲如沸铃辖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娇斩。三九已至,卻和暖如春穴翩,著一層夾襖步出監(jiān)牢的瞬間犬第,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工芒帕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留歉嗓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓背蟆,卻偏偏與公主長得像鉴分,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子淆储,可洞房花燭夜當晚...
    茶點故事閱讀 44,592評論 2 353

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