OkHttp 源码分析(三)——RetryAndFollowUpInterceptor

本文深入分析了OkHttp的RetryAndFollowUpInterceptor,它负责网络失败时的重试和重定向。在拦截器的intercept方法中,通过while循环实现重连。当遇到异常或特定条件时,RetryAndFollowUpInterceptor决定是否继续尝试连接,管理streamAllocation,处理连接的释放和异常恢复。最后,根据响应码和异常情况决定是否结束请求并返回响应。
摘要由CSDN通过智能技术生成

 

一、前言

上一篇文章中,简单分析了拦截器和拦截链的调用过程,简单概括起来就是拦截器的intercept方法中,对Request进行处理,并将处理后的Request传递给下一个拦截器,获取下一个拦截器返回的Response响应后,再对Response进行处理,并返回给上一级拦截器,而Okhttp中真正的网络请求,就是在这一系列的拦截器中完成的。而Okhttp框架提供的内部拦截器是从RetryAndFollowUpInterceptor拦截器开始执行的,所以我们先来分析他的源码。

 

二、RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor,顾名思义,就是重试重定向拦截器,从名字就可以看出它的主要作用就是进行网络失败重连的,下面我们从它的intercept方法来分析它是如何进行重连的。

public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();

    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
    while (true) {
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
        response = ((RealInterceptorChain) chain).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.streamFailed(null);
          streamAllocation.release();
        }
      }

      // Attach the prior response if it exists. Such responses never have a body.
      //前一个重试得到的Respons
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }
      // followUpRequest方法的主要作用就是为新的重试Request添加验证头等内容
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          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()), callStackTrace);
      } 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;
    }
  }

在方法开始首先拿到上一级传进来的Request对象,然后就新建了一个streamAllocation对象,这个对象主要是用于管理客户端和服务端之间的连接,拿到I\O流(基于Okio),同时管理着连接池、请求资源的释放等操作。这个对象在RetryAndFollowUpInterceptor中并未用到,我们在后面会做详细分析。接着往下走,进入一个while循环,RetryAndFollowUpInterceptor就是通过这个while循环,当网络请求失败并满足一定重连条件时,进入循环进行重连。

接着,判断用户是否取消了该次网络请求,若是取消了请求,streamAllocation就释放连接,并抛出异常,接下来的try/catch代码块中的调用拦截链proceed方法,我们在上一节分析过了,就是把Request对象和刚刚创建的streamAllocation对象传递给下一个拦截器进行处理请求,并得到下一个拦截器的响应,在这个过程中,如果捕捉到异常,将通过recover方法判断是否能够继续连接:

 private boolean recover(IOException e, boolean requestSendStarted, Request userRequest) {
    streamAllocation.streamFailed(e);

    // The application layer has forbidden retries.
    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;
  }

出现以下几种情况时,将取消重连,抛出异常: 

           3.1、协议错误(ProtocolException)
           3.2、超时(SocketTimeoutException && !requestSendStarted
           3.3、SSL握手错误(SSLHandshakeException && CertificateException)
           3.4、certificate pinning错误(SSLPeerUnverifiedException)

     4、没有更多路由线路选择

  1. 应用层配置禁止重连
  2. Request出错,无法继续使用;
  3. 通过isRecoverable判断是否可以重连,出现一下几种异常,无法重连:

 回到try/catch代码块,不管请求成功与否,都会执行finally里的代码:

    finally {
        // We're throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          streamAllocation.streamFailed(null);
          streamAllocation.release();
        }
      }

通过releaseConnection判断是否有为捕捉到的异常,当有未捕捉到的异常时,则释放 streamAllocation。接下来,得到Response后,根据相应码,处理相应头,比如重定向、超时等,如果一切正常,则停止循环,返回Response。

在前面提到,当在连接过程中捕获到异常或者需要重定向时,会继续while循环,然而这个循环需要满足一定的条件,如果不满足停止该次请求,并抛出异常:

     //重连次数超过最大限制数,抛出异常
     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());
      }
      // 判断followUp请求是否和当前请求相同,如果相同则复用,否则销毁streamAllocation,
      // 并创建新的streamAllocation(即为重定向请求)
      if (!sameConnection(response, followUp.url())) {
        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?");
      }

三、总结

RetryAndFollowUpInterceptor是Okhttp内部的第一个拦截器,也是贯穿整个请求过程的拦截器,它主要做的工作可以总结为以下几点:

  1. 初始化streamAllocation;

  2. 调用下一个拦截链处理网络请求,拿到响应结果;

  3. 根据异常结果或响应结果判断是否需要进行重新请求

  4. 返回Response响应

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值