聲明:原創(chuàng)作品,轉(zhuǎn)載請(qǐng)注明出處http://www.lxweimin.com/p/8c32e928613c
這篇文章接著上面一篇文章來(lái)詳細(xì)分析各個(gè)攔截器的實(shí)現(xiàn)機(jī)制,主要的攔截器有這幾個(gè):
-
RetryAndFollowUpInterceptor
、 -
BridgeInterceptor
、 -
CacheInterceptor
、 -
ConnectInterceptor
、 -
CallServerInterceptor
。
接下來(lái)挨個(gè)看下:
1.RetryAndFollowUpInterceptor
這個(gè)攔截器是用來(lái)處理異常請(qǐng)求重試和重定向的,所謂重定向,說(shuō)的簡(jiǎn)單點(diǎn)就是請(qǐng)求某一個(gè)資源,被告知資源被更改,讓你換個(gè)路徑重新請(qǐng)求。接下來(lái)就來(lái)看下源碼是怎么實(shí)現(xiàn)的:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Transmitter transmitter = realChain.transmitter();
int followUpCount = 0;
Response priorResponse = null;
while (true) {
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
Response response;
boolean success = false;
try {
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
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, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
// 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();
}
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
}
}
源碼有點(diǎn)長(zhǎng),我們來(lái)挨個(gè)看下,首先拿到請(qǐng)求體request、這個(gè)鏈條chain以及transmitter,這個(gè)transmitter其實(shí)是應(yīng)用層和網(wǎng)絡(luò)層的一個(gè)橋梁。接下來(lái)會(huì)進(jìn)入到一個(gè)while循環(huán)中,在循環(huán)一開(kāi)始會(huì)調(diào)用transmitter的prepareToConnect方法進(jìn)行網(wǎng)絡(luò)層的初始化,如下代碼,然后判斷下該請(qǐng)求是否已被取消,如果是則直接拋出異常。
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
接下來(lái)就是調(diào)用chain的proceed方法將request傳遞給下一個(gè)攔截器進(jìn)行網(wǎng)絡(luò)請(qǐng)求,如下:
Response response;
boolean success = false;
try {
response = realChain.proceed(request, transmitter, null);
success = true;
} catch (RouteException e) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.getLastConnectException(), transmitter, false, request)) {
throw e.getFirstConnectException();
}
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, transmitter, requestSendStarted, request)) throw e;
continue;
} finally {
// The network call threw an exception. Release any resources.
if (!success) {
transmitter.exchangeDoneDueToException();
}
}
這個(gè)proceed方法在一個(gè)try/catch中執(zhí)行,當(dāng)出現(xiàn)對(duì)應(yīng)的異常時(shí),會(huì)調(diào)用recover方法來(lái)判斷這個(gè)請(qǐng)求是否是可恢復(fù)的,如果可恢復(fù)則會(huì)重新執(zhí)行while循環(huán)進(jìn)行請(qǐng)求重試。如果不可恢復(fù)則直接拋出對(duì)應(yīng)的異常。
我們來(lái)看下recover的判斷邏輯:
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// 應(yīng)用層禁止重試
if (!client.retryOnConnectionFailure()) return false;
// 無(wú)法再次發(fā)送請(qǐng)求體
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 發(fā)生嚴(yán)重的錯(cuò)誤異常
if (!isRecoverable(e, requestSendStarted)) return false;
// 沒(méi)有額外的路由可嘗試
if (!transmitter.canRetry()) return false;
return true;
}
首先會(huì)判斷下我們的client是否配置了當(dāng)連接失敗可以重試,如果沒(méi)有則返回false,即不可恢復(fù)。如果我們配置了可以重試,那么接下來(lái)會(huì)判斷我們的請(qǐng)求是否已經(jīng)發(fā)送出去,并且請(qǐng)求只能被發(fā)送一次,如果滿足條件則表示不可恢復(fù)。如果不滿足條件,則會(huì)調(diào)用isRecoverable方法進(jìn)行接下來(lái)的判斷。這個(gè)方法會(huì)判斷拋出的異常是什么異常,如果是協(xié)議異常或者其他的一些特殊的異常則不可恢復(fù)。否則就調(diào)用transmitter的canRetry()方法進(jìn)行判斷,這個(gè)方法內(nèi)部會(huì)判斷是否有更多的路由可重試,如果沒(méi)有則返回false不可重試,如果上面的條件都不滿足則返回true,表示可重試。
接下來(lái)我們跳出recover方法,繼續(xù)看接下來(lái)的代碼。
如果上面的執(zhí)行沒(méi)有拋出異常,則會(huì)繼續(xù)往下接著執(zhí)行:
// 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();
}
接下來(lái)會(huì)判斷下priorResponse是否為空,這個(gè)priorResponse是保存著上次返回的response,如果不為空則會(huì)創(chuàng)建一個(gè)新的response,這個(gè)新的response將老的response和當(dāng)前的response組合起來(lái)。這里我們是第一次執(zhí)行,所以priorResponse為空,里面也就不會(huì)執(zhí)行。接下來(lái)再看下面的代碼:
Exchange exchange = Internal.instance.exchange(response);
Route route = exchange != null ? exchange.connection().route() : null;
Request followUp = followUpRequest(response, route);
if (followUp == null) {
if (exchange != null && exchange.isDuplex()) {
transmitter.timeoutEarlyExit();
}
return response;
}
RequestBody followUpBody = followUp.body();
if (followUpBody != null && followUpBody.isOneShot()) {
return response;
}
closeQuietly(response.body());
if (transmitter.hasExchange()) {
exchange.detachWithViolence();
}
if (++followUpCount > MAX_FOLLOW_UPS) {
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
request = followUp;
priorResponse = response;
首先會(huì)通過(guò)response得到exchange和route,exchange可以理解成實(shí)際處理io的類。然后調(diào)用followUpRequest方法并傳入exchange和route參數(shù)獲取重定向后的request,當(dāng)然如果不是重定向的話就會(huì)返回空。接下來(lái),如果上面返回的followUp重定向request為空的話,則表示我們的請(qǐng)求是正常的,就直接返回。這樣到這邏輯就執(zhí)行結(jié)束了。如果不為空就會(huì)接著往下執(zhí)行,如果followUp的body不為空并且只能被發(fā)送一次,那么就直接返回這個(gè)response,執(zhí)行結(jié)束。當(dāng)然這個(gè)isOneShot方法默認(rèn)是false的,所以不會(huì)直接返回。接下來(lái)繼續(xù)執(zhí)行,會(huì)關(guān)閉一些資源,然后把上面的followUp重定向的request作為新的request,然后把重定向返回的response賦值給priorResponse,接著會(huì)重復(fù)while循環(huán)進(jìn)行再次的網(wǎng)絡(luò)請(qǐng)求。當(dāng)然這里有個(gè)判斷重定向次數(shù)的邏輯,如果重定向超出20次則會(huì)拋出異常。
這樣我們的RetryAndFollowUpInterceptor攔截器就分析完了。
2.BridgeInterceptor
接下來(lái)看下BridgeInterceptor攔截器,這個(gè)攔截器顧名思義是起到一個(gè)橋梁的作用,連接應(yīng)用層和網(wǎng)絡(luò)層的代碼。相當(dāng)于把應(yīng)用層的代碼轉(zhuǎn)換成比較繁瑣的HTTP協(xié)議相關(guān)的東西,比如報(bào)文頭部的一些字段。比較簡(jiǎn)單這里就不展開(kāi)說(shuō)了。
3.CacheInterceptor
接下來(lái)看下CacheInterceptor這個(gè)攔截器,看名字就可以看出來(lái)這個(gè)是用來(lái)緩沖的,緩存HTTP返回的數(shù)據(jù)。講這個(gè)緩存攔截器之前還是有必要講下HTTP的緩存機(jī)制。
HTTP緩存機(jī)制
我們知道一個(gè)HTTP請(qǐng)求,其實(shí)就是客戶端發(fā)送請(qǐng)求報(bào)文,然后服務(wù)器接返回響應(yīng)報(bào)文的過(guò)程。通常當(dāng)我們需要某個(gè)資源的時(shí)候我們就會(huì)直接從服務(wù)器那請(qǐng)求,但如果每次請(qǐng)求時(shí)服務(wù)器資源都是一樣的沒(méi)有發(fā)生改變,這時(shí)我們就可以在第一次拿到資源后存在本地,下次如果需要就直接從本地讀取。但是有個(gè)問(wèn)題,什么時(shí)候從本地獲取什么時(shí)候從服務(wù)器拉取。這就涉及到HTTP的緩存機(jī)制。
HTTP緩存機(jī)制聽(tīng)起來(lái)挺復(fù)雜,其實(shí)就是利用一些HTTP報(bào)文頭來(lái)定義一套緩存規(guī)則。
HTTP緩存分強(qiáng)制緩存
和對(duì)比緩存
。
強(qiáng)制緩存
如上圖所示,左右兩個(gè)圖分別是本地緩存命中和不命中的流程。當(dāng)緩存命中,即緩存數(shù)據(jù)庫(kù)中有緩存數(shù)據(jù)并且沒(méi)有失效,就可以直接返回?cái)?shù)據(jù),不用向服務(wù)器發(fā)起請(qǐng)求。如果沒(méi)有命中,即緩存數(shù)據(jù)庫(kù)中沒(méi)有緩存數(shù)據(jù)或者數(shù)據(jù)失效,那么就要向服務(wù)器發(fā)起請(qǐng)求,服務(wù)器成功返回后,將數(shù)據(jù)保存到數(shù)據(jù)庫(kù)。那么上面提到的怎么確定緩存數(shù)據(jù)是否失效呢?
有兩種方式,分別是用
Expires
和Cache-Control
字段Expires
這個(gè)比較簡(jiǎn)單,就是當(dāng)向服務(wù)器請(qǐng)求資源時(shí),服務(wù)器會(huì)在響應(yīng)報(bào)文頭部增加Expires字段,表示這個(gè)資源的到期時(shí)間,如果下次請(qǐng)求數(shù)據(jù)的時(shí)間在這個(gè)時(shí)間內(nèi)就直接使用緩存數(shù)據(jù),否則就要重新向服務(wù)器請(qǐng)求資源。不過(guò)這個(gè)字段是HTTP1.0的,現(xiàn)在瀏覽器默認(rèn)使用HTTP1.1。
Cache-Control
由于Expires過(guò)期時(shí)間是服務(wù)器給的,可能會(huì)和客戶端的時(shí)間不一致,從而導(dǎo)致誤差的出現(xiàn)。所以引入了Cache-Control規(guī)則。Cache-Control定義很多字段:
字段 | 含義 |
---|---|
private | 客戶端可以緩存 |
public | 客戶端和代理服務(wù)器都可緩存 |
max-age = xxx | 緩存在xxx秒后失效 |
no-cache | 需要使用對(duì)比緩存來(lái)驗(yàn)證數(shù)據(jù) |
no-store | 所有數(shù)據(jù)都不緩存 |
對(duì)比緩存
上面左右分別是緩存命中和不命中的情況,可以看到所謂對(duì)比緩存就是當(dāng)向服務(wù)器請(qǐng)求資源時(shí),服務(wù)器會(huì)同時(shí)給你一個(gè)數(shù)據(jù)標(biāo)識(shí),下次再請(qǐng)求的時(shí)候要帶上這個(gè)標(biāo)識(shí),然后服務(wù)器會(huì)驗(yàn)證這個(gè)標(biāo)識(shí),如果驗(yàn)證到這個(gè)標(biāo)識(shí)對(duì)應(yīng)的數(shù)據(jù)未失效則返回304告知使用本地緩存數(shù)據(jù),否則返回最新的資源以及新的數(shù)據(jù)標(biāo)識(shí)。這個(gè)標(biāo)識(shí)有點(diǎn)類似于APP的登錄token,第一次登錄時(shí)服務(wù)器會(huì)返回一個(gè)token,后續(xù)再登錄只用發(fā)送這個(gè)token給服務(wù)器就可以。當(dāng)然這里不叫token。有下面兩種方式:
Last-Modified/If-Modified-Since
和Etag/If-None-Match
。下面分別來(lái)看下這兩種方式:Last-Modified/If-Modified-Since
當(dāng)客戶端向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)器返回響應(yīng)報(bào)文的同時(shí)還會(huì)在報(bào)文頭部添加該資源最近一次修改的時(shí)間,用Last-Modified來(lái)表示,后面跟具體時(shí)間,這樣當(dāng)客戶端再次需要這個(gè)數(shù)據(jù)時(shí),要在請(qǐng)求報(bào)文頭部增加If-Modified-Since字段,內(nèi)容就是之前Last-Modified后面的時(shí)間,服務(wù)器收到If-Modified-Since的值后,會(huì)進(jìn)行校驗(yàn)看最近更改時(shí)間是否一致,如果一致則返回304狀態(tài)碼,告知客戶端資源未更改可直接使用本地緩存,否則會(huì)返回新的資源和最近的更改時(shí)間。
Etag/If-None-Match
這個(gè)流程類似,當(dāng)客戶端向服務(wù)器發(fā)起請(qǐng)求時(shí),服務(wù)器返回響應(yīng)報(bào)文的同時(shí)會(huì)返回該資源的唯一標(biāo)識(shí)Etag,有點(diǎn)類似token,生成規(guī)則由服務(wù)器決定。當(dāng)客戶端再次發(fā)起請(qǐng)求是需要在報(bào)文頭部用If-None-Match字段后面就是上次保存的Etag,服務(wù)器接收到會(huì)校驗(yàn)這個(gè)值,如果資源更新了則這個(gè)值就會(huì)校驗(yàn)出錯(cuò),那么就會(huì)直接返回新的數(shù)據(jù)和新的Etag,否則返回304告知客戶端使用本地緩存。
緩存流程
上面我們看到,HTTP緩存有好幾種方式,每種方式所用字段也不一樣,那到底該使用哪種,或者說(shuō)當(dāng)同時(shí)出現(xiàn)上面的情況,以哪個(gè)為先,其實(shí)這也是由一定流程和優(yōu)先級(jí)的。他們的優(yōu)先級(jí)和流程圖如下:
知道HTTP的緩存機(jī)制,再來(lái)看這個(gè)CacheInterceptor會(huì)容易很多,我們來(lái)看下,
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
首先有個(gè)內(nèi)部cache容器,如果cache不為空則獲取當(dāng)前request對(duì)應(yīng)的response,否則返回空值。
long now = System.currentTimeMillis();
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get();
Request networkRequest = strategy.networkRequest;
Response cacheResponse = strategy.cacheResponse;
上面代碼由request和cacheCandidate定義了一個(gè)CacheStrategy類,CacheStrategy里具體實(shí)現(xiàn)其實(shí)就是我們上面講的HTTP緩存機(jī)制,然后獲取strategy的networkRequest和cacheResponse,這兩個(gè)不一定都有值,有可能為空,接下來(lái)的代碼就是根據(jù)這兩個(gè)是否為空的情況來(lái)判斷是要網(wǎng)絡(luò)請(qǐng)求還是直接使用緩存數(shù)據(jù)庫(kù)。
if (cacheCandidate != null && cacheResponse == null) {
closeQuietly(cacheCandidate.body()); // The cache candidate wasn't applicable. Close it.
}
這句比較好理解,如果cacheCandidate不為空并且cacheResponse為空,就清空之前的緩存。
// 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();
}
上面也比較好理解,如果不是用網(wǎng)絡(luò)并且之前也沒(méi)緩存,就返回504錯(cuò)誤。
// If we don't need the network, we're done.
if (networkRequest == null) {
return cacheResponse.newBuilder()
.cacheResponse(stripBody(cacheResponse))
.build();
}
如果沒(méi)有網(wǎng)路,但之前有緩存,則直接返回之前的緩存
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());
}
}
接下里,如果networkRequest不為空,則進(jìn)行網(wǎng)絡(luò)請(qǐng)求。
// 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());
}
}
如果之前有緩存,并且上面的網(wǎng)絡(luò)請(qǐng)求返回304,則使用之前的緩存,并更新cache緩存集合。
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;
如果不是304則說(shuō)明是新的資源,則接下里就是緩存這個(gè)新的response并返回。
這樣CacheInterceptor這個(gè)攔截器就說(shuō)完了。
4.ConnectInterceptor
接下來(lái)看下連接攔截器ConnectInterceptor,先看下它的intercept方法:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
Transmitter transmitter = realChain.transmitter();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
Exchange exchange = transmitter.newExchange(chain, doExtensiveHealthChecks);
return realChain.proceed(request, transmitter, exchange);
}
上面我們可以看到,這里的核心代碼是通過(guò)transmitter的newExchange方法創(chuàng)建一個(gè)Exchange對(duì)象,然后把它傳入到下一個(gè)攔截器中,這個(gè)Exchange可以理解成每次客戶端向服務(wù)器請(qǐng)求時(shí)進(jìn)行的數(shù)據(jù)交換,說(shuō)白了就是后面的攔截器就是通過(guò)這個(gè)類來(lái)進(jìn)行數(shù)據(jù)的讀寫操作,而這個(gè)攔截器做得工作就是與服務(wù)器建立連接,然后提供這個(gè)Exchange對(duì)象。所以接下來(lái)重點(diǎn)來(lái)看下這個(gè)對(duì)象是如何被創(chuàng)建出來(lái)的。我們進(jìn)入newExchange方法:
/** Returns a new exchange to carry a new request and response. */
Exchange newExchange(Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
synchronized (connectionPool) {
if (noMoreExchanges) throw new IllegalStateException("released");
if (exchange != null) throw new IllegalStateException("exchange != null");
}
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
synchronized (connectionPool) {
this.exchange = result;
this.exchangeRequestDone = false;
this.exchangeResponseDone = false;
return result;
}
}
這里關(guān)鍵是這兩行代碼:
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
先是通過(guò)exchangeFinder的find方法獲取一個(gè)ExchangeCodec對(duì)象,然后利用這個(gè)ExchangeCodec對(duì)象再創(chuàng)建Exchange對(duì)象。這里可能有人會(huì)感到奇怪,這里的exchangeFinder是哪來(lái)的,其實(shí)就在RetryAndFollowUpInterceptor
的transmitter.prepareToConnect(request);
這行代碼里就已經(jīng)初始化好了,可以進(jìn)入這個(gè)方法看下:
public void prepareToConnect(Request request) {
if (this.request != null) {
if (sameConnection(this.request.url(), request.url())) return; // Already ready.
if (exchange != null) throw new IllegalStateException();
if (exchangeFinder != null) {
maybeReleaseConnection(null, true);
exchangeFinder = null;
}
}
this.request = request;
this.exchangeFinder = new ExchangeFinder(this, connectionPool, createAddress(request.url()),
call, eventListener);
}
然后這里通過(guò)exchangeFinder找到一個(gè)ExchangeCodec,這個(gè)其實(shí)就是一個(gè)編碼解碼器,通俗點(diǎn)就是針對(duì)不同的協(xié)議比如HTTP1和HTTP2采用讀寫協(xié)議的不同。接下來(lái)就繼續(xù)看下這個(gè)find方法是如何實(shí)現(xiàn)的:
public ExchangeCodec find(
OkHttpClient client, Interceptor.Chain chain, boolean doExtensiveHealthChecks) {
int connectTimeout = chain.connectTimeoutMillis();
int readTimeout = chain.readTimeoutMillis();
int writeTimeout = chain.writeTimeoutMillis();
int pingIntervalMillis = client.pingIntervalMillis();
boolean connectionRetryEnabled = client.retryOnConnectionFailure();
try {
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);
return resultConnection.newCodec(client, chain);
} catch (RouteException e) {
trackFailure();
throw e;
} catch (IOException e) {
trackFailure();
throw new RouteException(e);
}
}
可以看到里面主要是調(diào)用findHealthyConnection這個(gè)方法獲取一個(gè)客戶端和服務(wù)器的連接,然后調(diào)用這個(gè)newCodec方法來(lái)創(chuàng)建ExchangeCodec,所以接下來(lái)就看下findHealthyConnection方法:
private RealConnection findHealthyConnection(int connectTimeout, int readTimeout,
int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled,
boolean doExtensiveHealthChecks) throws IOException {
while (true) {
RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks.
synchronized (connectionPool) {
if (candidate.successCount == 0) {
return candidate;
}
}
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it
// isn't, take it out of the pool and start again.
if (!candidate.isHealthy(doExtensiveHealthChecks)) {
candidate.noNewExchanges();
continue;
}
return candidate;
}
}
這里代碼也不是很復(fù)雜,有一個(gè)while循環(huán),通過(guò)findConnection來(lái)找到一個(gè)連接,如果這個(gè)連接是一個(gè)新的連接就直接返回,否則還需要做下額外的檢查,如果這個(gè)連接不是健康的連接,就標(biāo)志這個(gè)連接為不可用并且再重新查找連接,這樣不斷循環(huán)直到找到可用的連接。這里繼續(xù)往下看下findConnection是如何實(shí)現(xiàn)的:
private RealConnection findConnection(int connectTimeout, int readTimeout, int writeTimeout,
int pingIntervalMillis, boolean connectionRetryEnabled) throws IOException {
boolean foundPooledConnection = false;
RealConnection result = null;
Route selectedRoute = null;
RealConnection releasedConnection;
Socket toClose;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
hasStreamFailure = false; // This is a fresh attempt.
Route previousRoute = retryCurrentRoute()
? transmitter.connection.route()
: null;
// Attempt to use an already-allocated connection. We need to be careful here because our
// already-allocated connection may have been restricted from creating new exchanges.
releasedConnection = transmitter.connection;
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
if (result == null) {
// Attempt to get a connection from the pool.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else {
selectedRoute = previousRoute;
}
}
}
closeQuietly(toClose);
if (releasedConnection != null) {
eventListener.connectionReleased(call, releasedConnection);
}
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
}
if (result != null) {
// If we found an already-allocated or pooled connection, we're done.
return result;
}
// If we need a route selection, make one. This is a blocking operation.
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
// Now that we have a set of IP addresses, make another attempt at getting a connection from
// the pool. This could match due to connection coalescing.
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
// 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.
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// If we found a pooled connection on the 2nd time around, we're done.
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// Do TCP + TLS handshakes. This is a blocking operation.
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
}
這個(gè)方法里的代碼賊ji兒長(zhǎng),我們不要慌慢慢來(lái)分析下,這里我們先不看那些細(xì)枝末節(jié),挑重點(diǎn)的來(lái)看,首先我們來(lái)看下這段代碼:
toClose = transmitter.connection != null && transmitter.connection.noNewExchanges
? transmitter.releaseConnectionNoEvents()
: null;
if (transmitter.connection != null) {
// We had an already-allocated connection and it's good.
result = transmitter.connection;
releasedConnection = null;
}
這里首先判斷下當(dāng)前transmitter內(nèi)存中的連接是否可用,如果不可用就回收掉,如果可用的話直接賦值給result,然后后面就直接返回這個(gè)連接。當(dāng)連接不可用的時(shí)候,就接著往下執(zhí)行,主要代碼如下:
if (result == null) {
// Attempt to get a connection from the pool.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, null, false)) {
foundPooledConnection = true;
result = transmitter.connection;
} else {
selectedRoute = previousRoute;
}
}
}
這里通過(guò)一個(gè)transmitterAcquirePooledConnection方法來(lái)獲取一個(gè)連接,這個(gè)方法傳入了一個(gè)transmitter參數(shù),如果找到可用連接那么transmitter中的connection就是有值的,所以就將transmitter.connection賦值給result,接下來(lái)就看下這個(gè)方法:
boolean transmitterAcquirePooledConnection(Address address, Transmitter transmitter,
@Nullable List<Route> routes, boolean requireMultiplexed) {
assert (Thread.holdsLock(this));
for (RealConnection connection : connections) {
if (requireMultiplexed && !connection.isMultiplexed()) continue;
if (!connection.isEligible(address, routes)) continue;
transmitter.acquireConnectionNoEvents(connection);
return true;
}
return false;
}
這個(gè)方法參數(shù)傳入了Address連接地址,Transmitter傳輸協(xié)議層,Route路由列表,這個(gè)Route其實(shí)就比Address多了一個(gè)代理類型,最后一個(gè)參數(shù)是否要求多路復(fù)用,然后我們看方法里面具體代碼,里面是一個(gè)對(duì)連接池的遍歷,如果當(dāng)前的連接不是多路復(fù)用,但如果requireMultiplexed是true即要求多路復(fù)用那就執(zhí)行continue遍歷下一個(gè)connection,這里我們傳入的requireMultiplexed值為false,所以會(huì)接著執(zhí)行下面的代碼,也就是通過(guò)調(diào)用connection 的isEligible方法來(lái)判斷當(dāng)前的連接是否可用,如果不可用就接著遍歷下個(gè)connection,否則就執(zhí)行下面的代碼獲取這個(gè)連接。我們看下這個(gè)isEligible方法:
boolean isEligible(Address address, @Nullable List<Route> routes) {
// 如果一個(gè)連接已經(jīng)有一個(gè)或多個(gè)請(qǐng)求或者該連接不可用就直接返回false
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// 除了主機(jī)名外,如果當(dāng)前連接的路由地址和要請(qǐng)求的地址不同就直接返回false
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// 如果主機(jī)名也一樣說(shuō)明是同一個(gè)連接返回true,表示該連接可用
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// 如果域名不一樣,說(shuō)明連接不可重用,但是有一種情況除外,就是如果當(dāng)前為HTTP2協(xié)議,域名不一樣也是可以重用連接的,這個(gè)叫做連接合并,具體連接合并的概念可以參考一下文章
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. 當(dāng)前連接必須為HTTP2,否則為不可用
if (http2Connection == null) return false;
// 2. 不同的路由必須對(duì)應(yīng)到同一臺(tái)主機(jī),否則為不可用
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. 下面是驗(yàn)證證書相關(guān)的東西
if (address.hostnameVerifier() != OkHostnameVerifier.INSTANCE) return false;
if (!supportsUrl(address.url())) return false;
try {
address.certificatePinner().check(address.url().host(), handshake().peerCertificates());
} catch (SSLPeerUnverifiedException e) {
return false;
}
return true; // 該連接可重用
}
上面方法中的代碼都做了注釋,相信還是很好理解的。上面我們調(diào)用transmitterAcquirePooledConnection方法是傳入的routes為null,表示只是在連接池中查找HTTP1非多路復(fù)用的連接。如果找不到,我們接著再看下面:
boolean newRouteSelection = false;
if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
newRouteSelection = true;
routeSelection = routeSelector.next();
}
List<Route> routes = null;
synchronized (connectionPool) {
if (transmitter.isCanceled()) throw new IOException("Canceled");
if (newRouteSelection) {
routes = routeSelection.getAll();
if (connectionPool.transmitterAcquirePooledConnection(
address, transmitter, routes, false)) {
foundPooledConnection = true;
result = transmitter.connection;
}
}
if (!foundPooledConnection) {
if (selectedRoute == null) {
selectedRoute = routeSelection.next();
}
//創(chuàng)建一個(gè)連接(實(shí)際連接動(dòng)作在后面)
result = new RealConnection(connectionPool, selectedRoute);
connectingConnection = result;
}
}
// 如果找到則直接返回
if (foundPooledConnection) {
eventListener.connectionAcquired(call, result);
return result;
}
// 建立連接
result.connect(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis,
connectionRetryEnabled, call, eventListener);
connectionPool.routeDatabase.connected(result.route());
當(dāng)上面HTTP1的連接找不到時(shí),我們當(dāng)前請(qǐng)求可能有很多其他路由,比如有很多代理服務(wù)器,它們組成一個(gè)個(gè)IP列表,相當(dāng)于有很多route,然后把這個(gè)route集合傳入transmitterAcquirePooledConnection方法,來(lái)查找可多路復(fù)用的HTTP2連接。如果還沒(méi)找到可用的連接就自己創(chuàng)建一個(gè)RealConnection然后調(diào)用connect方法建立連接。
建立完連接后我們接著看下:
Socket socket = null;
synchronized (connectionPool) {
connectingConnection = null;
// Last attempt at connection coalescing, which only occurs if we attempted multiple
// concurrent connections to the same host.
if (connectionPool.transmitterAcquirePooledConnection(address, transmitter, routes, true)) {
// We lost the race! Close the connection we created and return the pooled connection.
result.noNewExchanges = true;
socket = result.socket();
result = transmitter.connection;
} else {
connectionPool.put(result);
transmitter.acquireConnectionNoEvents(result);
}
}
closeQuietly(socket);
eventListener.connectionAcquired(call, result);
return result;
可以看到連接建立成功后,并不是馬上返回,而是又調(diào)用了一次transmitterAcquirePooledConnection方法,并傳入了routes且requireMultiplexed參數(shù)為true,說(shuō)明此時(shí)是在連接池中只查找多路復(fù)用的,為啥還要查找一遍?不是連接已經(jīng)創(chuàng)建成功了?因?yàn)榧偃绠?dāng)我們正好同時(shí)進(jìn)行兩個(gè)請(qǐng)求時(shí),可能會(huì)出現(xiàn)創(chuàng)建了兩次連接,但是如果這兩個(gè)連接符合多路復(fù)用,那么就會(huì)造成資源浪費(fèi),所以每次建立連接后會(huì)再檢查遍,確認(rèn)連接池沒(méi)有可用連接才返回當(dāng)前連接。這樣整個(gè)連接查找的過(guò)程的就分析完了。
接下來(lái)我們來(lái)簡(jiǎn)單看下result.connect方法是如何建立連接的:
if (route.requiresTunnel()) {
connectTunnel(connectTimeout, readTimeout, writeTimeout, call, eventListener);
if (rawSocket == null) {
break;
}
} else {
connectSocket(connectTimeout, readTimeout, call, eventListener);
}
establishProtocol(connectionSpecSelector, pingIntervalMillis, call, eventListener);
主要看下上面的關(guān)鍵代碼,可以看到,如果需要建立隧道tunnel則先建立tunnel,沒(méi)有就直接創(chuàng)建socket連接即TCP連接,建立連接后通過(guò)establishProtocol方法來(lái)進(jìn)行協(xié)議握手,比如HTTPS相關(guān)的SSL握手及HTTP2相關(guān)協(xié)議,這里就不展開(kāi)講了。
上面我們用大量的篇幅講解了連接的獲取和建立,知道這個(gè)流程其實(shí)對(duì)ConnectInterceptor這個(gè)攔截器就已經(jīng)了解得差不多了,其他的一些細(xì)枝末節(jié)稍微再看下就好了。接下來(lái)來(lái)看下下一個(gè)攔截器:
5.CallServerInterceptor
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Exchange exchange = realChain.exchange();
Request request = realChain.request();
long sentRequestMillis = System.currentTimeMillis();
exchange.writeRequestHeaders(request);
boolean responseHeadersStarted = false;
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"))) {
exchange.flushRequest();
responseHeadersStarted = true;
exchange.responseHeadersStart();
responseBuilder = exchange.readResponseHeaders(true);
}
if (responseBuilder == null) {
if (request.body().isDuplex()) {
// Prepare a duplex body so that the application can send a request body later.
exchange.flushRequest();
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, true));
request.body().writeTo(bufferedRequestBody);
} else {
// Write the request body if the "Expect: 100-continue" expectation was met.
BufferedSink bufferedRequestBody = Okio.buffer(
exchange.createRequestBody(request, false));
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
}
} else {
exchange.noRequestBody();
if (!exchange.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.
exchange.noNewExchangesOnConnection();
}
}
} else {
exchange.noRequestBody();
}
if (request.body() == null || !request.body().isDuplex()) {
exchange.finishRequest();
}
if (!responseHeadersStarted) {
exchange.responseHeadersStart();
}
if (responseBuilder == null) {
responseBuilder = exchange.readResponseHeaders(false);
}
Response response = responseBuilder
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
int code = response.code();
if (code == 100) {
// server sent a 100-continue even though we did not request one.
// try again to read the actual response
response = exchange.readResponseHeaders(false)
.request(request)
.handshake(exchange.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
code = response.code();
}
exchange.responseHeadersEnd(response);
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(exchange.openResponseBody(response))
.build();
}
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
exchange.noNewExchangesOnConnection();
}
if ((code == 204 || code == 205) && response.body().contentLength() > 0) {
throw new ProtocolException(
"HTTP " + code + " had non-zero Content-Length: " + response.body().contentLength());
}
return response;
}
這是實(shí)際和服務(wù)端進(jìn)行數(shù)據(jù)交互的攔截器,可以看到正如上面所說(shuō),它的數(shù)據(jù)交互就是用我們?cè)贑onnectInterceptor中創(chuàng)建的Exchange來(lái)進(jìn)行數(shù)據(jù)的讀寫。如果你繼續(xù)深挖下去的話其實(shí)可以看到這里數(shù)據(jù)的讀寫操作是用到了Square他們自己家的另一個(gè)開(kāi)源庫(kù)okio
,這個(gè)庫(kù)是專門處理I/O流的讀寫,比Java自帶那一套API要方便很多,有興趣的同學(xué)可以研究下這個(gè)庫(kù),這里就不繼續(xù)展開(kāi)了。
6.結(jié)尾
到這里OkHttp中的攔截器也就都分析完了,攔截器的處理流程也是OkHttp的最妙的地方,理解了其中攔截器的實(shí)現(xiàn)也算是對(duì)該庫(kù)有了一個(gè)很好的理解。