OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor

目录

系列文章:

相关文章:

RetryAndFollowUpInterceptor:重试重定向拦截器

RetryAndFollowUpInterceptor.intercept

RetryAndFollowUpInterceptor作用:


系列文章:

OKHttp开源框架学习一:同步请求总结

OKHttp开源框架学习二:异步请求总结

OKHttp开源框架学习三:任务调度核心类Dispatcher

OKHttp开源框架学习四:拦截器

OKHttp开源框架学习五:拦截器之RetryAndFollowUpInterceptor

OKHttp开源框架学习六:拦截器之BridgeInterceptor

OKHttp开源框架学习七:缓存策略源码分析

OKHttp开源框架学习八:拦截器之CacheInterceptor

OKHttp开源框架学习九:拦截器之ConnectInterceptor

OKHttp开源框架学习十:ConnectionPool连接池

OKHttp开源框架学习十一:拦截器之CallServerInterceptor

Okhttp总结

相关文章:

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进行处理,返回给上一个拦截器

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值