Okhttp之RetryAndFollowUpInterceptor拦截器原理解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_15988951/article/details/85121361
如果研究过okhttp源码,应该知道okhttp的核心是拦截器,而拦截器所采用的设计模式是责任链设计,即每个拦截器只处理与自己相关的业务逻辑。

今天彻底分析Okhttp的核心拦截器RetryAndFollowUpInterceptor的原理解析:

这里先贴出RetryAndFollowUpInterceptor的核心伪代码,可以大体的看一遍,待下文一步一步带你解析。

拦截器的核心代码都在intercept(Chain chain )方法中,所以有必要彻底研究该方法是如何处理即可理解RetryAndFollowUpInterceptor的奥妙。

  @Override 
  public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    //...省略部分代码
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
    createAddress(request.url()), call, eventListener, callStackTrace);
      //...省略部分代码
    while (true) {
   
      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(), streamAllocation, false, request)) {
          throw e.getFirstConnectException();
        }
        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, streamAllocation, 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.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }


      Request followUp;
      try {
       //判断是否进行重新请求
        followUp = followUpRequest(response, streamAllocation.route());
      } catch (IOException e) {
        streamAllocation.release();
        throw e;
      }

      if (followUp == null) {
      //如果为空,则释放资源,不空则继续下一次请求
        streamAllocation.release();
        return response;
      }

      request = followUp;
      priorResponse = response;
    }
  }

1、先分析正常的一次网络请求的业务逻辑

1.1 实例化StreamAllocation,初始化一个Socket连接对象,获取到输入/输出流,从线程池中获取线程及拼接请求地址得到StreamAllocation对象
    StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
    createAddress(request.url()), call, eventListener, callStackTrace);
2、通过创建好的request和streamAllocation对象,去网络获取到response并传递给下一级拦截器
response = realChain.proceed(request, streamAllocation, null, null);

如果是正常的网络请求,RetryAndFollowUpInterceptor起作用的代码就这些。

2、回到最初的起点,看它的"真正"的作用

RetryAndFollowUpInterceptor的定义为:This interceptor recovers from failures and follows redirects as necessary 即网络请求失败后,在一些必要的条件下,会重新进行网络请求。

接下来,看看到底是如何重新进行网络请求?

2.1 首先可以看见它本身维护这一个死循环
  while (true) {
      try {
        response = realChain.proceed(request, streamAllocation, null, null);
        releaseConnection = false;
      } catch (RouteException e) {
         throw e;
     }catch(IOException e){
      //.....省略部分代码
         continue;
     }
}

会进行try…catch 如果出现了异常信息,可以看到,它本身会进行处理 RouteException、IOException 它会重新根据recover(…)方法,然后continue掉本次循环,然后继续下一次请求,即:

response = realChain.proceed(request, streamAllocation, null, null);

那么问题来了,假如说出现了异常,它岂不是一直重新进行请求网络,它什么时候跳出循环?

2.2 这里有两种情况进行跳出死循环
第一种:当发生RouteException时候,并且 recover为false的时候,这时候会跳出循环,然后抛出去异常,并回调callback.onFailure(Exception e),返回给UI层进行处理 ,假如说没有开数据流量的情况下,去请求网络,则会抛出该异常。
第二种:当发生IOException时候,recover为false的时候,则throw exception,中断死循环的操作,

3、以网络重定向进行分析,看它是如何进行重新请求网络

如果你对网络知识了解的话,正常一次请求会返回一个响应,但是重定向比较特殊,其实是客户端请求了2次网络,服务器返回了2次数据。

3.1 以访问百度为例,重定向实例,请求两次网络

百度的真正的地址为:https://www.baidu.com ,但是在地址栏中输入 http://www.baidu.com 也可以看到百度主页,但是只要看到百度主页,说明浏览器里面请求了https://www.baidu.com,这里就说明发生了重定向,通过打开开发者模式可以看见。如下图所示:
image.png
当按下回车的时候,地址栏变为自动变为https://www.baidu.com
image.png

3.2 在开发者模式中看到访问了两次www.baidu.com

第一次 则status code 为307 即表示是重定向,而且在返回的response Headers中Location字段中是重定向的地址,即真正的百度的地址。

第二次 则status code 为200 ,真正的网络请求,如下图所示:
第一次请求www.baidu.com

重定向请求网络.png

看到上面重定向以后,再来看看okhttp是如何进行处理这种重定向的请求

4、okhttp是如何实现重定向功能

知道了重定向返回的status code 为307,来研究okhttp是怎么实现,注意重定向并不会发生异常,而是执行如下方法 followUpRequest()判断是否要重新进行网络请求。

  try {
        followUp = followUpRequest(response, streamAllocation.route());
  } catch (IOException e) {
        streamAllocation.release();
        throw e;
  }

既然这里执行了followUpRequest(),那看看followUpRequest中的具体实现吧(伪核心代码)

  private Request followUpRequest(Response userResponse, Route route) throws IOException {
    if (userResponse == null) throw new IllegalStateException();
    int responseCode = userResponse.code();

    final String method = userResponse.request().method();
    switch (responseCode) {
     //... 省略部分代码...
      case HTTP_PERM_REDIRECT:
      case HTTP_TEMP_REDIRECT:
        if (!method.equals("GET") && !method.equals("HEAD")) {
          return null;
        }
      case HTTP_MULT_CHOICE:
      case HTTP_MOVED_PERM:
      case HTTP_MOVED_TEMP:
      case HTTP_SEE_OTHER:
        if (!client.followRedirects()) return null;

        String location = userResponse.header("Location");
        if (location == null) return null;
        HttpUrl url = userResponse.request().url().resolve(location);

        boolean sameScheme = url.scheme().equals(userResponse.request().url().scheme());
        Request.Builder requestBuilder = userResponse.request().newBuilder();
        if (HttpMethod.permitsRequestBody(method)) {
          final boolean maintainBody = HttpMethod.redirectsWithBody(method);
          if (HttpMethod.redirectsToGet(method)) {
            requestBuilder.method("GET", null);
          } else {
            RequestBody requestBody = maintainBody ? userResponse.request().body() : null;
            requestBuilder.method(method, requestBody);
          }
          if (!maintainBody) {
            requestBuilder.removeHeader("Transfer-Encoding");
            requestBuilder.removeHeader("Content-Length");
            requestBuilder.removeHeader("Content-Type");
          }
        
        return requestBuilder.url(url).build();

      default:
        return null;
    }
  }

该方法会取到状态码,然后进行switch判断,当status code为307即HTTP_TEMP_REDIRECT时,okhttp会从相应体中,获取Location值(重定向地址)从源码中可以看出,还有好多种状态码的判断。这里只介绍重定向307的情况:

 String location = userResponse.header("Location");

然后重新创建和拼接请求新的Request, 即:

 HttpUrl url = userResponse.request().url().resolve(location);

然后将原请求的相关Header信息进行拼装,最后创建一个新的Request返回回去,即:

 HttpUrl url = userResponse.request().url().resolve(location);
 Request.Builder requestBuilder = userResponse.request().newBuilder();
 requestBuilder.url(url).build();

重新将拼接好的request对象,重新进行网络请求。即:

while(true) {
 response = realChain.proceed(request, streamAllocation, null, null);
}

知道请求网络正常或者有特殊的异常的时候,结束掉本次网络请求。
到这里,RetryAndFollowUpInterceptor的核心代码就分析完了,相信你也有一定的收获吧!期待你的喜欢哦 _

猜你喜欢

转载自blog.csdn.net/qq_15988951/article/details/85121361