前言
拦截器是 okhttp 的精髓,okhttp 的网络请求过程其实就是通过一条拦截器链来完成的,这体现了责任链模式。本文将分析 okhttp 拦截链中各拦截器的作用,代码基于 okhttp-3.10.0。
各拦截器的分析
在上一篇文章说过,无论是同步请求还是异步请求,最后都是调用 getResponseWithInterceptorChain 方法来获得 Response,该方法实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 Response getResponseWithInterceptorChain() throws IOException {
// Build a full stack of interceptors.
List<Interceptor> interceptors = new ArrayList<>();
//在配置OkHttpClient时设置的interceptors
interceptors.addAll(client.interceptors());
//RetryAndFollowUpInterceptor:负责失败重试和重定向
interceptors.add(retryAndFollowUpInterceptor);
//BridgeInterceptor:负责把用户请求转换为发送到服务器的请求,
//并把服务器的响应转化为用户需要的响应
interceptors.add(new BridgeInterceptor(client.cookieJar()));
//CacheInterceptor:负责读取缓存、更新缓存
interceptors.add(new CacheInterceptor(client.internalCache()));
//ConnectInterceptor:负责和服务器建立连接
interceptors.add(new ConnectInterceptor(client));
//在配置OkHttpClient时设置的networkInterceptors
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
//CallServerInterceptor:负责向服务器发送数据,从服务器读取响应数据
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);
}
用户自定义的拦截器先不说,okhttp本身调用的拦截器依次是:
- RetryAndFollowUpInterceptor:负责失败重试和重定向
- BridgeInterceptor:负责把用户请求转换为发送到服务器的请求,并把服务器的响应转化为用户需要的响应
- CacheInterceptor:负责读取缓存、更新缓存
- ConnectInterceptor:负责和服务器建立连接
- CallServerInterceptor:负责向服务器发送数据,从服务器读取响应数据
下面分析这些拦截器的各自负责的功能,主要是看它们的intercept方法:
RetryAndFollowUpInterceptor
首先是 RetryAndFollowUpInterceptor,它负责失败重试和重定向。
它的intercept方法如下:
RetryAndFollowUpInterceptor#intercept
1 | public Response intercept(Chain chain) throws IOException { |
可以看到,如果任务没有被取消,那么将会调用下一拦截器进行后续操作。
如果后续请求过程出现问题,okhttp 会通过recover 方法判断是否要重新尝试执行请求,该方法定义如下:
RetryAndFollowUpInterceptor#recover
1 | private boolean recover(IOException e, StreamAllocation streamAllocation, |
可以看到,在四种情况下,不再重写请求:
- 用户设置了拒绝重连
用户可以在配置 OkHttpClient 时设置该选项,代码如下:1
2
3OkHttpClient okHttpClient = new OkHttpClient.Builder()
.retryOnConnectionFailure(false) //拒绝重连,默认情况下该值为true
.build();
- 请求已发送,并且该请求为不能重新发送的类型
- 发生的是再次重试也不能解决的问题,例如协议问题、证书问题
- 没有更多的路由
小结
下面小结一下 RetryAndFollowUpInterceptor 的执行步骤:
- 先看任务是否被取消,被取消了就释放资源,不再执行请求。
- 调用下一拦截器执行后续请求,如果请求出现问题就要判断是否要重新执行请求,不重新执行的话就释放资源并抛出异常。
- 请求成功的话会根据响应码判断是否需要重定向,不需要重定向的话就返回 Response。需要重定向的话就销毁旧连接并创建新连接,进行新一轮循环。最多可重定向20次。
BridgeInterceptor
BridgeInterceptor 负责把用户请求转换为发送到服务器的请求,并把服务器的响应转化为用户需要的响应。
它的 intercept 方法如下:
BridgeInterceptor#intercept
1 |
|
小结
BridgeInterceptor 的执行步骤如下:
- 将用户的 Request 构造为发送给服务器的 Reuquest,该过程会添加各种请求报头(包括 Host、Connection、cookie 等)
- 构造完成后,将新的 Request 交给下一拦截器来处理
- 得到服务器的 Response 后,先保存 cookies,接着将服务器的 Response 转换为用户需要的 Response 并返回(如果使用了 gzip 压缩并且服务器的 Response 有 body 的话,还要给用户的 Response 设置相应 body)
CacheInterceptor
CacheInterceptor 负责读取缓存、更新缓存
它的 intercept 方法如下:
CacheInterceptor#intercept
1 |
|
小结
CacheInterceptor 的执行步骤如下:
- 从 Cache 中得到 Request 对应的缓存,默认没有设置 Cache,需要用户自己配置
- 得到缓存策略
- 如果通过缓存策略没有得到缓存,则关闭缓存
- 如果缓存策略设置了禁用网络,看得到的缓存是否为空,如果缓存为空,则构建一个返回码为 504 的 Response,说明返回失败。如果缓存不为空,则返回缓存
- 如果可以使用网络,就交给下一拦截器执行请求,执行请求的过程发生异常,及时关闭缓存,并抛出异常,让上一拦截器处理。
- 当缓存和网络返回的 Response 同时存在时,如果返回的状态码为 304(说明服务器的文件未更新,可以使用缓存),则返回缓存。否则更新缓存,并返回网络请求后的 Response
ConnectInterceptor
ConnectInterceptor 负责和服务器建立连接
它的 intercept 方法如下:
ConnectInterceptor#intercept
1 |
|
该方法的步骤如下:
- 找到一个可用的 RealConnection, 再利用这个 RealConnection 的输入输出(BufferSource 和 BufferSink)创建 HttpCodec。( HttpCodec 有两个实现:Http1Codec 和 Http2Codec,分别对应 HTTP/1.1 和 HTTP/2 版本)
- 调用下一拦截器进行后续请求操作。
CallServerInterceptor
CallServerInterceptor 负责向服务器发送数据,从服务器读取响应数据
它的 intercept 方法如下:
CallServerInterceptor#intercept
1 |
|
小结
CallServerInterceptor 的执行步骤如下:
- 向服务器写入请求头,如果请求头有 Expect: 100-continue,需要根据服务器返回的结果决定是否可以继续写入请求体。
- 得到响应头并构建带有响应头的 Response,接着为 Response 构建响应体并返回。
结尾
到此为止,5个拦截器的功能都分析完了,不得不说,这条拦截链的设计真的是太棒了。当然,文章有很多分析还停留在比较浅的地方,还有很多东西没有深入,例如缓存策略(CacheStrategy)的获取、创建 HttpCodec 的过程、和 okio 的结合等,有机会再深入吧。