okhttp的攔截器
阿新 • • 發佈:2019-01-05
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
- 在接收 http 響應報文時,這裡則是最後的善後操作了,比方將伺服器返回的響應報文的內容全部打印出來
- 在發起 http 請求報文時,在其他 Interceptor
- 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 協議的介面,子類分別為 Http1Codec 和 Http2Codec ,它們的程式碼都很長,目前只要知道它們都是通過 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/