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

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

來源:https://juejin.im/post/5bd26291518825287847a316#comment

相關文章