本文為本人原創(chuàng),轉(zhuǎn)載請注明作者和出處诱咏。
在上一章我們分析了Okhttp分發(fā)器對同步/異步請求的處理禽车,本章將和大家一起分析Okhttp的最核心模塊--攔截鏈的代碼。在這里你將會了解到Okhttp究竟如何處理請求的朝群。
系列文章索引:
OkHttp源碼學(xué)習(xí)系列一:總流程和Dispatcher分析
OkHttp源碼學(xué)習(xí)系列二:攔截鏈分析
OkHttp源碼學(xué)習(xí)系列三:緩存總結(jié)
零、攔截鏈總流程
上一章我們分析到了RealCall的getResponseWithInterceptorChain方法中符,現(xiàn)在我們繼續(xù),先來看下這個(gè)方法:
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 (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(
interceptors, null, null, null, 0, originalRequest);
return chain.proceed(originalRequest);
}
可以看到這個(gè)方法前面一大堆都很簡單誉帅,new了一個(gè)存放攔截器的數(shù)組淀散。這個(gè)Interceptor是接口,后面添加的各個(gè)元素都是它的實(shí)現(xiàn)類蚜锨。這里先添加了okhttpClient對象里的攔截器档插,這里是用戶自定義的攔截器,全都會在這里添加進(jìn)去亚再。然后一次添加retryAndFollowUpInterceptor郭膛,BridgeInterceptor,CacheInterceptor氛悬,ConnectInterceptor则剃,CallServerInterceptor五個(gè)攔截器,其中在倒數(shù)第二的位置上如捅,如果上websocket連接棍现,還會額外添加networkInterceptor攔截器組。這里的添加順序要注意镜遣,后面我們會看到己肮,攔截器的攔截順序也是和添加順序是一致的。
添加完成后悲关,執(zhí)行了RealInterceptorChain也就是攔截器鏈的proceed方法谎僻,傳入原始request。其實(shí)攔截器鏈我們看名字大致能猜到寓辱,它會將各個(gè)攔截器串起來艘绍,像鏈條一樣依次去攔截或者說加工request,最終得到response后讶舰,依次再向上返回鞍盗,各個(gè)攔截器再依次處理response需了,最終返回給用戶。現(xiàn)在我們來看下proceed方法是如何串起各個(gè)攔截器的:
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(
interceptors, streamAllocation, httpCodec, connection, index + 1, request);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
源代碼有點(diǎn)長般甲,這里我們略去各種異常判斷肋乍,只看核心的這段代碼》蟠妫看源碼的時(shí)候一定要記得抓住主脈絡(luò)墓造,因?yàn)槲覀儾豢赡芤淮闻靼谆蛴涀∷械脑创a細(xì)節(jié)。抓住重點(diǎn)锚烦,才不會只見樹木觅闽,不見森林。
在這里它又重復(fù)創(chuàng)建了一個(gè)RealInterceptorChain對象涮俄,只是對index做了+1處理蛉拙。這個(gè)index就是記錄interceptors攔截器數(shù)組的角標(biāo)的。之后會從數(shù)組中取出當(dāng)前的攔截器彻亲,調(diào)用intercept方法并傳入這個(gè)新new出來的RealInterceptorChain孕锄,得到response并返回。
看到這里可能有點(diǎn)懵苞尝,直覺告訴我們應(yīng)該只要一個(gè)RealInterceptorChain對象去管理所有攔截器畸肆,然而這里似乎沒攔截一次都會產(chǎn)生一個(gè)新的RealInterceptorChain,只不過把攔截器數(shù)組的角標(biāo)index往后移了一位宙址。不要著急轴脐,我們看了這里關(guān)鍵的攔截方法intercept就可以知道是怎么一回事了。這是個(gè)抽象方法抡砂,它的所有實(shí)現(xiàn)大咱,都會調(diào)用chain.proceed(request)這句代碼,包括我們自己自定義的攔截器舀患,在intercept方法中我們也必須調(diào)用這句方法才能生效徽级。在后面的貼出來的代碼中大家也都會看到這句代碼。這個(gè)方法仿佛之前見過聊浅?是的沒錯(cuò)餐抢,在getResponseWithInterceptorChain中我們第一次new攔截器鏈對象就調(diào)用了該方法。這里又調(diào)用了該方法低匙,只是這里的攔截器對象是我們新new出來的旷痕,index+1的攔截器鏈。于是乎在這次調(diào)用中顽冶,又會new出一個(gè)新的攔截器鏈對象欺抗,并會根據(jù)index取出下一個(gè)攔截器,執(zhí)行該攔截器的intercept方法强重。如此遞歸調(diào)用绞呈,直至攔截器數(shù)組中沒有下一個(gè)攔截器贸人,得到response并向上傳遞。
可以看到這里的攔截鏈?zhǔn)褂玫姆浅G擅畹枭悬c(diǎn)像棧的數(shù)據(jù)結(jié)構(gòu)艺智。依次將各個(gè)攔截器的方法入棧,最后得到response圾亏,再依次彈棧十拣。如果是我來寫的話,可能就直接一個(gè)for循環(huán)依次調(diào)用每個(gè)攔截器的攔截方法志鹃。但是這樣的話我還得再來一遍反循環(huán)夭问,再來依次處理加工response。很明顯這里棧的結(jié)構(gòu)更符合我們的業(yè)務(wù)場景曹铃。
一缰趋、RetryAndFollowUpInterceptor
下面我們來看第一個(gè)攔截器RetryAndFollowUpInterceptor,看名字可以猜到這個(gè)攔截器是負(fù)責(zé)失敗重連和重定向的攔截器陕见。
直接來看它的intercept方法:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
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(), false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, 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) {
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()), callStackTrace);
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
這里的代碼非常長埠胖,但是又不太好精簡我就都貼上來了。不要害怕淳玩,還是比較容易理順的。首先它new了一個(gè)StreamAllocation對象非竿,這個(gè)對象封裝了RealConnection蜕着、RouteSelector、HttpCodec等http連接要用到的重要對象红柱,有興趣的同學(xué)可以看看這部分源碼承匣,這里不展開講它,只要知道這是個(gè)http連接必須要用到的重要資源對象锤悄。
之后定義了followUpCount也就是重連次數(shù)韧骗,以及一個(gè)空的response。緊接著就是一個(gè)while(true)死循環(huán)零聚。這個(gè)死循環(huán)不用看也能想到袍暴,肯定是不斷的進(jìn)行重連/重定向的。核心代碼是這句response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null)隶症,是的政模,所謂的重連就是重復(fù)調(diào)用下一個(gè)攔截器繼續(xù)攔截。這里和第一次在RealCall中的調(diào)用不一樣的地方是傳入了streamAllocation給下一個(gè)攔截器鏈蚂会,之前傳的是null淋样。
那么這個(gè)死循環(huán)如何結(jié)束呢?這里除了獲得response后的且沒有重定向得return胁住,好像沒找到其他退出循環(huán)的地方趁猴。是不是沒有response之前會一直重復(fù)請求直到有結(jié)果呢刊咳?這明顯不肯能,這里是通過拋異常的方式來結(jié)束重連儡司。我們來看拋異常的地方有哪些:
- 當(dāng)前請求被cancel的時(shí)候
- 重連次數(shù)超過上限(默認(rèn)是20娱挨,用戶可修改)
- 重定向的請求體屬于不能重復(fù)的請求體
- 沒有正確釋放streamAllocation內(nèi)的codec
除了最后一條,其他時(shí)候都會調(diào)用streamAllocation.release()來釋放資源枫慷。另外让蕾,這里還有一處要點(diǎn)是調(diào)用了followUpRequest方法進(jìn)行重定向。這個(gè)方法非常長或听,這里簡單講下是干嘛的:先獲得傳入的響應(yīng)體的code探孝,根據(jù)不同的code去構(gòu)建不同的request進(jìn)行重定向。如果不需要重定向誉裆,直接返回null顿颅,如果是301、302這樣的重定向響應(yīng)碼足丢,使用重定向的url重新構(gòu)建request粱腻,在上面的死循環(huán)中重新進(jìn)行請求。
到這里RetryAndFollowUpInterceptor就講的差不多了斩跌,它主要負(fù)責(zé)的任務(wù)就是失敗重連绍些,和重定向重新構(gòu)建request重連,是不是還挺簡單的耀鸦?
二柬批、BridgeInterceptor
BridgeInterceptor是五個(gè)攔截器中最簡單的一個(gè)了,它做的事情很簡單袖订,正如其名字氮帐,它是一個(gè)連接網(wǎng)絡(luò)數(shù)據(jù)流和我們程序能用的request、response對象的橋梁洛姑。
@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 && 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());
}
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();
}
可以看到它將我們傳入的request新增了好幾個(gè)請求頭茬暇,這就是為什么我們平時(shí)使用的時(shí)候即使完全不加請求頭揩瞪,抓出來的包依然有好多請求頭坯苹。這里要注意一下“Keep-Alive”犀农,這個(gè)請求頭是告訴服務(wù)器,在請求完成后不要斷開連接产徊。我們知道http是短鏈接昂勒,為什么okhttp還要默認(rèn)加這么個(gè)請求頭呢?這是為了復(fù)用連接舟铜,當(dāng)再次發(fā)起相同請求的時(shí)候戈盈,可以節(jié)省再次開啟連接的消耗。
當(dāng)?shù)玫絩esponse的時(shí)候,如果http連接使用了gzip壓縮塘娶,會對響應(yīng)流進(jìn)行g(shù)zip解壓縮归斤,因此我們自己在使用的時(shí)候完全不需要再做這些操作。
三刁岸、CacheInterceptor
CacheInterceptor顧名思義脏里,是負(fù)責(zé)緩存相關(guān)處理的一個(gè)攔截器。由于在下一章我會專門講okhttp的緩存機(jī)制虹曙,這里有些細(xì)節(jié)我會先跳過迫横。它的intercept代碼非常長,而且每一段都要分析酝碳,所以我分開來一段一段地分析矾踱。
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;
首先從cache對象根據(jù)當(dāng)前的request取出緩存的response命名為cacheCandidate,也就是候選的緩存疏哗,不一定會使用呛讲。之后使用當(dāng)前時(shí)間、request和候選緩存構(gòu)建出了一個(gè)CacheStrategy對象返奉,這個(gè)對象是負(fù)責(zé)緩存策略實(shí)施的贝搁。它會根據(jù)不同的請求碼、響應(yīng)碼返回不同的networkRequest和cacheResponse芽偏。這個(gè)類會放到下一章展開討論雷逆,目前我們只要知道,它會根據(jù)request和cacheCandidate返回networkRequest(實(shí)際要進(jìn)行網(wǎng)絡(luò)請求的request)以及cacheResponse(緩存的response)污尉。如果networkRequest為null(比如requset中含有only-if-cached請求頭的時(shí)候)关面,表示不進(jìn)行網(wǎng)絡(luò)請求。當(dāng)cacheResponse為null的時(shí)候十厢,說明沒有緩存,或者當(dāng)前緩存過期或者當(dāng)前緩存策略不允許使用緩存等捂齐,總之就代表無緩存可用蛮放。
if (cache != null) {
cache.trackResponse(strategy);
}
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
Cache的trackResponse是起計(jì)數(shù)作用的,不是重點(diǎn)奠宜。下面一個(gè)判斷如果cacheCandidate不為null代表我們?nèi)〉搅司彺姘洌琧acheResponse為null代表這個(gè)緩存過期或者由于緩存策略等原因用不了,那么這個(gè)cacheCandidate沒有用了压真,我門需要關(guān)閉它的流娩嚼。
// 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(Util.EMPTY_RESPONSE)
.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();
}
前面說過了networkRequest為null代表我們將不進(jìn)行網(wǎng)絡(luò)請求,這時(shí)候分為兩種情況滴肿。如果cacheResponse也為null說明我們沒有有效的緩存response岳悟,而我們又不會進(jìn)行網(wǎng)絡(luò)請求,因此給上層構(gòu)建了一個(gè)響應(yīng)碼味504的response。如果cacheResponse不為null贵少,說明我們有可用緩存呵俏,而此次請求又不會再請求網(wǎng)絡(luò),因此直接將緩存response返回滔灶。
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 (networkResponse.code() == HTTP_NOT_MODIFIED) {
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();
// 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());
}
}
上面networkRequest為null也就是不進(jìn)行網(wǎng)絡(luò)請求的情況我們已經(jīng)全部處理了普碎,下面必須要進(jìn)行網(wǎng)絡(luò)請求了,調(diào)用攔截鏈的proceed方法讓后面的攔截器去請求網(wǎng)絡(luò)數(shù)據(jù)得到response录平。如果cacheResponse不為null麻车,那么此時(shí)我們有兩個(gè)response:一個(gè)是緩存的,一個(gè)是網(wǎng)絡(luò)請求返回的动猬,此時(shí)我們需要處理到底使用哪個(gè)涝影。此時(shí)如果服務(wù)器返回的響應(yīng)碼為HTTP_NOT_MODIFIED,也就是我們常見的304燃逻,代表服務(wù)器的資源沒有變化序目,客戶端去取本地緩存即可伯襟,此時(shí)服務(wù)器不會返回響應(yīng)體。那么這個(gè)時(shí)候我們會使用緩存的cacheResponse構(gòu)建一個(gè)新的response并返回姆怪。同時(shí)我們會記錄這次我們的cache命中了叛赚,因?yàn)閛khttp默認(rèn)使用了LruCache算法,Least Recently Used最近最少使用原則俺附,需要我們在使用的時(shí)候計(jì)數(shù)溪掀。同時(shí)把新構(gòu)建的response刷新到緩存中去揪胃。當(dāng)然了,如果沒有返回304喊递,說明緩存已經(jīng)過期骚勘,我們需要將它的流關(guān)閉。
Response response = networkResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
if (cache != null) {
if (HttpHeaders.hasBody(response) && CacheStrategy.isCacheable(response, networkRequest)) {
// Offer this request to the cache.
CacheRequest cacheRequest = cache.put(response);
return cacheWritingResponse(cacheRequest, response);
}
if (HttpMethod.invalidatesCache(networkRequest.method())) {
try {
cache.remove(networkRequest);
} catch (IOException ignored) {
// The cache cannot be written.
}
}
}
return response;
最后剩下來的情況就是緩存過期盛杰,我們需要使用網(wǎng)絡(luò)返回的response這種情況。直接使用networkResponse構(gòu)建response并返回定拟。此時(shí)我們還需要做一件事青自,就是更新我們的緩存驱证,將最終response寫入到cache對象中去。但此時(shí)如果我們的請求方式不支持緩存(常見的兩種請求方式逆瑞,get請求支持緩存伙单,post不支持)吻育,我們不但不能更新緩存,還要將緩存刪掉摊趾。
至此游两,緩存攔截器也講的差不多了,在下一章我將會進(jìn)一步講解okhttp的緩存策略實(shí)現(xiàn)梢为。
四轰坊、ConnectInterceptor
經(jīng)歷過前面三個(gè)攔截器的處理肴沫,我們發(fā)現(xiàn)目前為止我們還沒有進(jìn)行真正的請求蕴忆。別急,ConnectInterceptor就是一個(gè)負(fù)責(zé)建立http連接的攔截器站蝠。它的intercept代碼不多,趕緊來看下:
@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");
HttpCodec httpCodec = streamAllocation.newStream(client, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
非常簡短是不是,它只做了兩件事澜倦,一是生成HttpCodec對象藻治,該對象是給http請求/響應(yīng)編碼、解碼的验靡。另外還調(diào)用了streamAllocation.connection方法取出RealConnection連接對象雏节。但是取出connection對象后矾屯,我找了半天沒找到在哪建立連接的,最后發(fā)現(xiàn)其實(shí)是在streamAllocation.newStream()方法中建立的孙技,來看看這個(gè)方法:
public HttpCodec newStream(OkHttpClient client, boolean doExtensiveHealthChecks) {
int connectTimeout = client.connectTimeoutMillis();
int readTimeout = client.readTimeoutMillis();
int writeTimeout = client.writeTimeoutMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, connectionRetryEnabled, doExtensiveHealthChecks);
HttpCodec resultCodec = resultConnection.newCodec(client, this);
synchronized (connectionPool) {
codec = resultCodec;
return resultCodec;
}
} catch (IOException e) {
throw new RouteException(e);
}
}
這里從okhttpclient中取出了一些連接參數(shù)牵啦,調(diào)用了findHealthyConnection方法獲得連接妄痪,并且通過這個(gè)RealConnection對象創(chuàng)建了此次請求的HttpCodec對象衫生。點(diǎn)進(jìn)來看看findHealthyConnection方法,該方法是個(gè)無限循環(huán),直到findConnection方法找到健康的連接彭羹,直接看findConnection方法:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
boolean connectionRetryEnabled) throws IOException {
Route selectedRoute;
synchronized (connectionPool) {
if (released) throw new IllegalStateException("released");
if (codec != null) throw new IllegalStateException("codec != null");
if (canceled) throw new IOException("Canceled");
// Attempt to use an already-allocated connection.
RealConnection allocatedConnection = this.connection;
if (allocatedConnection != null && !allocatedConnection.noNewStreams) {
return allocatedConnection;
}
// Attempt to get a connection from the pool.
Internal.instance.get(connectionPool, address, this, null);
if (connection != null) {
return connection;
}
selectedRoute = route;
}
// If we need a route, make one. This is a blocking operation.
if (selectedRoute == null) {
selectedRoute = routeSelector.next();
}
RealConnection result;
synchronized (connectionPool) {
if (canceled) throw new IOException("Canceled");
// Now that we have an IP address, make another attempt at getting a connection from the pool.
// This could match due to connection coalescing.
Internal.instance.get(connectionPool, address, this, selectedRoute);
if (connection != null) {
route = selectedRoute;
return connection;
}
// Create a connection and assign it to this allocation immediately. This makes it possible
// for an asynchronous cancel() to interrupt the handshake we're about to do.
route = selectedRoute;
refusedStreamCount = 0;
result = new RealConnection(connectionPool, selectedRoute);
acquire(result);
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, connectionRetryEnabled);
routeDatabase().connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
// Pool the connection.
Internal.instance.put(connectionPool, result);
// If another multiplexed connection to the same address was created concurrently, then
// release this connection and acquire that one.
if (result.isMultiplexed()) {
socket = Internal.instance.deduplicate(connectionPool, address, this);
result = connection;
}
}
closeQuietly(socket);
return result;
}
又是一個(gè)比較長的方法还最。前半段是在嘗試尋找connection毡惜,最開始尋找是否有可以復(fù)用的已經(jīng)建立好連接并且空閑的connection经伙。沒有的話繼續(xù)從連接池里取,如果連接池里沒有辜梳,就需要new一個(gè)connection并加入連接池泳叠。有了connection對象后危纫,終于可以調(diào)用其connect方法真正進(jìn)行連接啦!看注釋可以知道契耿,這里的connect方法進(jìn)行了TCP + TLS握手操作螃征,是個(gè)阻塞操作盯滚。同時(shí)這里還會判斷下連接是否multiplexed,也就是和當(dāng)前某個(gè)已有的連接重復(fù)了内列,如果重復(fù)的話會釋放該連接背率。已經(jīng)到這步了寝姿,當(dāng)然要看看connect方法是怎樣建立連接的:
public void connect(
int connectTimeout, int readTimeout, int writeTimeout, boolean connectionRetryEnabled) {
if (protocol != null) throw new IllegalStateException("already connected");
RouteException routeException = null;
List<ConnectionSpec> connectionSpecs = route.address().connectionSpecs();
ConnectionSpecSelector connectionSpecSelector = new ConnectionSpecSelector(connectionSpecs);
if (route.address().sslSocketFactory() == null) {
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not enabled for client"));
}
String host = route.address().url().host();
if (!Platform.get().isCleartextTrafficPermitted(host)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication to " + host + " not permitted by network security policy"));
}
}
while (true) {
try {
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout);
} else {
connectSocket(connectTimeout, readTimeout);
}
establishProtocol(connectionSpecSelector);
break;
} catch (IOException e) {
closeQuietly(socket);
closeQuietly(rawSocket);
socket = null;
rawSocket = null;
source = null;
sink = null;
handshake = null;
protocol = null;
http2Connection = null;
if (routeException == null) {
routeException = new RouteException(e);
} else {
routeException.addConnectException(e);
}
if (!connectionRetryEnabled || !connectionSpecSelector.connectionFailed(e)) {
throw routeException;
}
}
}
if (http2Connection != null) {
synchronized (connectionPool) {
allocationLimit = http2Connection.maxConcurrentStreams();
}
}
}
這里的代碼比較接近底層了饵筑,本人看起來也是十分吃力翻翩。首先它會判斷Protocol對象是否為null,Protocol是封裝的通信協(xié)議對象胶征,如果已經(jīng)存在桨仿,則表明已連接服傍。然后會根據(jù)路由地址獲取connectionSpecs集合,ConnectionSpec點(diǎn)進(jìn)去看了下是更一步的有TLS協(xié)議的連接罩抗。之后判斷如果TLS協(xié)議不合格灿椅,會拋RouteException不進(jìn)行連接茫蛹。這也從一方面解釋了okhttp為什么強(qiáng)調(diào)它是一個(gè)很安全的http框架。
緊接著是一個(gè)無限循環(huán)進(jìn)行連接骨坑,調(diào)用establishProtocol建立真正的Client-Server通信柬采,這個(gè)方法會拋出IO異常警没。如果順利的話跳出循環(huán),有異常的話會根據(jù)情況是否拋異常放棄連接還是繼續(xù)嘗試下一次連接亡脸。最后還會對http流的數(shù)量做一個(gè)限制浅碾。
繼續(xù)往下看的話establishProtocol方法會調(diào)用connectTls方法進(jìn)行三次握手操作续语,與https有關(guān)的ssl認(rèn)證過程就在其中,限于篇幅就不仔細(xì)講了滥朱。最終會調(diào)用Http2Connection對象的start方法開啟連接徙邻。這個(gè)過程的細(xì)節(jié)非常多,由于我們重點(diǎn)是講攔截器的缰犁,這里就此打住淳地。以后有機(jī)會的再開一篇文章專門講這里的代碼帅容。
CallServerInterceptor
這是okhttp網(wǎng)絡(luò)請求的最后一個(gè)攔截器并徘,ConnectInterceptor是負(fù)責(zé)建立http連接的,而它負(fù)責(zé)真正向服務(wù)器發(fā)起請求耍贾。直接看它的intercept代碼:
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();
httpCodec.writeRequestHeaders(request);
首先獲取HttpCodec和RealConnection連接對象荐开,通過HttpCodec編碼最終的請求頭简肴。
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();
responseBuilder = httpCodec.readResponseHeaders(true);
}
if (responseBuilder == null) {
// Write the request body if the "Expect: 100-continue" expectation was met.
Sink requestBodyOut = httpCodec.createRequestBody(request, request.body().contentLength());
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
} 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();
}
}
這段代碼則是編碼請求體的砰识,首先判斷下請求方法是否支持請求體(比如get請求就沒有請求體),然后通過HttpCodec編碼最終的請求體辫狼。
httpCodec.finishRequest();
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (forWebSocket && code == 101) {
// Connection is upgrading, but we need to ensure interceptors see a non-null response body.
response = response.newBuilder()
.body(Util.EMPTY_RESPONSE)
.build();
} else {
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
最終的requst完成后见秤,通過HttpCodec讀取響應(yīng)頭和響應(yīng)體并進(jìn)行解碼真椿,構(gòu)建最終的response。其中如果響應(yīng)碼為101(表明要升級協(xié)議)测摔,則返回一個(gè)空的response锋八。204或205(表明響應(yīng)體沒有內(nèi)容)但響應(yīng)體長度缺不為0則拋異常。如果Connection響應(yīng)頭為close路媚,表明請求完畢樊销,需要關(guān)閉流围苫。
最終將response逐層向上返回撤师,經(jīng)過所有攔截器的處理后返回給用戶剃盾。
終于寫完了,限于我的知識水平有限衰伯,有錯(cuò)誤的地方請大家及時(shí)糾正积蔚,感謝您的閱讀尽爆!