Android小知識-剖析OkHttp中的五個攔截器(上篇)

顧林海發表於2018-10-26

本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝

前面幾節介紹了OkHttp的同步和非同步請求的整體流程以及Dispatcher分發器的作用,接下來介紹一下OkHttp中的五個攔截器。

RetryAndFollowUpInterceptor攔截器

RetryAndFollowupInterceptor是重試重定向攔截器,它的主要作用是負責失敗重連。OkHttp中的重定向功能是預設開啟的。

該攔截器方法如下:

    @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request request = chain.request();
        RealInterceptorChain realChain = (RealInterceptorChain) chain;
        ...
        //標記1
        StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
                createAddress(request.url()), call, eventListener, callStackTrace);
        ...
        while (true) {
            //取消,釋放
            if (canceled) {
                streamAllocation.release();
                throw new IOException("Canceled");
            }

            Response response;
            boolean releaseConnection = true;
            ...
            //標記2
            response = realChain.proceed(request, streamAllocation, null, null);
            releaseConnection = false;
            ...
            Request followUp;
            //標記3
            followUp = followUpRequest(response, streamAllocation.route());
            ...
            //標記4
            if (++followUpCount > MAX_FOLLOW_UPS) {
                streamAllocation.release();
                throw new ProtocolException("Too many follow-up requests: " + followUpCount);
            }
            ...
        }
    }
複製程式碼

在intercept方法中省略了一些程式碼,我們只看核心程式碼:

在標記1處建立了StreamAllocation物件,建立的StreamAllocation物件在RetryAndFollowupInterceptor攔截器中並沒有使用到。

StreamAllocation物件會通過標記2處的proceed方法傳遞給下一個攔截器,直到ConnectInterceptor攔截器,StreamAllocation的主要作用是提供建立HTTP請求所需要的網路元件,ConnectInterceptor從RealInterceptorChain獲取前面的Interceptor傳過來的StreamAllocation物件,執行StreamAllocation物件的newStream()方法完成所有的連線建立工作,並將這個過程中建立的用於網路IO的RealConnection物件,以及與伺服器互動最為關鍵的HttpCodec等物件傳遞給後面的CallServerInterceptor攔截器。

在標記3處,會對返回的Response進行檢查,通過followUpRequest方法對Response返回的狀態碼判斷,並建立重定向需要發出的Request物件。

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) {
            case HTTP_PROXY_AUTH://407 代理伺服器認證
                ...
            case HTTP_UNAUTHORIZED://401 身份未認證
                ...
            case HTTP_PERM_REDIRECT://308
            case HTTP_TEMP_REDIRECT://307
                //當請求的method不為GET和HEAD時不進行重定向
                ...
            case HTTP_MULT_CHOICE://300 多種選擇
            case HTTP_MOVED_PERM://301 永久移除
            case HTTP_MOVED_TEMP://302 臨時重定向
            case HTTP_SEE_OTHER://303 其他問題
                ...
            case HTTP_CLIENT_TIMEOUT://408 請求超時
                ...
            case HTTP_UNAVAILABLE://503 服務不可用
                ...
            default:
                return null;
        }
    }
複製程式碼

followUpRequest方法主要是對返回的狀態碼進行判斷,根據特定的狀態碼建立重定向需要的Request物件。

回到上面攔截器intercept方法,在標記4判斷followUpCount是否大於MAX_FOLLOW_UPS(20),也就是說重定向次數上限為20,當重試次數超過20的時候,會釋放StreamAllocation這個物件,這樣做是為了防止無限制的重試網路請求,從而造成資源的浪費,關於重定向的次數建議可以按照Chrome遵循21個重定向;Firefox、CURL和WGET遵循20;Safari遵循16;HTTP/1推薦5。

總結RetryAndFollowupInterceptor攔截器:

  1. 建立StreamAllocation物件。

  2. 呼叫RealInterceptorChain.proceed()進行網路請求。

  3. 根據異常結果或響應結果判斷是否進行重新請求。

  4. 呼叫下一個攔截器,對Response進行處理,返回給上一個攔截器。

BridgeInterceptor攔截器

BridgeInterceptor是橋接和適配攔截器,它的作用是設定內容長度、編碼方式以及壓縮等等一系列操作,主要是新增頭部的作用。

該攔截器方法如下:

    @Override public Response intercept(Interceptor.Chain chain) throws IOException {
        Request userRequest = chain.request();
        Request.Builder requestBuilder = userRequest.newBuilder();

        //====================新增頭部資訊========================
        RequestBody body = userRequest.body();
        ...
        if (userRequest.header("Connection") == null) {
            //標記1
            requestBuilder.header("Connection", "Keep-Alive");
        }
        ...
        //標記2
        Response networkResponse = chain.proceed(requestBuilder.build());
        //標記3
        HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers());

        Response.Builder responseBuilder = networkResponse.newBuilder()
                .request(userRequest);
        //標記4
        if (transparentGzip
                && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
                && HttpHeaders.hasBody(networkResponse)) {
            //標記5
            GzipSource responseBody = new GzipSource(networkResponse.body().source());
           ...
        }

        return responseBuilder.build();
    }
複製程式碼

intercept方法主要是給Request新增一些頭部資訊,在標記1處的Connection設定為Keep-Alive,Keep-Alive的作用是當我們開啟一個HTTP連線後,在一定時間內保持它的連線狀態。

在標記2處呼叫了攔截器鏈的proceed方法向伺服器發起請求。

在標記3處通過HttpHeaders的receiveHeaders方法,通過網路請求,伺服器返回的Response轉化為客戶端可以識別的Response,如果HTTP預設支援gzip,那麼BridgeInterceptor攔截器將會對這個Response進行解壓,最終得到客戶端使用的Response。

在標記4處,對transparentGzip標誌位進行判斷,如果transparentGzip為true就表面Accept-Encoding支援gzip壓縮;再判斷頭部的Content-Encoding是否為gzip,保證服務端返回的響應體內容是經過了gzip壓縮的;最後判斷HTTP頭部是否有Body。當滿足這些條件後在標記5處將Response的body轉換為GzipSource型別,這樣的話client端直接以解壓的方式讀取資料。

總結BridgeInterceptor攔截器:

  1. 負責將使用者構建的一個Request請求轉化為能夠進行網路訪問的請求,通過給頭部新增資訊。

  2. 將這個符合網路請求的Request進行網路請求。

  3. 將網路請求回來的響應Response轉化為使用者可用的Response。


838794-506ddad529df4cd4.webp.jpg

搜尋微信“顧林海”公眾號,定期推送優質文章。

相關文章