OkHttp的精髓之一就在于Interceptor。其内部已经为我们定制了五个Interceptor——RetryAndFollowUpInterceptor、BridgeInterceptor、CacheInterceptor、ConnectInterceptor、CallServerInterceptor。除了系统预设的五个Interceptor之外,我们还可以自定义Interceptor实现各种需求。这篇笔记主要是记录RetryAndFollowUpInterceptor。
概述
RetryAndFollowUpInterceptor是除去我们自定义的拦截器之外第一个执行的拦截器。其主要作用是进行请求的重试与重定向。主要处理的业务是:
1. 实例化StreamAllocation,初始化一个Socket对象,获取到输入/输出流
2. 开启循环;创建下一个调用链,等待返回结果(Response)
3. 如果发生错误,判断是否继续请求,否则退出
4. 检查响应是否符合要求,是则返回
5. 关闭响应结果
6. 判断是否达到最大限制数,是则返回
7. 检查是否有相同连接,是则释放,重建连接
8. 重复以上流程
StreamAllocation
StreamAllocation是一个Socket管理类,管理着Connections、Streams、Calls三者之间的关系。计网的知识告诉我们Http连接是基于TCP的,需要进行三次握手。握手的前提是根据域名或者代理确定Socket的IP与端口。这个过程就是由StreamAllocation来管理。由于在RetryAndFollowUpInterceptor中仅仅是创建一个StreamAllocation实例传递给下面的拦截器,在这里不做更详细记录。
源码分析
在RetryAndFollowUpInterceptor的intercept方法内部,会开启一个while(true)
循环,首先会判断当前请求是否被取消,如果被取消,则释放连接,并抛出IOException:
if (canceled) {
streamAllocation.release(); //释放连接
throw new IOException("Canceled");
}
紧接着会向前推进,调用链中下一个结点,并获取到Response,同时进行异常捕获。根据各个情景判断是否需要释放连接:
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.
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进行释放
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
在上面代码中,捕获到异常之后会调用recover(…)方法进行判断能否恢复连接,这个方法内部的逻辑如下:
/**
* Report and attempt to recover from a failure to communicate with a server. Returns true if
* {@code e} is recoverable, or false if the failure is permanent. Requests with a body can only
* be recovered if the body is buffered or if the failure occurred before the request has been
* sent.
*/
private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// The application layer has forbidden retries.
//client是否允许恢复,在通过Builder创建client的时候可以设置
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;
}
上面判断是否能恢复中调用了isRecoverable(…)方法,内部逻辑在代码注释中已经有详细说明,如下:
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// If there was a protocol problem, don't recover.
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) {
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;
}
接下来在followUpRequest(response)方法里通过响应码来判断是否需要重定向,重新设置请求,如果followUpRequest(response)方法返回的是null,说明无需重定向,直接将response返回链中的上一个结点。
Request followUp = followUpRequest(response);
以上就是RetryAndFollowUpInterceptor的大致逻辑,下一篇笔记中会总结记录BridgeInterceptor与CacheInterceptor的逻辑。