前言
在使用的网络框架中,一般都会涉及拦截链的运用,因为拦截链带来的好处显而易见。比如,通过拦截链可以对Request和Response进行各类的工作,比如Header的预处理、Host的装配、Log的采集、Cache的运用、Mock的调度、Connection的复用等,都可以与拦截链相关并达到解藕、清晰的相互协作的状态。
本文以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 的过程,甚至更复杂。个人认为理解这一点很重要,可仔细体会。
网友评论