OkHttp深入理解(2)RetryAndFollowUpInterceptor

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的逻辑。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值