轉載請以鏈接形式標明出處:
本文出自:103style的博客
base on 3.12.0
目錄
- 前言
- 重試及重定向攔截器
RetryAndFollowUpInterceptor
- 橋接攔截器
BridgeInterceptor
- 緩存攔截器
CacheInterceptor
- 連接攔截器
ConnectInterceptor
- 讀寫攔截器
CallServerInterceptor
- 小結
前言
前面我們對 okhttp的RealCall.execute()流程做了介紹,在文末有提到五個自帶的攔截器,本文就來介紹五個攔截器主要的工作。
還沒看過 RealCall.execute()流程 的小夥伴可以先去看看。
這裏先簡單介紹下五個攔截器的作用:
RetryAndFollowUpInterceptor
:負責請求的重試和重定向BridgeInterceptor
:給請求添加對應的header
信息,處理響應結果的header
信息CacheInterceptor
:根據當前獲取的狀態選擇 網絡請求 、讀取緩存、更新緩存。ConnectInterceptor
:建立http
連接。CallServerInterceptor
:讀寫網絡數據。
話不多說,我們接下來直接看這寫攔截器的 intercept(chain)
主要做了什麼事情。
重試及重定向攔截器 RetryAndFollowUpInterceptor
首先介紹下RetryAndFollowUpInterceptor
主要做了哪些邏輯:
-
首先獲取以及初始化相關的實例.
-
獲取下層攔截器返回的結果,出現異常 則根據是否可以恢復來判斷中斷 還是 重新開始循環.
-
根據返回的信息 判斷是否需要重定向?
當重定向次數大於MAX_FOLLOW_UPS = 20
時則拋出異常. -
然後判斷重定向返回的信息是否出現異常。
- 出現則拋出異常並釋放資源.
- 不出現則用重定向返回的信息構建
request
重新傳給下層攔截器.
下面我們來結合具體代碼看看.
-
獲取以及初始化相關的實例:
我們可以看到這裏主要是獲取RealInterceptorChain
、RealCall
對象,然後構建了一個StreamAllocation
,然後進入死循環執行下面的邏輯。
StreamAllocation
主要是 用來建立執行HTTP請求所需網絡設施的組件,後面我們會詳細介紹。Request request = chain.request(); RealInterceptorChain realChain = (RealInterceptorChain) chain; Call call = realChain.call(); EventListener eventListener = realChain.eventListener(); StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()), call, eventListener, callStackTrace); this.streamAllocation =streamAllocation; //然後開始進入死循環 while (true) {...}
-
獲取並處理下層攔截器返回的結果
這裏主要是獲取下層攔截器返回的結果,然後判斷是否可以重試。Response response; boolean releaseConnection = true; try { response = realChain.proceed(request, streamAllocation, null, null); releaseConnection = false; } catch (RouteException e) { if (!recover(e.getLastConnectException(), streamAllocation, false, request)) { throw e.getFirstConnectException(); } releaseConnection = false; continue; } catch (IOException e) { boolean requestSendStarted = !(e instanceof ConnectionShutdownException); if (!recover(e, streamAllocation, requestSendStarted, request)) throw e; releaseConnection = false; continue; } finally { if (releaseConnection) { //不能重試則釋放資源 streamAllocation.streamFailed(null); streamAllocation.release(); } }
判斷是否可以重試的邏輯:
private boolean recover(IOException e, StreamAllocation streamAllocation, boolean requestSendStarted, Request userRequest) { streamAllocation.streamFailed(e); //client.retryOnConnectionFailure(false) 配置了不允許重試 if (!client.retryOnConnectionFailure()) return false; //無法發送請求內容 if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false; //異常是致命的 if (!isRecoverable(e, requestSendStarted)) return false; //沒有可重試的路徑 if (!streamAllocation.hasMoreRoutes()) return false; //上面都不滿足 則可以重試 return true; }
-
根據返回的信息判斷是否需要重定向
Request followUp; try { followUp = followUpRequest(response, streamAllocation.route()); } catch (IOException e) { streamAllocation.release(); throw e; } if (followUp == null) { streamAllocation.release(); return response; }
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 需要代理驗證 return client.proxyAuthenticator().authenticate(route, userResponse); case HTTP_UNAUTHORIZED://401 沒有認證 return client.authenticator().authenticate(route, userResponse); case HTTP_PERM_REDIRECT://308 case HTTP_TEMP_REDIRECT://307 if (!method.equals("GET") && !method.equals("HEAD")) { return null; } case HTTP_MULT_CHOICE://300 case HTTP_MOVED_PERM://301 case HTTP_MOVED_TEMP://302 case HTTP_SEE_OTHER://303 //client不允許重定向 if (!client.followRedirects()) return null; boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme()); if (!sameScheme && !client.followSslRedirects()) return null; //根據相應的狀態修改request的header信息 //... return requestBuilder.url(url).build(); case HTTP_CLIENT_TIMEOUT://408 ... case HTTP_UNAVAILABLE://503 ... default: return null; } }
-
然後判斷重定向返回的信息是否出現異常。
//關閉之前響應數據的流信息 closeQuietly(response.body()); //超過重定向次數 if (++followUpCount > MAX_FOLLOW_UPS) { streamAllocation.release(); throw new ProtocolException("Too many follow-up requests: " + followUpCount); } //不能重用的請求體 if (followUp.body() instanceof UnrepeatableRequestBody) { streamAllocation.release(); throw new HttpRetryException("Cannot retry streamed HTTP body", response.code()); } //跨主機導致連接地址不一樣 if (!sameConnection(response, followUp.url())) { streamAllocation.release(); streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(followUp.url()), call, eventListener, callStackTrace); this.streamAllocation = streamAllocation; } else if (streamAllocation.codec() != null) { throw new IllegalStateException("Closing the body of " + response + " didn't close its backing stream. Bad interceptor?"); } //將重定向的請求賦值給request request = followUp; priorResponse = response;
橋接攔截器 BridgeInterceptor
BridgeInterceptor
的主要做了以下工作:
- 給
http請求
添加對應的header
信息. - 如果下層攔截器返回的數據的
Content-Encoding
是gzip
,則通過GzipSource
獲取返回的數據。
廢話不多說,看代碼:
- 給http請求添加對應的header信息.
RequestBody
不爲空則根據內容添加Content-Type
、Content-Length
、Transfer-Encoding
.- 給沒有
Host
、Connection
、User-Agent
頭信息的request
補上. - 根據對應條件給
request
補上Accept-Encoding
、Cookie
.
Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); 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"); } boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { requestBuilder.header("Cookie", cookieHeader(cookies)); } if (userRequest.header("User-Agent") == null) { requestBuilder.header("User-Agent", Version.userAgent()); }
- 處理gzip格式數據、
- 主要就是通過
GzipSource
包裝gzip
格式的數據,具體可以看GzipSource.read(...)
。
Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); 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();
- 主要就是通過
緩存攔截器 CacheInterceptor
CacheInterceptor
的作用主要是:
- 根據當前時間獲取當前request的緩存。
- 根據緩存中緩存的request和response做對應處理。
- 有緩存時,則根據條件判斷是否緩存到本地。
接下來看代碼實現:
-
根據當前時間獲取當前request的緩存
Response cacheCandidate = cache != null ? cache.get(chain.request()) : null; long now = System.currentTimeMillis(); CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); Request networkRequest = strategy.networkRequest; Response cacheResponse = strategy.cacheResponse; if (cache != null) { cache.trackResponse(strategy); } if (cacheCandidate != null && cacheResponse == null) { closeQuietly(cacheCandidate.body()); }
-
根據緩存中緩存的request和 response做對應處理
- 網絡不可用,又沒有緩存則返回
504
錯誤. - 網絡不可用,緩存可用,則直接返回緩存。
- 網絡可用,緩存不可用,則通過下層攔截器獲取網絡數據。
//網絡不可用,又沒有緩存則返回 `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(); } //網絡不可用,緩存可用,則直接返回緩存 if (networkRequest == null) { return cacheResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .build(); } Response networkResponse = null; try { //請求網絡數據 networkResponse = chain.proceed(networkRequest); } finally { if (networkResponse == null && cacheCandidate != null) { closeQuietly(cacheCandidate.body()); } }
- 網絡不可用,又沒有緩存則返回
-
緩存不爲空時,根據條件是否緩存到本地
- 如果請求返回碼是
HTTP_NOT_MODIFIED: 304
,則更新緩存。 - 是
HEAD
類型的請求,則更新緩存。 - 是
POST
、PATCH
、PUT
、DELETE
、MOVE
請求則刪除緩存中的request
。
if (cacheResponse != null) { if (networkResponse.code() == HTTP_NOT_MODIFIED) { //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(); cache.trackConditionalCacheHit(); cache.update(cacheResponse, response); return response; } else { closeQuietly(cacheResponse.body()); } } Response response = networkResponse.newBuilder() .cacheResponse(stripBody(cacheResponse)) .networkResponse(stripBody(networkResponse)) .build(); if (cache != null) { if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) { // HEAD 請求 CacheRequest cacheRequest = cache.put(response); return cacheWritingResponse(cacheRequest, response); } if (HttpMethod.invalidatesCache(networkRequest.method())) { //POST、PATCH、PUT、DELETE、MOVE try { cache.remove(networkRequest); } catch (IOException ignored) { } } } return response;
- 如果請求返回碼是
連接攔截器 ConnectInterceptor
ConnectInterceptor
主要就是通過StreamAllocation
、HttpCodec
和 RealConnection
合理建立 http
連接。
這三個類後面會單獨講解,主要就是通過 在連接池中尋找可以的連接,沒有則創建,並通過okio
來操作數據流,然後由CallServerInterceptor
繼續處理。
public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
讀寫攔截器 CallServerInterceptor
CallServerInterceptor
即爲獲取請求的響應數據,並回傳給上一層攔截器。
- 處理帶有
RequestBody
並符合條件的request
。 - 然後通過
Response.Builder
構建響應數據,並根據相應數據的返回碼做響應處理。
開始看代碼
-
處理帶有RequestBody並符合條件的request
處理帶有RequestBody
的非GET
和HEAD
請求。- 當
header
的Expect
爲100-continue
時,則爲Response.Builder
添加頭信息 - 如果服務器允許發送請求
body
發送,則通過okio
寫入請求數據.
Response.Builder responseBuilder = null; if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) { //等待HTTP/1.1 100響應 if ("100-continue".equalsIgnoreCase(request.header("Expect"))) { httpCodec.flushRequest(); realChain.eventListener().responseHeadersStart(realChain.call()); responseBuilder = httpCodec.readResponseHeaders(true); } if (responseBuilder == null) { //如果響應滿足 HTTP/1.1 100 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()) { //不滿足HTTP/1.1 100 則防止連接被重用 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(); int code = response.code(); if (code == 100) { //服務器返回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(); } //Connection 爲 close 則禁止創建新的流 if ("close".equalsIgnoreCase(response.request().header("Connection")) || "close".equalsIgnoreCase(response.header("Connection"))) { streamAllocation.noNewStreams(); } //204 205 的請求返回Content-Length 必須爲 0 if ((code == 204 || code == 205) && response.body().contentLength() > 0) { throw new ProtocolException( "HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength()); } return response;
小結
通過上面的介紹我們基本瞭解了五個攔截器對應的作用,這裏再回顧下:
RetryAndFollowUpInterceptor
:負責請求的重試和重定向BridgeInterceptor
:給請求添加對應的header
信息,處理響應結果的header
信息CacheInterceptor
:根據當前獲取的狀態選擇 網絡請求 、讀取緩存、更新緩存。ConnectInterceptor
:建立http
連接。CallServerInterceptor
:讀寫網絡數據。
每個攔截器的都有自己負責的功能。
以上