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

2019-02-06  本文已影响0人  MxsQ

前言

在使用的网络框架中,一般都会涉及拦截链的运用,因为拦截链带来的好处显而易见。比如,通过拦截链可以对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

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

总结

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


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

上一篇 下一篇

猜你喜欢

热点阅读