1.okhttp源碼分析(一)——基本流程(超詳細)
2.okhttp源碼分析(二)——RetryAndFollowUpInterceptor過濾器
3.okhttp源碼分析(三)——CacheInterceptor過濾器
4.okhttp源碼分析(四)——ConnectInterceptor過濾器
5.okhttp源碼分析(五)——CallServerInterceptor過濾器
前言
緊接著上一篇基本流程分析完后,準備的是將過濾器分析分析,過濾器可以說是OkHttp的點睛之筆。這一篇主要分析RetryAndFollowUpInterceptor這個過濾器,首先從名字我們就可以明白這個過濾器的職責(zé)是重試和重定向。
分析
1.宏觀流程
讀過上一篇博客的人應(yīng)該都清楚看一個過濾器的功能,重點都在他重寫Interceptor的intercept(Chain chain)
方法。
打開源碼突然發(fā)現(xiàn)這么長。。。不要絕望,我按照我的理解將代碼處理一下,我們先從流程看起,至少從宏觀上要對這個過濾器有個認識。
@Override public Response intercept(Chain chain) throws IOException {
。。。
while (true) {
。。。
try {
response = realChain.proceed(request, streamAllocation, null, null);
}
。。。
if(滿足條件){
return response;
}
。。。
//不滿足條件,一頓操作,賦值再來!
request = followUp;
priorResponse = response;
}
}
其實就流程來上,我認為宏觀上代碼縮減到這樣就夠了,甚至可以再刪點,這里先從流程上理解.
其實代碼成這樣,基本上大家都能理解了,一個while(true)表明這是個循環(huán)體,循環(huán)體主要做的事可以看到其實是遞歸的主要方法。
response = realChain.proceed(request, streamAllocation, null, null);
執(zhí)行了這個方法后,就會交給下一個過濾器繼續(xù)執(zhí)行,所以單從這里來看,我們可以簡單的理解為這個過濾器其實沒做什么。
但是當(dāng)出現(xiàn)了一些問題,導(dǎo)致不滿足條件的時候,就需要進行一系列的操作,重新復(fù)制Request,重新請求,這也就是while的功能,對應(yīng)的也就是這個過濾器的主要功能:重試和重定向。
這里我們宏觀上已經(jīng)對RetryAndFollowUpInterceptor有了一個基本的理解了。
2.過程細節(jié)
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
//streamAllocation的創(chuàng)建位置
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
//取消
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
response = realChain.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.
//先判斷當(dāng)前請求是否已經(jīng)發(fā)送了
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.
//這里基本上都沒有講,priorResponse是用來保存前一個Resposne的,這里可以看到將前一個Response和當(dāng)前的Resposne
//結(jié)合在一起了,對應(yīng)的場景是,當(dāng)獲得Resposne后,發(fā)現(xiàn)需要重定向,則將當(dāng)前Resposne設(shè)置給priorResponse,再執(zhí)行一遍流程,
//直到不需要重定向了,則將priorResponse和Resposne結(jié)合起來。
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
//判斷是否需要重定向,如果需要重定向則返回一個重定向的Request,沒有則為null
Request followUp = followUpRequest(response);
if (followUp == null) {
//不需要重定向
if (!forWebSocket) {
//是WebSocket,釋放
streamAllocation.release();
}
//返回response
return response;
}
//需要重定向,關(guān)閉響應(yīng)流
closeQuietly(response.body());
//重定向次數(shù)++,并且小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//是UnrepeatableRequestBody, 剛才看過也就是是流類型,沒有被緩存,不能重定向
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//判斷是否相同,不然重新創(chuàng)建一個streamConnection
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, 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;
}
}
現(xiàn)在就要從源碼上具體學(xué)習(xí)理解這個過濾器了。這里我具體一點一點分析。
//streamAllocation的創(chuàng)建位置
streamAllocation = new StreamAllocation(client.connectionPool(), createAddress(request.url()),
call, eventListener, callStackTrace);
首先第一個點,這里可以看到這里對streamAllocation進行了初始化操作,其實在過濾器的鏈式調(diào)用的過程中會陸陸續(xù)續(xù)創(chuàng)建一系列對應(yīng)的參數(shù),這一點從最初的創(chuàng)建Chain的時候就可以看出來,可以看到一開始有很多參數(shù)是以null傳入的。
Response getResponseWithInterceptorChain() throws IOException {
。。。
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
。。。
return chain.proceed(originalRequest);
}
這里先大概說一下StreamAllocation這個對象是干什么的。這個類大概可以理解為是處理Connections,Streams,Calls三者之間的關(guān)系,這一點其實從構(gòu)造函數(shù)的傳參也可以看出來。
接下來就要進入循環(huán)體中看了,首先可以看到當(dāng)請求被取消的時候,會跳出循環(huán)體(第一種跳出的情況)。
boolean releaseConnection = true;
try {
response = realChain.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;
}
接下來看try catch體中的內(nèi)容,try其實很簡單,就是執(zhí)行后續(xù)過濾器鏈中的東西,這里要稍微注意一下releaseConnection這個變量的,對后續(xù)的判斷理解是有影響的,可以看到初始化時將releaseConnection這個變量賦值為true。
下面是重點內(nèi)容了:
進入catch體中,可以看到會捕獲很多okHttp自定義的Exception,從名字上可以有一個大體上的理解,但是還是要從源碼上分析,這里先看第一個異常RouteException,先理解理解注釋:嘗試連接一個路由失敗,這個請求還沒有被發(fā)出,接下來執(zhí)行了一個方法recover(),這里注意一下false參數(shù),現(xiàn)在進入方法體中。
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
//如果OkHttpClient直接配置拒絕失敗重連,return false
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
//如果請求已經(jīng)發(fā)送,并且這個請求體是一個UnrepeatableRequestBody類型,則不能重試。
//StreamedRequestBody實現(xiàn)了UnrepeatableRequestBody接口,是個流類型,不會被緩存,所以只能執(zhí)行一次,具體可看。
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// This exception is fatal.
//一些嚴重的問題,就不要重試了
if (!isRecoverable(e, requestSendStarted)) return false;
// No more routes to attempt.
//沒有更多的路由就不要重試了
if (!streamAllocation.hasMoreRoutes()) return false;
// For failure recovery, use the same route selector with a new connection.
return true;
}
可以看到這里面有很多的if判斷,這里先看第一個。
if (!client.retryOnConnectionFailure()) return false;
//==========OkHttpClient.java===========
public boolean retryOnConnectionFailure() {
return retryOnConnectionFailure;
}
代碼一放上來其實就很好理解了,如果我們在配置OkHttpClient中配置retryOnConnectionFailure屬性為false,表明拒絕失敗重連,那么這里返回false(第一種拒絕重連的方式)。這里另外說明一下如果我們默認方式創(chuàng)建OkHttpClient的話,retryOnConnectionFailure屬性是true。
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
下面一個判斷首先要明白參數(shù)的含義,這里requestSendStarted這個參數(shù)就是剛才在recover方法中的第二個參數(shù),是為了表明請求是否已經(jīng)被發(fā)送,這里這里為false,但是這個判斷我們需要了解清楚。
單單從判斷條件我們可以理解為:如果請求已經(jīng)發(fā)送,并且這個請求體是一個UnrepeatableRequestBody類型,則不能重試(第二種拒絕重連的方式)。
這里就要說明一下UnrepeatableRequestBody這個類了。
public interface UnrepeatableRequestBody {
}
這就是UnrepeatableRequestBody的源碼,沒有看錯...就是一個空的接口,作用就是標記那些不能被重復(fù)請求的請求體,這時候可能就想要了解一下那些請求是不能被重復(fù)請求的哪?看一下那些Request實現(xiàn)了這個接口,結(jié)果會發(fā)現(xiàn),到目前Okhttp源碼中,只有一種請求實現(xiàn)了這個接口,那就是StreamedRequestBody。
/**
* This request body streams bytes from an application thread to an OkHttp dispatcher thread via a
* pipe. Because the data is not buffered it can only be transmitted once.
*/
final class StreamedRequestBody extends OutputStreamRequestBody implements UnrepeatableRequestBody {}
從這個類的注釋我們也可以理解,StreamedRequestBody實現(xiàn)了UnrepeatableRequestBody接口,是個流類型,不會被緩存,所以只能執(zhí)行一次。
if (!isRecoverable(e, requestSendStarted)) return false;
//=====================isRecoverable()=====================
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// If there was a protocol problem, don't recover.
//如果是協(xié)議問題,不要在重試了
if (e instanceof ProtocolException) {
return false;
}
// If there was an interruption don't recover, but if there was a timeout connecting to a route
// we should try the next route (if there is one).
if (e instanceof InterruptedIOException) {
//超時問題,并且請求還沒有被發(fā)送,可以重試
//其他就不要重試了
return e instanceof SocketTimeoutException && !requestSendStarted;
}
// Look for known client-side or negotiation errors that are unlikely to be fixed by trying
// again with a different route.
if (e instanceof SSLHandshakeException) {
// If the problem was a CertificateException from the X509TrustManager,
// do not retry.
//理解為如果是安全原因,就不要重試了
if (e.getCause() instanceof CertificateException) {
return false;
}
}
if (e instanceof SSLPeerUnverifiedException) {
// e.g. a certificate pinning error.
//安全原因
return false;
}
// An example of one we might want to retry with a different route is a problem connecting to a
// proxy and would manifest as a standard IOException. Unless it is one we know we should not
// retry, we return true and try a new route.
return true;
}
下面一個判斷可以總體理解為:如果是一些嚴重的問題(協(xié)議,安全...),拒絕重試(第三種拒絕重連的方式)
這里可以看到判斷被歸結(jié)到一個isRecoverable()的方法中,注釋頁寫的很清楚,這里嚴重的情況主要由這幾種:
- 1.協(xié)議問題,不能重試。
- 2.如果是超時問題,并且請求沒有被發(fā)送,可以重試,其他的就不要重試了。
- 3.安全問題,不要重試。
if (!streamAllocation.hasMoreRoutes()) return false;
//========================StreamAllocation.java=====================
public boolean hasMoreRoutes() {
return route != null
|| (routeSelection != null && routeSelection.hasNext())
|| routeSelector.hasNext();
}
下面這個判斷表明:沒有更多的可以使用的路由,則不要重試了(第四種拒絕重連的方式)
這里也列出了hasMoreRoutes()方法,可以看到,這里面當(dāng)游標在最末尾,也就是保存的路由的容器已經(jīng)遍歷完了,也就沒辦法繼續(xù)重試了。這里大概說明一下routeSelection是用List保存的。
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;
}
所以當(dāng)以上判斷結(jié)束后,如果需要重試,則continue,重新執(zhí)行while循環(huán)體,也就是發(fā)揮了這個過濾器的作用,重試
catch (IOException e) {
// An attempt to communicate with a server failed. The request may have been sent.
//先判斷當(dāng)前請求是否已經(jīng)發(fā)送了
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//同樣的重試判斷
if (!recover(e, requestSendStarted, request)) throw e;
releaseConnection = false;
//重試。。。
continue;
}
這時候看一下下一個異常IOException,首先可以看到,需要先判斷請求是否已經(jīng)發(fā)送了,緊接著繼續(xù)剛才分析的方法recover(),這時默認傳的就不是false,而是判斷得到的requestSendStarted。最后同樣當(dāng)需要重試時,continue。
finally {
// We're throwing an unchecked exception. Release any resources.
//沒有捕獲到的異常,最終要釋放
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
finally體中的內(nèi)容比較好理解,由于releaseConnection初始化為true,而當(dāng)正常執(zhí)行realChain.proceed
或在執(zhí)行過程中捕捉到異常時設(shè)置為false,所以當(dāng)執(zhí)行過程中捕捉到?jīng)]有檢測到的異常時,需要釋放一些內(nèi)容。(此處感謝@messi_wpy的指正)
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
接下來這段代碼一開始是我比較難以理解的,而且網(wǎng)上其他分析這個過濾器的都沒有分析這塊,最后自己分析,理解為priorResponse是用來保存前一個Resposne的,這里可以看到將前一個Response和當(dāng)前的Resposne結(jié)合在一起了。對應(yīng)的場景是:當(dāng)獲得Resposne后,發(fā)現(xiàn)需要重定向,則將當(dāng)前Resposne設(shè)置給priorResponse,再執(zhí)行一遍流程,直到不需要重定向了,則將priorResponse和Resposne結(jié)合起來。
Request followUp = followUpRequest(response);
//=========================followUpRequest()==============================
private Request followUpRequest(Response userResponse) throws IOException {
if (userResponse == null) throw new IllegalStateException();
Connection connection = streamAllocation.connection();
Route route = connection != null
? connection.route()
: null;
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
case HTTP_PROXY_AUTH:
Proxy selectedProxy = route != null
? route.proxy()
: client.proxy();
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw new ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy");
}
return client.proxyAuthenticator().authenticate(route, userResponse);
case HTTP_UNAUTHORIZED:
return client.authenticator().authenticate(route, userResponse);
case HTTP_PERM_REDIRECT:
case HTTP_TEMP_REDIRECT:
// "If the 307 or 308 status code is received in response to a request other than GET
// or HEAD, the user agent MUST NOT automatically redirect the request"
if (!method.equals("GET") && !method.equals("HEAD")) {
return null;
}
// fall-through
case HTTP_MULT_CHOICE:
case HTTP_MOVED_PERM:
case HTTP_MOVED_TEMP:
case HTTP_SEE_OTHER:
// Does the client allow redirects?
if (!client.followRedirects()) return null;
String location = userResponse.header("Location");
if (location == null) return null;
HttpUrl url = userResponse.request().url().resolve(location);
// Don't follow redirects to unsupported protocols.
if (url == null) return null;
// If configured, don't follow redirects between SSL and non-SSL.
boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
if (!sameScheme && !client.followSslRedirects()) return null;
// Most redirects don't include a request body.
Request.Builder requestBuilder = userResponse.request().newBuilder();
if (HttpMethod.permitsRequestBody(method)) {
final boolean maintainBody = HttpMethod.redirectsWithBody(method);
if (HttpMethod.redirectsToGet(method)) {
requestBuilder.method("GET", null);
} else {
RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
requestBuilder.method(method, requestBody);
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding");
requestBuilder.removeHeader("Content-Length");
requestBuilder.removeHeader("Content-Type");
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!sameConnection(userResponse, url)) {
requestBuilder.removeHeader("Authorization");
}
//重新構(gòu)造了一個Request
return requestBuilder.url(url).build();
case HTTP_CLIENT_TIMEOUT:
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure()) {
// The application layer has directed us not to retry the request.
return null;
}
if (userResponse.request().body() instanceof UnrepeatableRequestBody) {
return null;
}
if (userResponse.priorResponse() != null
&& userResponse.priorResponse().code() == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null;
}
return userResponse.request();
default:
return null;
}
}
下面這行代碼主要是對followUpRequest()這個方法的理解,代碼我也粘出來了,這里其實沒必要在意每一行代碼,這樣反而影響我們閱讀,這里主要可以觀察發(fā)現(xiàn),其實這個方法的主要操作就是,當(dāng)返回碼滿足某些條件時就重新構(gòu)造一個Request,不滿足就返回null,所以接下來的代碼就很容易理解了。
if (followUp == null) {
//不需要重定向
if (!forWebSocket) {
//是WebSocket,釋放
streamAllocation.release();
}
//返回response
return response;
}
當(dāng)不需要重定向,也就是返回的為null,直接返回response。
//需要重定向,關(guān)閉響應(yīng)流
closeQuietly(response.body());
//重定向次數(shù)++,并且小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//是UnrepeatableRequestBody, 剛才看過也就是是流類型,沒有被緩存,不能重定向
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
//判斷是否相同,不然重新創(chuàng)建一個streamConnection
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, 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;
//==========================sameConnection()======================
private boolean sameConnection(Response response, HttpUrl followUp) {
HttpUrl url = response.request().url();
return url.host().equals(followUp.host())
&& url.port() == followUp.port()
&& url.scheme().equals(followUp.scheme());
}
下面的代碼當(dāng)然就是當(dāng)返回的不為空,也就是重新構(gòu)造了一個Request,需要重定向。
- 1.首先關(guān)閉響應(yīng)流。
- 2.增加重定向的次數(shù),保證小于最大重定向次數(shù)MAX_FOLLOW_UPS(20)
- 3.不能是UnrepeatableRequestBody類型,剛才也分析過,是一個空接口,用于標記那些只能請求一次的請求。
- 4.判斷是否相同,如果不相同,則需要重新創(chuàng)建一個streamConnection。
- 5.重新賦值,結(jié)束當(dāng)前循環(huán),繼續(xù)while循環(huán),也就是執(zhí)行重定向請求。
總結(jié)
到此,RetryAndFollowUpInterceptor這個過濾器已經(jīng)大體分析完了,總體流程下來可以發(fā)現(xiàn),這個過濾器的主要作用就是用于對請求的重試和重定向的。
其中拒絕重試的判斷條件有如下幾種:
- 1.如果我們在配置OkHttpClient中配置retryOnConnectionFailure屬性為false,表明拒絕失敗重連,那么這里返回false
- 2.如果請求已經(jīng)發(fā)送,并且這個請求體是一個UnrepeatableRequestBody類型,則不能重試
- 3.如果是一些嚴重的問題(協(xié)議,安全...),拒絕重試
- 4.沒有更多的可以使用的路由,則不要重試了