上一篇文章我們分析了 OkHttpClient 創(chuàng)建請求哑子,以及相關(guān)隊列操作的一些方法。具體可查看
探究Okhttp的運行原理(1)
此篇文章我們繼續(xù)來看 OkHttpClient 另外一個重要的流程getResponseWithInterceptorChain() 方法去獲取請求響應(yīng)的乾蓬。
getResponseWithInterceptorChain 方法
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);// RetryAndFollowUpInterceptor 攔截器
interceptors.add(new BridgeInterceptor(client.cookieJar())); // BridgeInterceptor 攔截器
interceptors.add(new CacheInterceptor(client.internalCache())); // CacheInterceptor 攔截器
interceptors.add(new ConnectInterceptor(client)); // ConnectInterceptor 攔截器
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket)); // CallServerInterceptor 攔截器
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis()); // 初始化 RealInterceptorChain 內(nèi)部變量
return chain.proceed(originalRequest); // 執(zhí)行
}
getResponseWithInterceptorChain 加入了五個主要的攔截器剥懒,攔截器即攔截請求鱼鸠,做一些對應(yīng)的處理;
了解攔截器工作原理之前色迂,首先看下 RealInterceptorChain 類,Okhttp 通過 RealInterceptorChain 使用責(zé)任鏈模式處理下發(fā)的請求手销;
RealInterceptorChain 類內(nèi)部維護了 Request 請求歇僧、interceptors 攔截器容器、當(dāng)前攔截器(初始為0);
RealInterceptorChain
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
......
// Call the next interceptor in the chain.
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout); // 創(chuàng)建下一個RealInterceptorChain
Interceptor interceptor = interceptors.get(index);// 獲取到 index 的攔截器
Response response = interceptor.intercept(next); // 執(zhí)行攔截器的 intercept
......
return response;
}
攔截器調(diào)用 proceed 方法主要做了一下操作:
1 诈悍、創(chuàng)建下一個攔截器(傳參為 index + 1)祸轮;
2、 獲取當(dāng)前 index 值(初始為 0)的攔截器侥钳,第一個即為 RetryAndFollowUpInterceptor 攔截器
3适袜、執(zhí)行 RetryAndFollowUpInterceptor 的 intercept 方法;
intercept 方法具有傳遞效果(即鏈?zhǔn)絺鬟f給下一個攔截器)舷夺,在 RetryAndFollowUpInterceptor 內(nèi)部苦酱,會再次調(diào)用 RealInterceptorChain (即傳入的 next 參數(shù))的 proceed 方法,index 會繼續(xù)自增给猾,拿到下一個攔截器疫萤,從而完成責(zé)任傳遞效果;
直到最終返回結(jié)果的處理耙册,過程如下圖所示:
接下來给僵,分析第一個攔截器的 intercept 方法 RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor 請求重定向攔截器
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request(); // 獲取請求
RealInterceptorChain realChain = (RealInterceptorChain) chain;// 獲取攔截器
Call call = realChain.call(); // 獲取 RealCall 對象
EventListener eventListener = realChain.eventListener();// 獲取監(jiān)聽事件
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation; // 創(chuàng)建 streamAllocation 實例
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(), streamAllocation, 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, streamAllocation, 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, streamAllocation.route()); // 如果地址被重定向,重新組裝重定向的請求
if (followUp == null) {// 沒有重定向 釋放資源
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
if (++followUpCount > MAX_FOLLOW_UPS) { // 重定向超過一定次數(shù) 釋放資源
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())) { // 不是同一個連接详拙,重定向操作 重新創(chuàng)建 StreamAllocation 實例
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} 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;
}
}
RetryAndFollowUpInterceptor 攔截器主要去處理請求的重定向相關(guān)操作帝际,攔截器先創(chuàng)建了 StreamAllocation 實例對象,而后通過調(diào)用 followUpRequest 方法去查看相應(yīng)返回 response 數(shù)據(jù)饶辙,判斷是否需要進(jìn)行重定向蹲诀;
private Request followUpRequest(Response userResponse, Route route) throws IOException {
if (userResponse == null) throw new IllegalStateException();
int responseCode = userResponse.code();
final String method = userResponse.request().method();
switch (responseCode) {
......
case HTTP_MULT_CHOICE: // 300
case HTTP_MOVED_PERM:// 301
case HTTP_MOVED_TEMP:// 302
case HTTP_SEE_OTHER: // 302
// 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");
}
return requestBuilder.url(url).build();
......
}
}
當(dāng)返回碼 300、301弃揽、302脯爪、303 時代表請求需要重定向,此時重新構(gòu)建 Request 請求并進(jìn)行返回矿微;
當(dāng) RetryAndFollowUpInterceptor 攔截器發(fā)現(xiàn)請求需要重定向的時候痕慢。即 followUpRequest 返回的 Requset 不為空的時候,會重新創(chuàng)建 StreamAllocation 實例對象涌矢;
這里分析下 StreamAllocation 對象掖举,后續(xù)的攔截器會使用到;
StreamAllocation
實例方法
new StreamAllocation(client.connectionPool(),createAddress(request.url()), call, eventListener, callStackTrace);
client.connectionPool() ------主要維護 RealConnection 類的隊列娜庇;
createAddress ------- 維護請求地址的相關(guān)信息
private Address createAddress(HttpUrl url) {
SSLSocketFactory sslSocketFactory = null;
HostnameVerifier hostnameVerifier = null;
CertificatePinner certificatePinner = null;
if (url.isHttps()) {
sslSocketFactory = client.sslSocketFactory();
hostnameVerifier = client.hostnameVerifier();
certificatePinner = client.certificatePinner();
}
return new Address(url.host(), url.port(), client.dns(), client.socketFactory(),
sslSocketFactory, hostnameVerifier, certificatePinner, client.proxyAuthenticator(),
client.proxy(), client.protocols(), client.connectionSpecs(), client.proxySelector()); // 包括 host 塔次、port 、dns 等數(shù)據(jù)
}
call ------ RealCall 實例
eventListener ------ 監(jiān)聽實例
callStackTrace ------ 記錄
看下 StreamAllocation 類名秀;
public final class StreamAllocation {
public final Address address;
private RouteSelector.Selection routeSelection;
private Route route;
private final ConnectionPool connectionPool;
public final Call call;
public final EventListener eventListener;
private final Object callStackTrace;
// State guarded by connectionPool.
private final RouteSelector routeSelector;
private int refusedStreamCount;
private RealConnection connection;
private boolean reportedAcquired;
private boolean released;
private boolean canceled;
private HttpCodec codec;
public StreamAllocation(ConnectionPool connectionPool, Address address, Call call,
EventListener eventListener, Object callStackTrace) {
this.connectionPool = connectionPool;
this.address = address;
this.call = call;
this.eventListener = eventListener;
this.routeSelector = new RouteSelector(address, routeDatabase(), call, eventListener);
this.callStackTrace = callStackTrace;
}
}
StreamAllocation 主要維護了上述變量的相關(guān)方法励负,這里先存儲著,等到后續(xù)攔截器進(jìn)行使用匕得;
RetryAndFollowUpInterceptor 的攔截器到這里就分析完了继榆,總結(jié) RetryAndFollowUpInterceptor 主要做了以下事情:
1、 創(chuàng)建 StreamAllocation 實例進(jìn)行保存,內(nèi)部維護了 RealConnection 類的隊列池裕照,同時保存請求相關(guān)的 Host攒发、Port、DNS 等信息供后續(xù)攔截器使用晋南;
2惠猿、 對請求返回的重定向進(jìn)行重新創(chuàng)建 Request 、StreamAllocation 负间;