OKhttp源码学习(四)—— RetryAndFollowUpInterceptor

RetryAndFollowUpInterceptor拦截器分析

源码地址:https://github.com/square/okhttp

前面已经对整体流程以及几个类做了了解,这里就开始对第一个拦截器RetryAndFollowUpInterceptor的分析了。

整体结构

首先通过一张图了解一下这个拦截器的整体结构:

整体结构

纵观整个类,方法分为了两部分:

  1. 供外部调用的:cancle相关的 , intercept等 ;
  2. 内部使用的,主要服务于intercept方法。

那么接下来我们就从两部分对这个类进行分析:

外部调用方法


//进行取消连接的操作
  public void cancel() {
    canceled = true;
  //通过StreamAllocation进行cancle操作
    StreamAllocation streamAllocation = this.streamAllocation;
    if (streamAllocation != null) streamAllocation.cancel();
  }

//获取cancle状态
  public boolean isCanceled() {
    return canceled;
  }

//设置调用堆栈跟踪,在创建StreamAllocation对象的时候作为参数传入
  public void setCallStackTrace(Object callStackTrace) {
    this.callStackTrace = callStackTrace;
  }

//获取StreamAllocation,一个流分配的类,处理连接,数据流,Call请求的关系
  public StreamAllocation streamAllocation() {
    return streamAllocation;
  }

这些方法提供给外部调用,你跟踪一下,会发现其实都是给RealCall调用使用的,而Cancle相关的方法,RealCall 就直接提供给外部使用了。也就是我们取消一个连接的操作。而另外两个方法,则都是提供给内部使用。

这个几个方法中,你会发现,大部分都与这个StreamAllocation类有关。这个类到底是什么?到底做了什么操作?在后面还会用到吗?带着这些疑问,在这一节之后再对这个类进行学习分析。

intercept方法

每个拦截器,最重要的方法就是这个intercept方法了。
而对于RetryAndFollowUpInterceptor这个拦截器都做了些什么?

  1. 新建StreamAllocation类,传入okhttpClicent中创建的连接池,传入通过url创建Address类,传入callStackTrace,调用堆栈跟踪。

  2. 开启一个while循环;

  3. 判读是否用户已经cancle,是-抛出异常,否-继续走下去;

  4. 通过RealInterceptorChain调用下一个拦截器(request, streamAllocation),并等待下一个拦截器返回结果。

  5. 获取返回结果做两个捕获异常,处理后面步骤抛出的异常, 判断是否继续请求。

  6. 判读结果是否符合要求,返回或者进行重定向。

  7. 关闭响应结果

  8. 判断重定向数目是否超过 MAX_FOLLOW_UPS(20)

  9. 判断重新连接是否问相同的连接,否-新建,是-释放。

  10. 循环2以后的步骤。

源码:

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

   //1. 新建StreamAllocation类
    streamAllocation = new StreamAllocation(
        client.connectionPool(), createAddress(request.url()), callStackTrace);

    int followUpCount = 0;
    Response priorResponse = null;
   //2.开启一个while循环
    while (true) {
   //3.cancle判读
      if (canceled) {
        streamAllocation.release();
        throw new IOException("Canceled");
      }

      Response response = null;
      boolean releaseConnection = true;
      try {
       //4.调用下一个拦截器
        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.
       //5.判断是否继续请求
        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.
      if (priorResponse != null) {
        response = response.newBuilder()
            .priorResponse(priorResponse.newBuilder()
                    .body(null)
                    .build())
            .build();
      }

     //6.判断是否重定向或者超时重试
      Request followUp = followUpRequest(response);

      if (followUp == null) {
        if (!forWebSocket) {
          streamAllocation.release();
        }
        return response;
      }

      // 7. 关闭响应结果
      closeQuietly(response.body());

     // 8.判断是否重定向数目
      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;
    }
  }

内部调用方法

提供内部的方法都是服务于interceptor(),有三个:

  1. createAddress,通过url的host,port, dns 以及一系列ohHttpClient的协议连接参数,简历一个Address类。而这个Address对象是提供给StreamAllocation,建立连接使用的。

  2. recover, 连接异常判断,是否继续。(含isRecoverable)

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

    //调用层面,是否禁止了重连重试
    // The application layer has forbidden retries.
    if (!client.retryOnConnectionFailure()) return false;

    //请求的Request本身出错,不能继续再连接
    // We can't send the request body again.
    if (requestSendStarted && userRequest.body() instanceof UnrepeatableRequestBody) return false;
   
    // 是否可以恢复的,主要判断了,是否协议异常,中断异常,SSL的握手异常,SSL通道未许可异常。
    // 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;
  }
  1. followUpRequest, 通过结果的http code码,来判断是否可以重定向,可以正常重定向会返回对应的Request,不然就返回null。

总结:
RetryAndFollowUpInterceptor 的主要做了三件事:

  1. 初始化了连接的对象(StreamAllocation,但是比没有真正建立连接,只是初始化了对象)(前置拦截);

  2. 通过RealInterceptorChain,再调用下一个拦截器;

  3. 收到结果之后,做异常处理,判断是否重连或者重定向,或者返回结果。(后置拦截)

作为第一个拦截器,功能也是相对比较简单的。留下一个疑问就是StreamAllocation 这个类,这个类的学习在以后会专门提出来。

系列:
OKhttp源码学习(一)—— 基本请求流程
OKhttp源码学习(二)—— OkHttpClient
OKhttp源码学习(三)—— Request, RealCall
OKhttp源码学习(五)—— BridgeInterceptor
OKhttp源码学习(六)—— CacheInterceptor拦截器
OKhttp源码学习(七)—— ConnectInterceptor拦截器
OKhttp源码学习(八)——CallServerInterceptor拦截器
OKhttp源码学习(九)—— 任务管理(Dispatcher)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容