一、前言
在上一篇文章中,简单分析了拦截器和拦截链的调用过程,简单概括起来就是拦截器的intercept方法中,对Request进行处理,并将处理后的Request传递给下一个拦截器,获取下一个拦截器返回的Response响应后,再对Response进行处理,并返回给上一级拦截器,而Okhttp中真正的网络请求,就是在这一系列的拦截器中完成的。而Okhttp框架提供的内部拦截器是从RetryAndFollowUpInterceptor拦截器开始执行的,所以我们先来分析他的源码。
二、RetryAndFollowUpInterceptor
RetryAndFollowUpInterceptor,顾名思义,就是重试重定向拦截器,从名字就可以看出它的主要作用就是进行网络失败重连的,下面我们从它的intercept方法来分析它是如何进行重连的。
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response = null;
boolean releaseConnection = true;
try {
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.
//路由异常,尝试恢复,如果再失败就抛出异常
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.
//前一个重试得到的Respons
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// followUpRequest方法的主要作用就是为新的重试Request添加验证头等内容
Request followUp = followUpRequest(response);
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
closeQuietly(response.body());
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;
}
}
在方法开始首先拿到上一级传进来的Request对象,然后就新建了一个streamAllocation对象,这个对象主要是用于管理客户端和服务端之间的连接,拿到I\O流(基于Okio),同时管理着连接池、请求资源的释放等操作。这个对象在RetryAndFollowUpInterceptor中并未用到,我们在后面会做详细分析。接着往下走,进入一个while循环,RetryAndFollowUpInterceptor就是通过这个while循环,当网络请求失败并满足一定重连条件时,进入循环进行重连。
接着,判断用户是否取消了该次网络请求,若是取消了请求,streamAllocation就释放连接,并抛出异常,接下来的try/catch代码块中的调用拦截链proceed方法,我们在上一节分析过了,就是把Request对象和刚刚创建的streamAllocation对象传递给下一个拦截器进行处理请求,并得到下一个拦截器的响应,在这个过程中,如果捕捉到异常,将通过recover方法判断是否能够继续连接:
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
if (!client.retryOnConnectionFailure()) return false;
// We can't send the request body again.
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;
}
出现以下几种情况时,将取消重连,抛出异常:
3.1、协议错误(ProtocolException)
3.2、超时(SocketTimeoutException && !requestSendStarted
3.3、SSL握手错误(SSLHandshakeException && CertificateException)
3.4、certificate pinning错误(SSLPeerUnverifiedException)4、没有更多路由线路选择
- 应用层配置禁止重连
- Request出错,无法继续使用;
- 通过isRecoverable判断是否可以重连,出现一下几种异常,无法重连:
回到try/catch代码块,不管请求成功与否,都会执行finally里的代码:
finally {
// We're throwing an unchecked exception. Release any resources.
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
通过releaseConnection判断是否有为捕捉到的异常,当有未捕捉到的异常时,则释放 streamAllocation。接下来,得到Response后,根据相应码,处理相应头,比如重定向、超时等,如果一切正常,则停止循环,返回Response。
在前面提到,当在连接过程中捕获到异常或者需要重定向时,会继续while循环,然而这个循环需要满足一定的条件,如果不满足停止该次请求,并抛出异常:
//重连次数超过最大限制数,抛出异常
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());
}
// 判断followUp请求是否和当前请求相同,如果相同则复用,否则销毁streamAllocation,
// 并创建新的streamAllocation(即为重定向请求)
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?");
}
三、总结
RetryAndFollowUpInterceptor是Okhttp内部的第一个拦截器,也是贯穿整个请求过程的拦截器,它主要做的工作可以总结为以下几点:
初始化streamAllocation;
调用下一个拦截链处理网络请求,拿到响应结果;
根据异常结果或响应结果判断是否需要进行重新请求
返回Response响应