目录
RetryAndFollowUpInterceptor:重试重定向拦截器
RetryAndFollowUpInterceptor.intercept
RetryAndFollowUpInterceptor作用:
系列文章:
OKHttp开源框架学习三:任务调度核心类Dispatcher
OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor
OKHttp开源框架学习六:拦截器之BridgeInterceptor
OKHttp开源框架学习八:拦截器之CacheInterceptor
OKHttp开源框架学习九:拦截器之ConnectInterceptor
OKHttp开源框架学习十:ConnectionPool连接池
OKHttp开源框架学习十一:拦截器之CallServerInterceptor
相关文章:
OKHttp3--重试及重定向拦截器RetryAndFollowUpInterceptor源码解析【六】
OkHttp框架的RetryAndFollowUpInterceptor请求重定向源码解析
RetryAndFollowUpInterceptor:重试重定向拦截器
它的主要作用就是失败重连
我们应该怎么理解这个拦截器呢?
其实从名字也可以大概知道它的意思,主要有两个方面:
一、请求失败后重新尝试连接:从Retry这个单词理解,但是在OKHttp中并不是所有的请求失败后(即返回码不是200)都会去重新连接,而是在发生 RouteException 或者 IOException 后再根据一些策略进行一些判断,如果可以恢复,就重新进请求
二、继续请求:FollowUp本意是跟进的意思,主要有以下几种类型可以继续发起请求
- 407/401:未进行身份认证,需要对请求头进行处理后再发起新的请求
- 408:客户端请求超时,如果 Request 的请求体没有被 UnrepeatableRequestBody 标记,会继续发起新的请求
- 308/307/303/302/301/300:需要进行重定向,发起新的请求
- 比如:client向server发送一个请求,要求获取一个资源但是server收到请求后发现这个资源在另一个位置,然后server就在返回的response的头部header的location字段中存入该资源新的地址url,并设置响应码为30x(常用状态码有301,303,也有临时码302,307)client收到响应后,根据响应码判断这是一个重定向的响应,就去解析url,最后再次发出请求获取资源
其中FollowUp的次数受到限制,OKHTTP内部限制次数为20次以内,避免消耗过多资源,20次是一个综合考虑的结果
Chrome遵循21次重定向; Firefox,curl和wget遵循20; Safari跟随16; HTTP / 1.0建议5
RetryAndFollowUpInterceptor.intercept
RetryAndFollowUpInterceptor进入到这个类中,我们看intercept()方法:
/**
* 拦截器的拦截方法,主要作用是失败重连和重定向
*/
@Override
public Response intercept(Chain chain) throws IOException {
// 获取请求对象
Request request = chain.request();
/**
* 实例化一个StreamAllocation对象,是一个管理类,字面理解是分配流,分配与服务器数据传输的流
* 是用来建立HTTP请求所需的网络设施组件,比如说HttpCodec(跟服务端进行数据传输的流 HttpStream)、连接服务器的RealConnection等
* 它还提供了调用RealConnection的connect()方法与服务器建立连接的方法,提供了断开连接的方法release(),提供了对路由的判断等等
* 在这个拦截器里没有用到,真正使用的地方是在ConnectInterceptor
*/
streamAllocation = new StreamAllocation(
client.connectionPool(), createAddress(request.url()), callStackTrace);
// 重连次数(包括重定向次数)
int followUpCount = 0;
// 上一个重试得到的响应
Response priorResponse = null;
// 死循环
while (true) {
// 如果RealCall调用了cancel,即取消请求,那就释放资源,抛出异常结束请求
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
// 定义请求的响应
Response response = null;
// 是否释放连接,默认为true
boolean releaseConnection = true;
try {
// 调用下一个拦截器 即BridgeInterceptor;进行网络连接,获取response
response = ((RealInterceptorChain) chain).proceed(request, streamAllocation, null, null);
// 如果没有发送异常,修改标志 不需要重试
releaseConnection = false;
} catch (RouteException e) {//后续拦截器抛出路由异常
// 出现路由连接异常,通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
// recover方法见下方
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
// 能恢复连接,修改标志 不释放连接
releaseConnection = false;
//回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
continue;
} catch (IOException e) {//后续拦截器在与服务器通信中抛出IO异常
// 判断该异常是否是连接关闭异常
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
//通过recover方法判断能否恢复连接,如果不能将抛出异常不再重试
if (!recover(e, requestSendStarted, request)) throw e;
//能恢复连接, 修改标志 不释放连接
releaseConnection = false;
//回到下一次循环 继续重试 除了finally代码外,下面的代码都不会执行
continue;
} finally {
// 如果releaseConnection为true,说明后续拦截器抛出了其它异常,那就释放所有资源,结束请求
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 走到这里,说明网络请求已经完成了,但是响应码并不一定是200
// 可能是其它异常的响应码或者重定向响应码
// 如果priorResponse 不等于null,说明前面已经完成了一次请求
// 那就通过上一次的response构建新的response,但是body为null.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 对response进行响应码的判断,如果需要进行重定向,那就获取新的Request
// followUpRequest方法见下方
Request followUp = followUpRequest(response);
// 如果为null,那就没必要重新请求,说明已经有了合适的Response,直接返回
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
//关闭,忽略任何已检查的异常
closeQuietly(response.body());
//检测重连次数是否超过20次,如果超过就抛出异常,避免消耗客户端太多资源
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
//如果该请求体被UnrepeatableRequestBody标记,则不可重试
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 判断重连前的Request与重新构建的Request是否有相同的连接,即host、port、scheme是否一致
if (!sameConnection(response, followUp.url())) {
// 如果不是相同的url连接,先释放之间的,再创建新的StreamAllocation
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 ,以便再次进入循环
request = followUp;
// 将重新构建的响应赋值给priorResponse,在下一次循环中使用
priorResponse = response;
// 本次循环结束,进入下一个循环,重新连接
}
}
RetryAndFollowUpInterceptor作用:
1、创建StreamAllocation对象
2、调用RealInterceptorChain.proceed(...)进行网络请求
3、根据异常结果或者响应结果判断是否要进行重新请求
4、调用下一个拦截器,对response进行处理,返回给上一个拦截器