为了账号安全,请及时绑定邮箱和手机立即绑定

Okhttp之RetryAndFollowUpInterceptor拦截器解析

标签:
Android
如果研究过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,这里就说明发生了重定向,通过打开开发者模式可以看见。如下图所示:

webp

image.png


当按下回车的时候,地址栏变为自动变为https://www.baidu.com

webp

image.png


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

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

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

webp

第一次请求www.baidu.com


webp

重定向请求网络.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;
    }
  }



作者:OneXzgj
链接:https://www.jianshu.com/p/bd1ed7b5dc9b


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消