这篇文章,对okhttp的另一个非常重要的概念-拦截器(Interceptor)进行源码分析。或许,有的朋友就要说了,前面两篇文章分别总结了两种请求的源码以及Dispatcher的源码,为什么突然扯到Interceptor了呢?接下来,我们先了解一下,拦截器是什么。
一、Interceptor是什么
Interceptor翻译过来就叫拦截器。引用官网的解释:拦截器是okhttp的一种强大的机制。他可以实现网络监听、请求以及重写响应、请求失败重试等功能。
二、从同步请求说起
我们前面的源码分析,在讲同步请求的时候,我们对response的获取一笔带过,并没有深入去追究response是如何获取到的。我们先把同步请求执行的相关源码贴一下:
@Override public Response execute() throws IOException {
synchronized (this) {
if (executed) throw new IllegalStateException("Already Executed");
executed = true;
}
captureCallStackTrace();
timeout.enter();
eventListener.callStart(this);
try {
client.dispatcher().executed(this);
Response result = getResponseWithInterceptorChain();
if (result == null) throw new IOException("Canceled");
return result;
} catch (IOException e) {
e = timeoutExit(e);
eventListener.callFailed(this, e);
throw e;
} finally {
client.dispatcher().finished(this);
}
}
上面的代码,我们在同步请求源码解析中,大部分都做了解析。但是,有很重要的一行,我一笔带过。也就是:
Response result = getResponseWithInterceptorChain();
这个方法,乍一看很简单,就是在发起请求后,获取到请求的结果。但实际上真的那么简单吗?当然不是。我们通过同步请求和异步请求的执行可以看出,请求的过程特别是异步请求还是比较麻烦的。因此。请求结果的获取,也并不是一行代码就轻松得到的,而是通过拦截器链一步一步执行获得的。
三、拦截器
首先,我们看一下okhttp的拦截器链:
我们接着上面的方法,跟进去源码。我们看到,代码量不多,就十几行代码。但是,我们看到了这中间有很多个我们从来没见到过的类,各种XXXInterceptor。这其实就是okhttp的拦截器链:
Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
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);
}
简单说一下这个方法,其实,如果我们忽视掉这些拦截器的功能,单纯看代码的话,还是比较好理解的。首先,我们往一个拦截器的List中添加了很多个拦截器。然后,我们通过这个拦截器的List等参数去创建了一个拦截器链,注意,这个地方传入的index为0,也就是第一个拦截器。最后,这个拦截器链调用proceed方法。可见,核心的代码实现是在proceed方法中,我们跟进去看一下做了什么。
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
calls++;
// If we already have a stream, confirm that the incoming request will use it.
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");
}
// If we already have a stream, confirm that this is the only call to chain.proceed().
if (this.httpCodec != null && calls > 1) {
throw new IllegalStateException("network interceptor " + interceptors.get(index - 1)
+ " must call proceed() exactly once");
}
// Call the next interceptor in the chain.
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");
}
// Confirm that the intercepted response isn't null.
if (response == null) {
throw new NullPointerException("interceptor " + interceptor + " returned null");
}
if (response.body() == null) {
throw new IllegalStateException(
"interceptor " + interceptor + " returned a response with no body");
}
return response;
}
我们看一下几处核心代码,首先是下面这一段。我们看到,跟我们在进入proceed方法前一样,创建了一个拦截器链,但是这里的index是index+1,也就是拦截器链中当前index的下一个拦截器,这其实就是拦截器链的实现。然后,获取当前index的拦截器,去执行下一个拦截器,代码如下:
// Call the next interceptor in the chain.
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);
我们看一下intercept方法的实现。Interceptor是一个接口,我们看一下实现类的方法实现。在上面我们说过,拦截器有很多个,他们是链式调用的。第一个拦截器,其实就是RetryAndFollowUpInterceptor,它的intercept方法具体实现在这里,我们先不去关注具体实现,我们只看下面这么一行代码:
response = realChain.proceed(request, streamAllocation, null, null
是的,每个拦截器的intercept方法,其实又调用了proceed方法,而proceed方法,又去调用下一个拦截器的intercept方法。接下来,我们简单看一下每一种拦截器的作用。
1、RetryAndFollowUpInterceptor
这个中文名字,我们叫他重试和跟踪拦截器。他的主要作用就是发起网络请求,如果该请求需要重定向,那么进行重定向请求,并且在请求失败的情况下,发起重试。
(1)创建网络请求
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
(2)重定向
// 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;
}
(3)重试
重试次数不是无限的,在这里设置的是20次。
private static final int MAX_FOLLOW_UPS = 20;
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
2、BridgeInterceptor
中文叫桥接拦截器,他的主要作用是添加头部信息(例如User-Agent,,Host,Cookie等),使请求成为一个标准的http请求并发起请求,并且处理返回给我们的response,使之转化为用户可用的response。
(1)添加头部信息
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", Version.userAgent());
}
(2)处理response
if (transparentGzip
&& "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding"))
&& HttpHeaders.hasBody(networkResponse)) {
GzipSource responseBody = new GzipSource(networkResponse.body().source());
Headers strippedHeaders = networkResponse.headers().newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build();
responseBuilder.headers(strippedHeaders);
String contentType = networkResponse.header("Content-Type");
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
3、CacheInterceptor
中文叫缓存拦截器。试想一下,是不是我们每次发起网络请求都真正的去服务器请求呢?当然不是,Okhttp也有自己的缓存策略。当我们发起请求的时候,会去判断是否有缓存,如果有缓存则直接从缓存中拿这个请求。如果没有缓存,那么去使用网络发起请求。这些缓存功能的实现,就是缓存拦截器来做的。
4、ConnectInterceptor
网络连接拦截器,在经过上面一系列拦截器的处理后(是否重试和重定向,拼接头部信息,是否使用缓存等),终于到了和服务器连接的时刻,网络连接拦截器就是和服务器进行连接的拦截器。我们看一下源码:
@Override public Response intercept(Chain chain) throws IOException {
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Request request = realChain.request();
StreamAllocation streamAllocation = realChain.streamAllocation();
// We need the network to satisfy this request. Possibly for validating a conditional GET.
boolean doExtensiveHealthChecks = !request.method().equals("GET");
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);
RealConnection connection = streamAllocation.connection();
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
网络连接拦截器的工作流程如下:
(1)获取前面的拦截器传过来的StreamAllocation
(2)把StreamAllocation,httpCodec,RealConnection等等传递给后面的拦截器。
HttpCodec是一个非常关键的类,看到Codec,我们能猜出它的作用,一般是编解码相关的类。HttpCodec就是负责对http请求和响应进行编解码的类。
RealConnection也是一个非常关键的类,他是真正的网络连接类,它维护了一个网络连接池ConnectionPool。
5、CallServerInterceptor
访问服务端拦截器。这个拦截器从名字可以看出,它是真正向服务端发起请求的拦截器。这里面都是和网络请求相关的一些方法和特殊情况的处理,最后会返回我们所需要的response。
最后,简单总结一下,okhttp有多个拦截器,这些拦截器一起构成了拦截器链,每一个链接器在执行自己功能的同时,会调用下一个拦截器,同时对response进行处理,返回上一个拦截器,这样完成了拦截器的链式调用。