RetryAndFollowUpInterceptor攔截器分析
源碼地址:https://github.com/square/okhttp
前面已經(jīng)對整體流程以及幾個類做了了解遵湖,這里就開始對第一個攔截器RetryAndFollowUpInterceptor的分析了囱桨。
整體結(jié)構(gòu)
首先通過一張圖了解一下這個攔截器的整體結(jié)構(gòu):
縱觀整個類,方法分為了兩部分:
- 供外部調(diào)用的:cancle相關(guān)的 , intercept等 ;
- 內(nèi)部使用的记靡,主要服務(wù)于intercept方法。
那么接下來我們就從兩部分對這個類進(jìn)行分析:
外部調(diào)用方法
//進(jìn)行取消連接的操作
public void cancel() {
canceled = true;
//通過StreamAllocation進(jìn)行cancle操作
StreamAllocation streamAllocation = this.streamAllocation;
if (streamAllocation != null) streamAllocation.cancel();
}
//獲取cancle狀態(tài)
public boolean isCanceled() {
return canceled;
}
//設(shè)置調(diào)用堆棧跟蹤盯捌,在創(chuàng)建StreamAllocation對象的時候作為參數(shù)傳入
public void setCallStackTrace(Object callStackTrace) {
this.callStackTrace = callStackTrace;
}
//獲取StreamAllocation蓖柔,一個流分配的類,處理連接谓媒,數(shù)據(jù)流,Call請求的關(guān)系
public StreamAllocation streamAllocation() {
return streamAllocation;
}
這些方法提供給外部調(diào)用何乎,你跟蹤一下句惯,會發(fā)現(xiàn)其實(shí)都是給RealCall調(diào)用使用的,而Cancle相關(guān)的方法支救,RealCall 就直接提供給外部使用了抢野。也就是我們?nèi)∠粋€連接的操作。而另外兩個方法各墨,則都是提供給內(nèi)部使用指孤。
這個幾個方法中,你會發(fā)現(xiàn),大部分都與這個StreamAllocation類有關(guān)恃轩。這個類到底是什么结洼?到底做了什么操作?在后面還會用到嗎叉跛?帶著這些疑問松忍,在這一節(jié)之后再對這個類進(jìn)行學(xué)習(xí)分析。
intercept方法
每個攔截器筷厘,最重要的方法就是這個intercept方法了鸣峭。
而對于RetryAndFollowUpInterceptor這個攔截器都做了些什么?
新建StreamAllocation類敞掘,傳入okhttpClicent中創(chuàng)建的連接池,傳入通過url創(chuàng)建Address類楣铁,傳入callStackTrace玖雁,調(diào)用堆棧跟蹤。
開啟一個while循環(huán)盖腕;
判讀是否用戶已經(jīng)cancle,是-拋出異常赫冬,否-繼續(xù)走下去;
通過RealInterceptorChain調(diào)用下一個攔截器(request, streamAllocation)溃列,并等待下一個攔截器返回結(jié)果劲厌。
獲取返回結(jié)果做兩個捕獲異常,處理后面步驟拋出的異常, 判斷是否繼續(xù)請求听隐。
判讀結(jié)果是否符合要求补鼻,返回或者進(jìn)行重定向。
關(guān)閉響應(yīng)結(jié)果
判斷重定向數(shù)目是否超過 MAX_FOLLOW_UPS(20)
判斷重新連接是否問相同的連接雅任,否-新建风范,是-釋放。
循環(huán)2以后的步驟沪么。
源碼:
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
//1. 新建StreamAllocation類
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
//2.開啟一個while循環(huán)
while (true) {
//3.cancle判讀
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
//4.調(diào)用下一個攔截器
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.
//5.判斷是否繼續(xù)請求
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();
}
//6.判斷是否重定向或者超時重試
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 7. 關(guān)閉響應(yīng)結(jié)果
closeQuietly(response.body());
// 8.判斷是否重定向數(shù)目
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;
}
}
內(nèi)部調(diào)用方法
提供內(nèi)部的方法都是服務(wù)于interceptor()硼婿,有三個:
createAddress,通過url的host禽车,port, dns 以及一系列ohHttpClient的協(xié)議連接參數(shù)寇漫,簡歷一個Address類。而這個Address對象是提供給StreamAllocation殉摔,建立連接使用的州胳。
recover, 連接異常判斷逸月,是否繼續(xù)陋葡。(含isRecoverable)
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
//調(diào)用層面,是否禁止了重連重試
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
//請求的Request本身出錯彻采,不能繼續(xù)再連接
// We can't send the request body again.
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 是否可以恢復(fù)的腐缤,主要判斷了捌归,是否協(xié)議異常,中斷異常岭粤,SSL的握手異常惜索,SSL通道未許可異常。
// 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;
}
- followUpRequest剃浇, 通過結(jié)果的http code碼巾兆,來判斷是否可以重定向,可以正常重定向會返回對應(yīng)的Request,不然就返回null虎囚。
總結(jié):
RetryAndFollowUpInterceptor 的主要做了三件事:
初始化了連接的對象(StreamAllocation角塑,但是比沒有真正建立連接,只是初始化了對象)(前置攔截)淘讥;
通過RealInterceptorChain圃伶,再調(diào)用下一個攔截器;
收到結(jié)果之后蒲列,做異常處理窒朋,判斷是否重連或者重定向,或者返回結(jié)果蝗岖。(后置攔截)
作為第一個攔截器侥猩,功能也是相對比較簡單的。留下一個疑問就是StreamAllocation 這個類抵赢,這個類的學(xué)習(xí)在以后會專門提出來欺劳。
系列:
OKhttp源碼學(xué)習(xí)(一)—— 基本請求流程
OKhttp源碼學(xué)習(xí)(二)—— OkHttpClient
OKhttp源碼學(xué)習(xí)(三)—— Request, RealCall
OKhttp源碼學(xué)習(xí)(五)—— BridgeInterceptor
OKhttp源碼學(xué)習(xí)(六)—— CacheInterceptor攔截器
OKhttp源碼學(xué)習(xí)(七)—— ConnectInterceptor攔截器
OKhttp源碼學(xué)習(xí)(八)——CallServerInterceptor攔截器
OKhttp源碼學(xué)習(xí)(九)—— 任務(wù)管理(Dispatcher)