1.RetryAndFollowUpInterceptor
作为OKHTTP内置的第一个拦截器, 其功能主要有以下几点:
- 1、创建StreamAllocation
- 2、调用RealInterceptorChain.proceed()进行网络请求
- 3、根据异常结果或者响应结果判断是否要进行重新请求 结合代码, 重连机制是通过while(true)实现
重试机制分两个层面的重试机制:
- 1、请求过程中抛出的异常(连接建议、HTTP请求)
- 2、请求成功之后获取到状态码, 根据状态码判断是否需要重试
第二个重试有一个最大次数限制, 但是第一种情况的重试则很可能会出现死循环的情况. 如何定位这种问题, OKHTTP提供的了自定义拦截器可以实现这个需求.
如果Request能够正常执行, 则所有拦截器都是递归的方式依次顺序执行完成, 但是如果请求的链路上发生了异常, 如果抛出了RouteException或者是IOExcept, 则可能会被RetryAndFollowUpInterceptor捕获, 然后判断这种异常是否可以重新连接, 如果可以重新连接, 则继续从BridgeInterceptor拦截器开始进行处理, 此时如果只通过自定义的NormalInterceptors拦截器, 是无法感知这种重连的, 可以通过自定义的NetworkInterceptor可以感知这种重试情况.
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
// 1.创建StreamAllocation对象
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
int followUpCount = 0;
Response priorResponse = null;
// 2.通过while(true)实现重连机制
while (true) {
// 3.如果显示调用cancel方法取消该请求, 重连机制取消
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// 4.将请求交给RealInterceptorChain, 然后对请求链的异常进行捕获与分析
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// 5.如果发生了路由异常, 再分析ConnectInterceptor拦截器时了解到路由异常发生在TCP连接阶段
// 针对该阶段抛出的异常, 通过recover判断该异常是否需要抛出还是可以继续重连
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getFirstConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// 6.捕获到IO异常, 判断该异常是否需要抛出还是可以继续重连.
// 与5都是通过recover进行判断, 区别就是第三个参数, 5传的是false, 6传的是requestSendStarted,
// 之所以这样, 是因为这个阶段可能存在两种情况, (1)HTTP请求还未发送到服务器就出现了异常,
// (2) 请求发送到服务器, 但是由于服务器原因, 连接被迫断开, 导致IO异常
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 7.执行到这里, 说明请求已经发送到Server, 并且收到了Server端的响应, 但是响应码不一定为200
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
Request followUp;
try {
// 8.请求获得正常响应, 根据请求结果判断是否需要进行重试
followUp = followUpRequest(response, streamAllocation.route());
} catch (IOException e) {
streamAllocation.release();
throw e;
}
if (followUp == null) {
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()), 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;
}
}
2.RouteException
当抛出RouteException异常时, 通过以下几步来确定是否需要重连;
- 1、通过recover方法检测该RouteException是否能重新连接
- 2、如果不能重连, 抛出该异常, 如果能重连, 将标识位releaseConnection置为false, 表示不释放连接
- 3、continue进入到下一次循环, 进行网络请求
2.1 RouteException场景下recover
结合上图, RouteException是在RealConnection.connect与StreamAllocation.newStream中抛出, 这个阶段是建立TCP连接的过程, 此过程请求还没有开始, 同时注意RouteException异常时, recover第三个参数为false, 表示请求还没有发出
@return true:可以重连, false:不支持重连
private boolean recover(IOException e, StreamAllocation streamAllocation,
boolean requestSendStarted, Request userRequest) {
streamAllocation.streamFailed(e);
// 1.如果应用层不支持重连(对外提供接口, 提高优先级, SDK开发某些功能是否可用可以参考这里的设计)
if (!client.retryOnConnectionFailure()) return false;
// 2.请求体不支持重试机制时
if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
// 3.判断该异常是否支持重连
if (!isRecoverable(e, requestSendStarted)) return false;
// 4.如果没有更多的路由, 也不支持重连
if (!streamAllocation.hasMoreRoutes()) return false;
// 5.剩余的场景可以重连
return true;
}
2.2 isRecoverable
@return true: 允许该异常的情况下进行重连, false: 不允许该异常的情况下进行重连
private boolean isRecoverable(IOException e, boolean requestSendStarted) {
// 1.如果协议异常
if (e instanceof ProtocolException) {
return false;
}
// 2.如果请求还未发出的情况下发生了超时异常, 也允许这种情况下进行重新连接
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.
// 3.如果是证书校验相关的异常, 也不允许进行重连
if (e instanceof SSLHandshakeException) {
if (e.getCause() instanceof CertificateException) {
return false;
}
}
if (e instanceof SSLPeerUnverifiedException) {
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.
// 4.注释解释的很清楚了, 除非这个异常是我们明确知道的不能进行重连的, 否则我们尽量尝试进行下一次的重连, 重连时切换到
// 下一个路由(这里的下一个路由包括同一个Proxy下的路由, 或者是切换到下一个Proxy然后遍历其所有的Route)
return true;
}