美文网首页
OkHttp3拦截链——灵巧的小伙子

OkHttp3拦截链——灵巧的小伙子

作者: MxsQ | 来源:发表于2019-02-06 09:58 被阅读0次

    前言

    在使用的网络框架中,一般都会涉及拦截链的运用,因为拦截链带来的好处显而易见。比如,通过拦截链可以对Request和Response进行各类的工作,比如Header的预处理、Host的装配、Log的采集、Cache的运用、Mock的调度、Connection的复用等,都可以与拦截链相关并达到解藕、清晰的相互协作的状态。

    本文以OkHttp3为引子,看一看拦截链的实现原理。

    你可能需要知道OkHttp3原理,点我

    怎么解释拦截链

    在正式进入OkHttp3见识拦截链如何实现前,先简要了解一下什么是拦截链。

    拦截链实际上是一种责任链,链上的每一节点向下或由自身提供结果,向上汇报结果。由多个此类节点组成的完成某种任务的链,就是责任链。见图


    拦截链简要.png

    从图上看,由1234组成了一条链,节点层级关系依次排列。当外部有需求交由拦截链处理时,会从链上拿到结果。

    每个节点负责提供自己期望的结果,不管是从下层还是自己提供呢。在此期间,能针对场景的不同,每个节点拥有自己的策略执行不同的处理。需要注意的是,这是一种短路模型,获取结果的过程可能经历了1234321,也有可能仅仅经历了121。对于外部来说,这些都是透明的。

    原理

    如果看过OkHttp3的运行原理,可以知道,Request拿到的Response从RealCall.getResponseWithInterceptorChain()拿到,在此函数里,便是通过拦截链获取了合适的结果。

    在进入此函数前,插播一则广告

    // 拦截器
    public interface Interceptor {
      // 拦截并处理,返回Response
      Response intercept(Chain chain) throws IOException;
    
      // 拦截链,管理拦截器如何调度
      interface Chain {
        // 获取请求
        Request request();
        
        // 进行调度
        Response proceed(Request request) throws IOException;
        
        ......
      }
    }
    

    上面展示的是OkHttp3拦截器和拦截链的基本要求。以OkHttp3来说,其实现了各类拦截器处理不同的问题场景,这里其实可以不去细看拦截器如何实现以及做了什么样的工作,将目光聚焦于整个拦截链式如何协调工作的。

    具体代码如下

      Response getResponseWithInterceptorChain() throws IOException {
        // 构建拦截器
        List<Interceptor> interceptors = new ArrayList<>();
        interceptors.addAll(client.interceptors());
        interceptors.add(retryAndFollowUpInterceptor);
        interceptors.add(new BridgeInterceptor(client.cookieJar()));
        interceptors.add(new CacheInterceptor(client.internalCache()));
        interceptors.add(new ConnectInterceptor(client));
        if (!forWebSocket) {
          interceptors.addAll(client.networkInterceptors());
        }
        interceptors.add(new CallServerInterceptor(forWebSocket));
        
        // 构造拦截链    
        Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
            originalRequest, this, eventListener, client.connectTimeoutMillis(),
            client.readTimeoutMillis(), client.writeTimeoutMillis());
        
        // 拦截链处理请求
        return chain.proceed(originalRequest);
      }
    

    如果粗略的分类,暂且将拦截器分成两种,一种是框架本身所需的拦截器,以维持框架的运转,如BridgeInterceptor、CacheInterceptor、ConnectInterceptor等;另一种是框架的使用者提供的拦截器,处理自身的Business。并且可以看到,使用者添加的拦截器会位于拦截链的上层而不是下层,因此处于拦截链下层的拦截器,实际上处理的是更核心更基本的事务。

    当前场景下,OkHttp3所提供的Interceptor.Chain为RealInterceptorChain。

    public final class RealInterceptorChain implements Interceptor.Chain {
      private final List<Interceptor> interceptors;
      private final Request request;
      private final int index;
      private final Call call;
      ......
    
      public RealInterceptorChain(List<Interceptor> interceptors, StreamAllocation streamAllocation,
          HttpCodec httpCodec, RealConnection connection, int index, Request request, Call call,
          EventListener eventListener, int connectTimeout, int readTimeout, int writeTimeout) {
        this.interceptors = interceptors;
        this.index = index;
        this.request = request;
        this.call = call;
        ......
      }
    }
    

    RealInterceptorChain的构造函数截取了部分实例,包括存储拦截器的List,请求、当前索引和RealCall等。

    调度代码如下

     public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
          RealConnection connection) throws IOException {
        // 防越界
        if (index >= interceptors.size()) throw new AssertionError();
    
        calls++;
    
        // 确认如果有stream,即将到来的Request会使用它
        if (this.httpCodec != null && !this.connection.supportsUrl(request.url())) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must retain the same host and port");
        }
    
        // 确认如果有stream,确保此stream仅被proceed()一次
        if (this.httpCodec != null && calls > 1) {
          throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
              + " must call proceed() exactly once");
        }
    
        // 获取拦截链上的下一个节点,注意 "index+1" 这个操作
        RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
            connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
            writeTimeout);
        // 获取当前拦截器
        Interceptor interceptor = interceptors.get(index);
        // 当前拦截器进行处理
        Response response = interceptor.intercept(next);
    
        // Confirm that the next interceptor made its required call to chain.proceed().
        if (httpCodec != null && index + 1 < interceptors.size() && next.calls != 1) {
          throw new IllegalStateException("network interceptor " + interceptor
              + " must call proceed() exactly once");
        }
    
        // 确保结果不为空
        if (response == null) {
          throw new NullPointerException("interceptor " + interceptor + " returned null");
        }
        
        // 确保响应body不为空
        if (response.body() == null) {
          throw new IllegalStateException(
              "interceptor " + interceptor + " returned a response with no body");
        }
        
        // 返回结果
        return response;
      }
    

    代码中比较难理解的地方是与类型为RealInterceptorChain的next的操作。

    首先,调度是以Chain来进行的,在Chain构造时,既保存了Request,也保存了Index。注意,所说的Request是从上层来的,也是指当前节点所处理的Request,是它的上一个节点给的,而这个Request可能是经过改动的,是具体情况而定。Index则是指当前处理Request的拦截器的位置。因为每次需要向下层获取结果时,即会调用Chain.proceed()。此操作都会构造出Chain对象,所有Chain对象共享一个List<Interceptor>,拿到了Response则先经由拦截器处理,随后向上返回。

    过程如图


    拦截链运行 (1).png

    通过图,能理解上面代码是怎样运转的,需要理解的点有

    • ChainX 共享拦截链
    • 通过Request等必要构造ChainX,ChainX知道此时应由拦截链上的哪个拦截器处理Request
    • Response 是由拦截器获取,由ChainX向上一层返回的
    • 拦截器向下层获取数据,通过Chain.proceed间接构造ChainX构造,比如0向1获取Response时,通过Chain.proceed()构造出了Chain1,由Chain1去交涉
    • 短路模型,比如如之前说可以经过如010的过程,这种情况下拦截器1不需要Chain2向下获取数据,即不需要调用Chain.proceed(), 自身提供并返回合适的Response即可

    总结

    以OkHttp3为例的拦截链的实现,其短路模型和Chain调度设计是非常灵活的,优势如

    • 拦截器不需要知道下层是谁,即不关心自身所处位置。也因此拦截链的节点层级需要逻辑维护
    • 拦截器关心Request、Response,并由合适的时机对二者进行处理,执行不同策略。不关心他们从哪来,关心他们是否符合自己的期望
    • 调度由Chain控制,拦截器节点仅需通过Chain暴露出的API如Chain.proceed()即可完成向下层节点获取Response。正因如此,整个过程不仅仅可以是单次的,也可以是折返多次的,即可以有不同的条件控制多次向下层获取Reponse,直到满足期望。比如有链接链0123,可以经历如0123-21-23-210 的过程,甚至更复杂。个人认为理解这一点很重要,可仔细体会。

    下一篇:以拦截链为基础的Mock方案

    相关文章

      网友评论

          本文标题:OkHttp3拦截链——灵巧的小伙子

          本文链接:https://www.haomeiwen.com/subject/hyibsqtx.html