聲明:原創作品,轉載請註明出處https://www.jianshu.com/p/8c32e928613c
這篇文章接着上面一篇文章來詳細分析各個攔截器的實現機制,主要的攔截器有這幾個:
-
RetryAndFollowUpInterceptor
、 -
BridgeInterceptor
、 -
CacheInterceptor
、 -
ConnectInterceptor
、 -
CallServerInterceptor
。
接下來挨個看下:
1.RetryAndFollowUpInterceptor
這個攔截器是用來處理異常請求重試和重定向的,所謂重定向,說的簡單點就是請求某一個資源,被告知資源被更改,讓你換個路徑重新請求。接下來就來看下源碼是怎麼實現的:
@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;
}
}
源碼有點長,我們來挨個看下,首先拿到請求體request、這個鏈條chain以及transmitter,這個transmitter其實是應用層和網絡層的一個橋樑。接下來會進入到一個while循環中,在循環一開始會調用transmitter的prepareToConnect方法進行網絡層的初始化,如下代碼,然後判斷下該請求是否已被取消,如果是則直接拋出異常。
transmitter.prepareToConnect(request);
if (transmitter.isCanceled()) {
throw new IOException("Canceled");
}
接下來就是調用chain的proceed方法將request傳遞給下一個攔截器進行網絡請求,如下:
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();
}
}
這個proceed方法在一個try/catch中執行,當出現對應的異常時,會調用recover方法來判斷這個請求是否是可恢復的,如果可恢復則會重新執行while循環進行請求重試。如果不可恢復則直接拋出對應的異常。
我們來看下recover的判斷邏輯:
private boolean recover(IOException e, Transmitter transmitter,
boolean requestSendStarted, Request userRequest) {
// 應用層禁止重試
if (!client.retryOnConnectionFailure()) return false;
// 無法再次發送請求體
if (requestSendStarted && requestIsOneShot(e, userRequest)) return false;
// 發生嚴重的錯誤異常
if (!isRecoverable(e, requestSendStarted)) return false;
// 沒有額外的路由可嘗試
if (!transmitter.canRetry()) return false;
return true;
}
首先會判斷下我們的client是否配置了當連接失敗可以重試,如果沒有則返回false,即不可恢復。如果我們配置了可以重試,那麼接下來會判斷我們的請求是否已經發送出去,並且請求只能被髮送一次,如果滿足條件則表示不可恢復。如果不滿足條件,則會調用isRecoverable方法進行接下來的判斷。這個方法會判斷拋出的異常是什麼異常,如果是協議異常或者其他的一些特殊的異常則不可恢復。否則就調用transmitter的canRetry()方法進行判斷,這個方法內部會判斷是否有更多的路由可重試,如果沒有則返回false不可重試,如果上面的條件都不滿足則返回true,表示可重試。
接下來我們跳出recover方法,繼續看接下來的代碼。
如果上面的執行沒有拋出異常,則會繼續往下接着執行:
// 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();
}
接下來會判斷下priorResponse是否爲空,這個priorResponse是保存着上次返回的response,如果不爲空則會創建一個新的response,這個新的response將老的response和當前的response組合起來。這裏我們是第一次執行,所以priorResponse爲空,裏面也就不會執行。接下來再看下面的代碼:
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;
首先會通過response得到exchange和route,exchange可以理解成實際處理io的類。然後調用followUpRequest方法並傳入exchange和route參數獲取重定向後的request,當然如果不是重定向的話就會返回空。接下來,如果上面返回的followUp重定向request爲空的話,則表示我們的請求是正常的,就直接返回。這樣到這邏輯就執行結束了。如果不爲空就會接着往下執行,如果followUp的body不爲空並且只能被髮送一次,那麼就直接返回這個response,執行結束。當然這個isOneShot方法默認是false的,所以不會直接返回。接下來繼續執行,會關閉一些資源,然後把上面的followUp重定向的request作爲新的request,然後把重定向返回的response賦值給priorResponse,接着會重複while循環進行再次的網絡請求。當然這裏有個判斷重定向次數的邏輯,如果重定向超出20次則會拋出異常。
這樣我們的RetryAndFollowUpInterceptor攔截器就分析完了。
2.BridgeInterceptor
接下來看下BridgeInterceptor攔截器,這個攔截器顧名思義是起到一個橋樑的作用,連接應用層和網絡層的代碼。相當於把應用層的代碼轉換成比較繁瑣的HTTP協議相關的東西,比如報文頭部的一些字段。比較簡單這裏就不展開說了。
3.CacheInterceptor
接下來看下CacheInterceptor這個攔截器,看名字就可以看出來這個是用來緩衝的,緩存HTTP返回的數據。講這個緩存攔截器之前還是有必要講下HTTP的緩存機制。
HTTP緩存機制
我們知道一個HTTP請求,其實就是客戶端發送請求報文,然後服務器接返回響應報文的過程。通常當我們需要某個資源的時候我們就會直接從服務器那請求,但如果每次請求時服務器資源都是一樣的沒有發生改變,這時我們就可以在第一次拿到資源後存在本地,下次如果需要就直接從本地讀取。但是有個問題,什麼時候從本地獲取什麼時候從服務器拉取。這就涉及到HTTP的緩存機制。
HTTP緩存機制聽起來挺複雜,其實就是利用一些HTTP報文頭來定義一套緩存規則。
HTTP緩存分強制緩存
和對比緩存
。
強制緩存
如上圖所示,左右兩個圖分別是本地緩存命中和不命中的流程。當緩存命中,即緩存數據庫中有緩存數據並且沒有失效,就可以直接返回數據,不用向服務器發起請求。如果沒有命中,即緩存數據庫中沒有緩存數據或者數據失效,那麼就要向服務器發起請求,服務器成功返回後,將數據保存到數據庫。那麼上面提到的怎麼確定緩存數據是否失效呢?
有兩種方式,分別是用
Expires
和Cache-Control
字段Expires
這個比較簡單,就是當向服務器請求資源時,服務器會在響應報文頭部增加Expires字段,表示這個資源的到期時間,如果下次請求數據的時間在這個時間內就直接使用緩存數據,否則就要重新向服務器請求資源。不過這個字段是HTTP1.0的,現在瀏覽器默認使用HTTP1.1。
Cache-Control
由於Expires過期時間是服務器給的,可能會和客戶端的時間不一致,從而導致誤差的出現。所以引入了Cache-Control規則。Cache-Control定義很多字段:
字段 | 含義 |
---|---|
private | 客戶端可以緩存 |
public | 客戶端和代理服務器都可緩存 |
max-age = xxx | 緩存在xxx秒後失效 |
no-cache | 需要使用對比緩存來驗證數據 |
no-store | 所有數據都不緩存 |
對比緩存
上面左右分別是緩存命中和不命中的情況,可以看到所謂對比緩存就是當向服務器請求資源時,服務器會同時給你一個數據標識,下次再請求的時候要帶上這個標識,然後服務器會驗證這個標識,如果驗證到這個標識對應的數據未失效則返回304告知使用本地緩存數據,否則返回最新的資源以及新的數據標識。這個標識有點類似於APP的登錄token,第一次登錄時服務器會返回一個token,後續再登錄只用發送這個token給服務器就可以。當然這裏不叫token。有下面兩種方式:
Last-Modified/If-Modified-Since
和Etag/If-None-Match
。下面分別來看下這兩種方式:Last-Modified/If-Modified-Since
當客戶端向服務器發起請求時,服務器返回響應報文的同時還會在報文頭部添加該資源最近一次修改的時間,用Last-Modified來表示,後面跟具體時間,這樣當客戶端再次需要這個數據時,要在請求報文頭部增加If-Modified-Since字段,內容就是之前Last-Modified後面的時間,服務器收到If-Modified-Since的值後,會進行校驗看最近更改時間是否一致,如果一致則返回304狀態碼,告知客戶端資源未更改可直接使用本地緩存,否則會返回新的資源和最近的更改時間。
Etag/If-None-Match
這個流程類似,當客戶端向服務器發起請求時,服務器返回響應報文的同時會返回該資源的唯一標識Etag,有點類似token,生成規則由服務器決定。當客戶端再次發起請求是需要在報文頭部用If-None-Match字段後面就是上次保存的Etag,服務器接收到會校驗這個值,如果資源更新了則這個值就會校驗出錯,那麼就會直接返回新的數據和新的Etag,否則返回304告知客戶端使用本地緩存。
緩存流程
上面我們看到,HTTP緩存有好幾種方式,每種方式所用字段也不一樣,那到底該使用哪種,或者說當同時出現上面的情況,以哪個爲先,其實這也是由一定流程和優先級的。他們的優先級和流程圖如下:
知道HTTP的緩存機制,再來看這個CacheInterceptor會容易很多,我們來看下,
Response cacheCandidate = cache != null
? cache.get(chain.request())
: null;
首先有個內部cache容器,如果cache不爲空則獲取當前request對應的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定義了一個CacheStrategy類,CacheStrategy裏具體實現其實就是我們上面講的HTTP緩存機制,然後獲取strategy的networkRequest和cacheResponse,這兩個不一定都有值,有可能爲空,接下來的代碼就是根據這兩個是否爲空的情況來判斷是要網絡請求還是直接使用緩存數據庫。
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();
}
上面也比較好理解,如果不是用網絡並且之前也沒緩存,就返回504錯誤。
// 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());
}
}
接下里,如果networkRequest不爲空,則進行網絡請求。
// 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());
}
}
如果之前有緩存,並且上面的網絡請求返回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則說明是新的資源,則接下里就是緩存這個新的response並返回。
這樣CacheInterceptor這個攔截器就說完了。
4.ConnectInterceptor
接下來看下連接攔截器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);
}
上面我們可以看到,這裏的核心代碼是通過transmitter的newExchange方法創建一個Exchange對象,然後把它傳入到下一個攔截器中,這個Exchange可以理解成每次客戶端向服務器請求時進行的數據交換,說白了就是後面的攔截器就是通過這個類來進行數據的讀寫操作,而這個攔截器做得工作就是與服務器建立連接,然後提供這個Exchange對象。所以接下來重點來看下這個對象是如何被創建出來的。我們進入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;
}
}
這裏關鍵是這兩行代碼:
ExchangeCodec codec = exchangeFinder.find(client, chain, doExtensiveHealthChecks);
Exchange result = new Exchange(this, call, eventListener, exchangeFinder, codec);
先是通過exchangeFinder的find方法獲取一個ExchangeCodec對象,然後利用這個ExchangeCodec對象再創建Exchange對象。這裏可能有人會感到奇怪,這裏的exchangeFinder是哪來的,其實就在RetryAndFollowUpInterceptor
的transmitter.prepareToConnect(request);
這行代碼裏就已經初始化好了,可以進入這個方法看下:
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);
}
然後這裏通過exchangeFinder找到一個ExchangeCodec,這個其實就是一個編碼解碼器,通俗點就是針對不同的協議比如HTTP1和HTTP2採用讀寫協議的不同。接下來就繼續看下這個find方法是如何實現的:
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);
}
}
可以看到裏面主要是調用findHealthyConnection這個方法獲取一個客戶端和服務器的連接,然後調用這個newCodec方法來創建ExchangeCodec,所以接下來就看下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;
}
}
這裏代碼也不是很複雜,有一個while循環,通過findConnection來找到一個連接,如果這個連接是一個新的連接就直接返回,否則還需要做下額外的檢查,如果這個連接不是健康的連接,就標誌這個連接爲不可用並且再重新查找連接,這樣不斷循環直到找到可用的連接。這裏繼續往下看下findConnection是如何實現的:
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;
}
這個方法裏的代碼賊ji兒長,我們不要慌慢慢來分析下,這裏我們先不看那些細枝末節,挑重點的來看,首先我們來看下這段代碼:
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;
}
這裏首先判斷下當前transmitter內存中的連接是否可用,如果不可用就回收掉,如果可用的話直接賦值給result,然後後面就直接返回這個連接。當連接不可用的時候,就接着往下執行,主要代碼如下:
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;
}
}
}
這裏通過一個transmitterAcquirePooledConnection方法來獲取一個連接,這個方法傳入了一個transmitter參數,如果找到可用連接那麼transmitter中的connection就是有值的,所以就將transmitter.connection賦值給result,接下來就看下這個方法:
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;
}
這個方法參數傳入了Address連接地址,Transmitter傳輸協議層,Route路由列表,這個Route其實就比Address多了一個代理類型,最後一個參數是否要求多路複用,然後我們看方法裏面具體代碼,裏面是一個對連接池的遍歷,如果當前的連接不是多路複用,但如果requireMultiplexed是true即要求多路複用那就執行continue遍歷下一個connection,這裏我們傳入的requireMultiplexed值爲false,所以會接着執行下面的代碼,也就是通過調用connection 的isEligible方法來判斷當前的連接是否可用,如果不可用就接着遍歷下個connection,否則就執行下面的代碼獲取這個連接。我們看下這個isEligible方法:
boolean isEligible(Address address, @Nullable List<Route> routes) {
// 如果一個連接已經有一個或多個請求或者該連接不可用就直接返回false
if (transmitters.size() >= allocationLimit || noNewExchanges) return false;
// 除了主機名外,如果當前連接的路由地址和要請求的地址不同就直接返回false
if (!Internal.instance.equalsNonHost(this.route.address(), address)) return false;
// 如果主機名也一樣說明是同一個連接返回true,表示該連接可用
if (address.url().host().equals(this.route().address().url().host())) {
return true; // This connection is a perfect match.
}
// 如果域名不一樣,說明連接不可重用,但是有一種情況除外,就是如果當前爲HTTP2協議,域名不一樣也是可以重用連接的,這個叫做連接合並,具體連接合並的概念可以參考一下文章
// https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding
// https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. 當前連接必須爲HTTP2,否則爲不可用
if (http2Connection == null) return false;
// 2. 不同的路由必須對應到同一臺主機,否則爲不可用
if (routes == null || !routeMatchesAny(routes)) return false;
// 3. 下面是驗證證書相關的東西
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; // 該連接可重用
}
上面方法中的代碼都做了註釋,相信還是很好理解的。上面我們調用transmitterAcquirePooledConnection方法是傳入的routes爲null,表示只是在連接池中查找HTTP1非多路複用的連接。如果找不到,我們接着再看下面:
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();
}
//創建一個連接(實際連接動作在後面)
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());
當上面HTTP1的連接找不到時,我們當前請求可能有很多其他路由,比如有很多代理服務器,它們組成一個個IP列表,相當於有很多route,然後把這個route集合傳入transmitterAcquirePooledConnection方法,來查找可多路複用的HTTP2連接。如果還沒找到可用的連接就自己創建一個RealConnection然後調用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;
可以看到連接建立成功後,並不是馬上返回,而是又調用了一次transmitterAcquirePooledConnection方法,並傳入了routes且requireMultiplexed參數爲true,說明此時是在連接池中只查找多路複用的,爲啥還要查找一遍?不是連接已經創建成功了?因爲假如當我們正好同時進行兩個請求時,可能會出現創建了兩次連接,但是如果這兩個連接符合多路複用,那麼就會造成資源浪費,所以每次建立連接後會再檢查遍,確認連接池沒有可用連接才返回當前連接。這樣整個連接查找的過程的就分析完了。
接下來我們來簡單看下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);
主要看下上面的關鍵代碼,可以看到,如果需要建立隧道tunnel則先建立tunnel,沒有就直接創建socket連接即TCP連接,建立連接後通過establishProtocol方法來進行協議握手,比如HTTPS相關的SSL握手及HTTP2相關協議,這裏就不展開講了。
上面我們用大量的篇幅講解了連接的獲取和建立,知道這個流程其實對ConnectInterceptor這個攔截器就已經瞭解得差不多了,其他的一些細枝末節稍微再看下就好了。接下來來看下下一個攔截器:
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;
}
這是實際和服務端進行數據交互的攔截器,可以看到正如上面所說,它的數據交互就是用我們在ConnectInterceptor中創建的Exchange來進行數據的讀寫。如果你繼續深挖下去的話其實可以看到這裏數據的讀寫操作是用到了Square他們自己家的另一個開源庫okio
,這個庫是專門處理I/O流的讀寫,比Java自帶那一套API要方便很多,有興趣的同學可以研究下這個庫,這裏就不繼續展開了。
6.結尾
到這裏OkHttp中的攔截器也就都分析完了,攔截器的處理流程也是OkHttp的最妙的地方,理解了其中攔截器的實現也算是對該庫有了一個很好的理解。