前言
做React Native的時(shí)候遇到業(yè)務(wù)線反饋的一個(gè)Bug:在使用Charles做代理的時(shí)候淘菩,將reactTimeout
值改小的時(shí)候渠牲,有時(shí)候會(huì)發(fā)現(xiàn)在Charles沒有捕獲到Http請求的時(shí)候搂擦,仍然返回?cái)?shù)據(jù)了口锭。這是一個(gè)比較詭異的問題胀溺,出現(xiàn)問題的原因可能有以下兩點(diǎn):
- Http請求通過緩存直接返回;
- Http請求并未通過設(shè)置的代理請求幢痘。
針對(duì)這個(gè)問題唬格,用了STFW沒有找到什么明確的答案,既然如此颜说,那就直接從源碼著手分析购岗,通過一些筆記的整理,就有了這篇文章门粪。由于精力能力有限藕畔,對(duì)于OkHttp3的分析不會(huì)在細(xì)節(jié)處深入,如果有錯(cuò)誤指出煩請拍磚庄拇,共同進(jìn)步。
(另源碼分析基于OkHttp3-3.4.1版本)
概述
本文會(huì)先簡單說下OkHttp3的工作流程,然后介紹OkHttp3的一些核心類(如連接池StreamAllocation
以及各式各樣的Interceptor
)措近,接著從源碼角度分析一次HTTP請求在OkHttp3中所經(jīng)歷的過程溶弟,在不同的Interceptor
(攔截器)可以看到一些OkHttp3設(shè)計(jì)的一些巧妙思想,最后對(duì)上述分析做個(gè)簡單的總結(jié)瞭郑。
Okhttp3是Square公司開源的強(qiáng)大高效的Java網(wǎng)絡(luò)請求庫辜御,具有以下特性:
- 支持Http2/SPDY;
- 默認(rèn)啟用長連接屈张,使用連接池管理擒权,支持Cache(目前僅支持GET請求的緩存);
- 路由節(jié)點(diǎn)管理阁谆,提升訪問速度碳抄;
- 透明的Gzip處理,節(jié)省網(wǎng)絡(luò)流量场绿。
- 靈活的攔截器剖效,行為類似Java EE的Filter或者函數(shù)編程中的Map。
旅程開始
OkHttp3支持同步和異步兩種請求方式焰盗,異步請求會(huì)經(jīng)過Dispatcher
在線程池中執(zhí)行璧尸。同步請求沒有線程池這一個(gè)過程,由于同步請求很簡單熬拒,這里僅分析異步請求方式爷光。OkHttp3一次完整的請求過程是從構(gòu)造一個(gè)Request對(duì)象開始,接著調(diào)用OkHttpClient.newCall()
返回一個(gè)RealCall對(duì)象并調(diào)用RealCall.enqueue()
方法澎粟,最后會(huì)進(jìn)入Dispacher.enqueue()
方法中蛀序,這里會(huì)將RealCall對(duì)象放入線程池中調(diào)度執(zhí)行。
OkHttp3的核心類
這部分會(huì)簡單介紹一些OkHttp3的核心類捌议,這些核心類共同支持了OkHttp3的一些基礎(chǔ)功能哼拔。粗略的分成了Interceptor(攔截器)、Router(路由)和Stream(流)三部分瓣颅,OkHttpClient類是初始化時(shí)候就配置的倦逐,比較簡單就不說了。
Interceptor(攔截器)
OkHttp3的Interceptor是Request -> Response請求過程中的一個(gè)"節(jié)點(diǎn)"單位宫补,通過一連串有序的Interceptor攔截器"節(jié)點(diǎn)"組成一條加工鏈檬姥,加工鏈中的任意一個(gè)"節(jié)點(diǎn)"都可以去攔截加工Request和Response。OkHttp默認(rèn)提供了一套完善的Interceptor集合粉怕,當(dāng)然也支持自定義一個(gè)Interceptor來實(shí)現(xiàn)一個(gè)上傳/下載的進(jìn)度更新器或者黑白名單攔截等等個(gè)性化的功能健民。
閱讀OkHttp3的源碼建議從Interceptor開始。
OkHttp默認(rèn)提供了如下Interceptor:
RetryAndFollowUpInterceptor:默認(rèn)情況下位于OkHttp3加工鏈的首位贫贝,顧名思義秉犹,具有失敗-重試機(jī)制蛉谜,支持頁面重定向和一些
407
之類的代理驗(yàn)證等,此外負(fù)責(zé)StreamAllocation對(duì)象的創(chuàng)建(稍后介紹)崇堵。BridgeInterceptor:橋攔截器型诚,配置Request的Headers頭信息:讀取Cookie,默認(rèn)啟用
Gzip
鸳劳,默認(rèn)加入Keep-Alive
長連接狰贯,如果不想讓OkHttp3擅自使用長連接,只需在Request的Header中預(yù)設(shè)Connection
字段即可赏廓。CacheInterceptor: 管理OkHttp3的緩存涵紊,目前僅支持
GET
類型的緩存,使用文件形式的Lru緩存管理策略幔摸,CacheStrategy類負(fù)責(zé)了緩存相關(guān)的策略管理摸柄。ConnectInterceptor:OkHttp3打開一個(gè)Socket連接的地方,OkHttp3相關(guān)的Router路由切換策略也可以從這里開始跟蹤抚太。
CallServerInterceptor:處于OkHttp3加工鏈的末尾塘幅,通過HttpStream往Socket中寫入Request報(bào)文信息,并回讀Response報(bào)文信息尿贫。
OkHttp3的攔截器執(zhí)行順序依次是:自定義Interceptors(暫且稱作
A
) ->RetryAndFollowUpInterceptor
->BridgeInterceptor
->CacheInterceptor
->ConnectInterceptor
-> 自定義NetInterceptors(暫且稱作B
) ->CallServerInterceptor
B
僅在非WebSocket
情況下被調(diào)用电媳。A
與B
的區(qū)別是,A
能攔截所有類型的請求庆亡,包括緩存命中的請求匾乓;而B
僅攔截非WebSocket
的情況下產(chǎn)生真正網(wǎng)絡(luò)訪問的請求。因此在B
上做網(wǎng)絡(luò)上傳和下載進(jìn)度的監(jiān)聽器是比較合適的又谋。
Stream(流相關(guān)類)
OkHttp3并沒有直接操作Socket拼缝,而是通過okio庫進(jìn)行了封裝,okio庫的設(shè)計(jì)也是非常贊的彰亥,它的Sink
對(duì)應(yīng)輸入流咧七,Source
對(duì)應(yīng)輸出流,okio已經(jīng)實(shí)現(xiàn)了與之對(duì)應(yīng)的緩沖相關(guān)的包裝類任斋,采用了Segment
切片和循環(huán)鏈表結(jié)構(gòu)實(shí)現(xiàn)緩沖處理继阻,有興趣還是可以看看okio的源碼。
StreamAllocation:OkHttp3管理物理連接的對(duì)象废酷,負(fù)責(zé)連接流的創(chuàng)建關(guān)閉等管理工作瘟檩,通過池化物理連接來減少Hand-shake握手過程以提升請求效率,另外StreamAllocation通過持有RouteSelector對(duì)象切換路由澈蟆。關(guān)于路由切換墨辛,這里有個(gè)場景,在Android系統(tǒng)中趴俘,如果你配置了代理睹簇,當(dāng)代理服務(wù)器訪問超時(shí)的時(shí)候奏赘,OkHttp3在進(jìn)行請求重試時(shí)候會(huì)切換到下個(gè)代理或者采用無代理直連形式請求。因此并非設(shè)置了代理带膀,OkHttp3就會(huì)"老實(shí)"的跟著你的規(guī)則走志珍。這也是本文一開始提到的問題產(chǎn)生的原因。
HttpStream: 這是一個(gè)抽象類垛叨,其子類實(shí)現(xiàn)了各類網(wǎng)絡(luò)協(xié)議流格式。 HttpStream在OkHttp3中有兩個(gè)實(shí)現(xiàn)類Http1xStream和Http2xStream柜某,Http1xStream實(shí)現(xiàn)了
HTTP/1.1
協(xié)議流嗽元,Http2xStream則實(shí)現(xiàn)了HTTP/2
和SPDY
協(xié)議流。ConnectionPool:OkHttp連接池喂击,由OkHttpClient持有該對(duì)象剂癌,ConnectionPool持有一個(gè)0核心線程數(shù)的線程池(與
Executors.newCachedThreadPool()
提供的線程池行為完全一樣)用于清理一些超時(shí)的RealConnection連接對(duì)象,持有一個(gè)Deque對(duì)象緩存OkHttp3的RealConnection連接對(duì)象翰绊。
Router(路由)
OkHttp3要求每個(gè)連接都需要指明一個(gè)Router路由對(duì)象佩谷,當(dāng)然這個(gè)Router路由對(duì)象可以是直連類型的,意即你不使用任何的代理服務(wù)器监嗜。當(dāng)嘗試使用某個(gè)路由請求失敗的時(shí)候谐檀,OkHttp3會(huì)在允許請求重試的情況下通過RouterSelector切換到下個(gè)路由繼續(xù)請求,并將失敗的路由記錄到黑名單中裁奇,這樣在OkHttp3重復(fù)請求一個(gè)目標(biāo)地址的時(shí)候能夠優(yōu)先選擇成功的路由進(jìn)行網(wǎng)絡(luò)請求桐猬。
- Router:包含代理與Socket地址信息。
- RouteDatabase:記錄請求失敗的Router路由對(duì)象的"黑名單"刽肠。
- RouteSelector:負(fù)責(zé)指派Router路由溃肪。持有RouteDatabase對(duì)象。
源碼角度解析OkHttp3請求
分析查看源碼一般抓主干音五,本文也不例外惫撰,我們接下來就直接從代碼來看OkHttp3是如何進(jìn)行一次完整的網(wǎng)絡(luò)請求。限于篇幅躺涝,僅僅分析異步情況下的網(wǎng)絡(luò)請求厨钻,同步方式的網(wǎng)絡(luò)請求更加簡單,核心部分都是一樣的诞挨。另外為了方便閱讀莉撇,部分無關(guān)語句會(huì)被省略。
//所在類 okhttp3.RealCall
@Override public void enqueue(Callback responseCallback) {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
client.dispatcher().enqueue(new AsyncCall(responseCallback));
}
在調(diào)用client.newCall(request).enqueue(...)
方法開始請求之后惶傻,就會(huì)進(jìn)入上面的enqueue()
方法棍郎,可以看到最后是調(diào)用了Dispatcher.enqueue()
,繼續(xù)跟蹤源碼到如下地方:
//所在類 okhttp3.Dispatcher
synchronized void enqueue(AsyncCall call) {
if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
runningAsyncCalls.add(call);
executorService().execute(call);
} else {
readyAsyncCalls.add(call);
}
}
可以看到OkHttp3使用了一個(gè)線程池來執(zhí)行這個(gè)AsyncCall银室,AsyncCall本質(zhì)上是一個(gè)Runnable對(duì)象涂佃,最后會(huì)調(diào)用AsyncCall.execute()
方法励翼。
//所在類 okhttp3.RealCall.AsyncCall
@Override protected void execute() {
boolean signalledCallback = false;
try {
Response response = getResponseWithInterceptorChain();
if (retryAndFollowUpInterceptor.isCanceled()) {
signalledCallback = true;
responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
} else {
signalledCallback = true;
responseCallback.onResponse(RealCall.this, response);
}
} catch (IOException e) {
...
} finally {
client.dispatcher().finished(this);
}
}
通過getResponseWithInterceptorChain()
獲取Response報(bào)文,繼續(xù)跟蹤下去辜荠。
//所在類 okhttp3.RealCall
private Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!retryAndFollowUpInterceptor.isForWebSocket()) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(
retryAndFollowUpInterceptor.isForWebSocket()));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
OkHttp3將Interceptor(攔截器)放到一個(gè)集合里汽抚,通過自增遞歸
的方式調(diào)用RealInterceptorChain.proceed()
方法依次執(zhí)行集合里的每一個(gè)Interceptor(攔截器),這個(gè)Chain的傳遞過程剛開始看有點(diǎn)繞伯病,這里簡單畫個(gè)流程圖方便理解造烁。
RealInterceptor關(guān)鍵源碼如下:
// 所在類 okhttp3.internal.http.RealInterceptorChain
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpStream httpStream, Connection connection, int index, Request request) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpStream = httpStream;
this.index = index;
this.request = request;
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpStream httpStream,
Connection connection) throws IOException {
// 省略部分源碼
...
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpStream, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
// Confirm that the next interceptor made its required call to chain.proceed().
if (httpStream != null && index + 1 < interceptors.size() && next.calls != 1) {
throw new IllegalStateException("network interceptor " + interceptor
+ " must call proceed() exactly once");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
return response;
}
RealInterceptorChain的構(gòu)造函數(shù)中的index
和interceptors
是匹配的,用來索引接下來需要執(zhí)行的Intercetpor攔截器午笛。
通過調(diào)用interceptor.intercept(next)
方法惭蟋,在各個(gè)Intercetpor攔截器里又能通過next
參數(shù)繼續(xù)調(diào)用proceed()
方法,完成遞歸操作药磺。
接下來我們按照執(zhí)行順序依次看下這些interceptors
具體都干了什么告组,從RetryAndFollowUpInterceptor開始。
// 所在類 okhttp3.internal.http.RetryAndFollowUpInterceptor
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()));
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), true, request)) throw e.getLastConnectException();
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, false, request)) throw e;
releaseConnection = false;
continue;
} finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
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()));
} else if (streamAllocation.stream() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
代碼有點(diǎn)長癌佩,不過主要還是做了這幾個(gè)操作木缝,首先StreamAllocation對(duì)象在這里被創(chuàng)建,接著調(diào)用proceed()
方法執(zhí)行了一次請求围辙,并拿到一個(gè)Response報(bào)文我碟,在followUpRequest()
方法中對(duì)Response報(bào)文進(jìn)行了各種判斷(驗(yàn)證了407
,判斷需不需要重定向等)確定是否需要再次請求酌畜,如果需要持續(xù)請求會(huì)在followUpRequest()
返回一個(gè)新的Request對(duì)象并重新請求怎囚。followUpRequest()
的代碼有點(diǎn)長,可以自行查閱源碼桥胞,這里就不貼了恳守。繼續(xù)看執(zhí)行的下一個(gè)攔截器BridgeInterceptor。
// 所在類 okhttp3.internal.http.BridgeInterceptor
@Override public Response intercept(Chain chain) throws IOException {
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");
}
// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing
// the transfer stream.
boolean transparentGzip = false;
if (userRequest.header("Accept-Encoding") == 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());
}
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);
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
}
return responseBuilder.build();
}
可以看出來BridgeInterceptor對(duì)Request和Response報(bào)文加工的具體步驟贩虾,默認(rèn)對(duì)Request報(bào)文增加了gzip
頭信息催烘,并在Response報(bào)文中對(duì)gzip
進(jìn)行解壓縮處理。另外CookieJar類也是在這里處理的缎罢。接下來就是CacheInterceptor攔截器伊群。
// 所在類 okhttp3.internal.cache.CacheInterceptor
@Override public Response intercept(Chain chain) throws IOException {
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()); // The cache candidate wasn't applicable. Close it.
}
// If we're forbidden from using the network and the cache is insufficient, fail.
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(EMPTY_BODY)
.sentRequestAtMillis(-1L)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
}
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest);
} finally {
// If we're crashing on I/O or otherwise, don't leak the cache body.
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body());
}
}
// If we have a cache response too, then we're doing a conditional get.
if (cacheResponse != null) {
if (validate(cacheResponse, networkResponse)) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
// Update the cache after combining headers but before stripping the
// Content-Encoding header (as performed by initContentStream()).
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (HttpHeaders.hasBody(response)) {
CacheRequest cacheRequest = maybeCache(response, networkResponse.request(), cache);
response = cacheWritingResponse(cacheRequest, response);
}
return response;
}
CacheInterceptor.intercept()方法也挺長的,首先從cache
緩存中獲取一個(gè)匹配的Response報(bào)文并賦給cacheCandidate
變量策精,cache
是一個(gè)InternalCache對(duì)象舰始,里面持有DiskLruCache這個(gè)對(duì)象,以文件流的形式存儲(chǔ)Response報(bào)文咽袜,采用LRU
原則管理這些緩存丸卷;接著使用CacheStrategy.Factory工廠類生成一個(gè)緩存策略類CacheStrategy,通過該類拿到兩個(gè)關(guān)鍵變量networkRequest
和cacheResponse
询刹,這里針對(duì)cacheCandidate
谜嫉、networkRequest
和cacheResponse
這三個(gè)變量的賦值情況依次進(jìn)行了以下處理:
-
cacheCandidate
不為空萎坷,cacheResponse
為空,說明緩存過期沐兰,將cacheCandidate
從cache
中清除哆档; -
networkRequest
和cacheResponse
同時(shí)為空,說明Request要求只使用緩存住闯,而緩存并不存在或者已經(jīng)失效瓜浸,直接返回504
的錯(cuò)誤報(bào)文,請求結(jié)束比原; -
networkRequest
為空斟叼,說明cacheResponse
不為空,命中緩存春寿,直接返回cacheResponse
報(bào)文; - 未命中緩存忽孽,開啟網(wǎng)絡(luò)請求绑改,繼續(xù)執(zhí)行下一個(gè)Interceptor攔截器。
分析完Request兄一,對(duì)請求回來的Response報(bào)文處理就很簡單了厘线,就是針對(duì)Response報(bào)文情況決定是否使用緩存特性。
OkHttp3的Cache相關(guān)策略可參考RFC 2616, 14.9
當(dāng)緩存未命中時(shí)候OkHttp3就開始執(zhí)行真正的網(wǎng)絡(luò)請求出革,CacheInterceptor的下一個(gè)就是ConnectInterceptor攔截器造壮。
// 所在類 okhttp3.internal.connection.ConnectInterceptor
@Override public Response intercept(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");
HttpStream httpStream = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpStream, connection);
}
ConnectInterceptor做的事情很簡單,先獲取了在RetryAndFollowUpInterceptor中創(chuàng)建的StreamAllocation對(duì)象骂束,接著執(zhí)行streamAllocation.newStream()
打開一個(gè)物理連接并返回一個(gè)HttpStream的對(duì)象耳璧,HttpStream在前文提到了是網(wǎng)絡(luò)協(xié)議流(HTTP/1.1
、HTTP/2
和SPDY
)的具體實(shí)現(xiàn)展箱。這時(shí)候調(diào)用realChain.proceed()
方法的時(shí)候旨枯,四個(gè)參數(shù)均不為空,這是集齊了所有的"龍珠"召喚最終的"神龍"CallServerInterceptor攔截器了混驰。
在ConnectInterceptor的下一個(gè)攔截器并非絕對(duì)是CallServerInterceptor攀隔,如果有自定義NetInterceptors則會(huì)被優(yōu)先執(zhí)行,不過絕大部分情況下CallServerInterceptor在最后也是會(huì)被調(diào)用的栖榨。
// 所在類 okhttp3.internal.http.CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
HttpStream httpStream = ((RealInterceptorChain) chain).httpStream();
StreamAllocation streamAllocation = ((RealInterceptorChain) chain).streamAllocation();
Request request = chain.request();
long sentRequestMillis = System.currentTimeMillis();
httpStream.writeRequestHeaders(request);
if (HttpMethod.permitsRequestBody(request.method()) && request.body() != null) {
Sink requestBodyOut = httpStream.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
httpStream.finishRequest();
Response response = httpStream.readResponseHeaders()
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
if (!forWebSocket || response.code() != 101) {
response = response.newBuilder()
.body(httpStream.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
int code = response.code();
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
CallServerInterceptor攔截器里先調(diào)用httpStream
協(xié)議流對(duì)象寫入Request的Header部分炬转,接著寫入Body部分纳令,這樣就完成了Request的請求,從httpStream
里回讀Response報(bào)文,并根據(jù)情況讀取Response的Body部分贼涩,當(dāng)Response響應(yīng)報(bào)文的頭信息中Connection
字段為close
時(shí),將streamAllocation
設(shè)置成noNewStreams
狀態(tài),標(biāo)識(shí)其當(dāng)前Connection
對(duì)象不再被復(fù)用,將在流請求結(jié)束之后被回收掉本冲。
總結(jié)
通過三四天對(duì)OkHttp3源碼的閱讀,佩服框架設(shè)計(jì)的巧妙劫扒,不光在于類的封裝上檬洞,里面對(duì)設(shè)計(jì)模式的實(shí)踐也挺好的。其中Prototype
原型模式和Builder
建造者模式被廣泛使用沟饥,關(guān)于Interceptor的概念使得全新設(shè)計(jì)一個(gè)私有請求協(xié)議不無可能添怔,而okio對(duì)于流的封裝也是很巧妙的。推薦好好學(xué)習(xí)下贤旷。