1. 程式人生 > >okhttp的攔截器

okhttp的攔截器

okhttp 其實就做了 3 個操作,分別是 請求(call) ,TCP 連線(Connection) ,資料流(okio),這 3 個操作都是通過 okhttp 的攔截器來完成的

okhttp 的攔截器到底是有些,我們可以在 opkhttp3/RealCall 裡面的一個方法看到:

Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    //新增開發者設定的攔截器,也就自定義的攔截器
    interceptors.addAll(client.interceptors());
    //新增失敗重試的攔截器
    interceptors.add(retryAndFollowUpInterceptor);
   /**
    * BridgeInterceptor 網路橋接攔截器
    * 這裡處理http的報文, Content-Length 的計算和新增、gzip的支援
    * 等都是在這裡面處理的
    */
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    //新增快取攔截器
    interceptors.add(new CacheInterceptor(client.internalCache()));
    //新增連線攔截器,在這裡建立TCP或SSL連線
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
        //新增webSock長連線的攔截器
        interceptors.addAll(client.networkInterceptors());
    }
    /**
     * CallServerInterceptor 請求伺服器響應攔截器,負責網路連線
     * 它負責實質的網路請求與響應的 I/O 操作,即往 Socket 裡寫入請求資料
     * 和從 Socket 裡讀取響應資料
     */
    interceptors.add(new CallServerInterceptor(forWebSocket));
    //RealInterceptorChain配置全部的攔截器以及攔截器的排程順序
    Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener
, client.connectTimeoutMillis(),client.readTimeoutMillis()
, client.writeTimeoutMillis());
    //啟動攔截器去進行網路請求與響應
    return chain.proceed(originalRequest);
}

這個方法就是把所有配置好的 Interceptor 放在一個 List 裡,然後作為引數,建立一個 RealInterceptorChain 物件,它負責全部的攔截器的排程,它是通過呼叫 chain.proceed(request) 方法來切換到另外一個攔截器,這種叫做責任鏈模式
那麼,一個完整的 http 請求過程,這些攔截器的呼叫如下圖:
在這裡插入圖片描述
首先,先從發起一個 http 請求開始,從上到下,每級 Interceptor 做的事:

  • client.interceptors():這裡是開發者設定的自定義 Interceptor
    • 在發起 http 請求報文時,在其他 Interceptor
      處理之前,進行最早的預處理工作,比方說可以在這裡新增統一的 header;
    • 在接收 http 響應報文時,這裡則是最後的善後操作了,比方將伺服器返回的響應報文的內容全部打印出來
  • RetryAndFollowUpInterceptor:該攔截器負責在請求失敗時的重試,以及重定向的自動後續請求,也就是連線到新的 IP 地址;
  • BridgeInterceptor:該攔截器負責把使用者構造的 http 請求轉換為 http 請求報文以及得到伺服器返回的資料包轉為 http 響應報文,例如 Content-Length 的計算和新增,gzip 的支援和 gzip 資料包的解壓都是在這裡處理的
  • CacheInterceptor :該攔截器負責 Cache 的處理,通過 Expires,ETag,Last-Modified 等欄位判斷本地是否有了可用的 Cache,如果有那麼就直接忽然接下來的網路互動,直接返回 Cache 中的資料,如果沒有,那麼繼續執行網路請求並在得到 http 響應報文時更新本地的 Cache 資料
  • ConnectInterceptor:該攔截器負責建立起 TCP 連線 或 SSL 連線,這裡的程式碼其實非常少:
public final class ConnectInterceptor implements Interceptor {
  public final OkHttpClient client;
  public ConnectInterceptor(OkHttpClient client) {
    this.client = client;
  }
  @Override public Response intercept(@NonNull Chain chain) throws IOException {
    RealInterceptorChain realChain = (RealInterceptorChain) chain;
    Request request = realChain.request();
    StreamAllocation streamAllocation = realChain.streamAllocation();
    // We need the network to satisfy this request. Possibly for validating a conditional GET.
    boolean doExtensiveHealthChecks = !request.method().equals("GET");
   //httpCodec http的編碼解碼器
    HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
    //RealConnection,和伺服器搭建起的連線物件
    RealConnection connection = streamAllocation.connection();
    return realChain.proceed(request, streamAllocation, httpCodec, connection);
  }
}       

上面程式碼的核心就是 HttpCodec 物件,它是一個 http 協議的介面,子類分別為 Http1CodecHttp2Codec ,它們的程式碼都很長,目前只要知道它們都是通過 okio 來拼接出 http 請求報文發給伺服器以及解析伺服器返回的 http 響應報文,也就是對 Socket 的讀寫操作進行封裝(關於 okio 我會在晚些的分享中進行分析)

  • CallServerInterceptor:該攔截器負責實質的請求與響應的 I/O 操作,也就是最後一個呼叫或最開始呼叫的攔截器了,簡單看下它的程式碼:
Override
public Response intercept(@NonNull 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();

    long sentRequestMillis = System.currentTimeMillis();

    realChain.eventListener().requestHeadersStart(realChain.call());
    //向伺服器傳送request請求
    httpCodec.writeRequestHeaders(request);
    realChain.eventListener().requestHeadersEnd(realChain.call(), request);

    Response.Builder responseBuilder = null;
    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.
        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();
            //傳送request body
            realChain.eventListener()
                    .requestBodyEnd(realChain.call(), requestBodyOut.successfulCount);
        } else if (!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.
            streamAllocation.noNewStreams();
        }
    }
   //結束髮送請求報文
    httpCodec.finishRequest();

    if (responseBuilder == null) {
    realChain.eventListener().responseHeadersStart(realChain.call());
    responseBuilder = httpCodec.readResponseHeaders(false);
    }

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

大致還是可以看出上面到底幹了什麼,那就是向伺服器傳送請求報文和解析伺服器返回的響應報文,同時也可以看到核心工作都是由 HttpCodec 完成,而 HttpCodec 實際上是 okio 的封裝利用,也就是說 okhttp 其實就是對 okio 的進一步封裝,讓其具備網路請求的能力。
內容參考:https://blog.piasy.com/2016/07/11/Understand-OkHttp/