在 OkHttp 中,拦截器(Interceptor)是一种强大的机制,用于在发送请求和接收响应的过程中,对请求和响应进行拦截、处理和修改。拦截器可以在网络请求的不同阶段进行操作,允许开发者对请求进行修改、记录日志、添加头部信息、处理重定向、处理错误等。
OkHttp 的拦截器是基于责任链模式设计的,每个拦截器都可以对请求进行拦截并对其进行处理,然后将请求传递给下一个拦截器,形成一个拦截器链。这样,在发送请求和接收响应的过程中,请求会依次经过拦截器链中的每个拦截器,每个拦截器都有机会对请求进行操作。
用 Okhttp 执行网络后,会调用到 getResponseWithInterceptorChain() 方法:
package okhttp3;
final class RealCall implements Call {
// ....
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)); // 请求服务拦截器
// ....
return chain.proceed(originalRequest);
}
}
所有的拦截器都存储在一个 List 集合中,在之后会进行顺序调用,顺序:
自定义拦截器 -> 重试和重定向拦截器 -> 桥接拦截器 -> 缓存拦截器 -> 连接拦截器 -> 请求服务拦截器
1、重试和重定向拦截器:
package okhttp3.internal.http;
public final class RetryAndFollowUpInterceptor implements Interceptor {
// ....
@Override public Response intercept(Chain chain) throws IOException {
// ....
while (true) {
Response response;
try {
response = realChain.proceed(request, streamAllocation, null, null); // 真正干活的
} catch (RouteException e) {
// recover 这个方法判断是否重试
if (!recover(e.getLastConnectException(), false, request)) {
throw e.getLastConnectException();
}
continue;
} catch (IOException e) {
// recover 这个方法判断是否重试
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, requestSendStarted, request)) throw e;
continue;
} finally {
// 释放
}
// 根据返回的结果判断是否需要重定向
Request followUp = followUpRequest(response);
if (followUp == null) { // 不需要重定向,直接返回
return response;
}
// ...
}
}
不难看出,当中有一个 while 死循环,先执行 RealInterceptorChain.proceed(),若出现了异常再进行重试(经过了 recover 方法中一系列的判断后满足重试条件后),正常请求后对 Response 进行判断是否需要重定向
那这个 RealInterceptorChain.proceed() 做了什么呢:
package okhttp3.internal.http;
/**
* 一个具体的拦截器链,它包含整个拦截器链:所有应用程序拦截器,OkHttp核心,所有网络拦截器。
* 最后是网络调用者。
*/
public final class RealInterceptorChain implements Interceptor.Chain {
// ....
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
// ....
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout); // 组装chain
Interceptor interceptor = interceptors.get(index); // 获取下一个拦截器
Response response = interceptor.intercept(next); // 调用下一个拦截器
// ....
return response;
}
}
这里我只截取了关键的代码,经过 chian.proceed() 方法调用下一个拦截器:
2、桥接拦截器:
这个拦截器的主要目的就是帮我们补全一些网络请求的信息,还有对响应体 Gzip 压缩判断
package okhttp3.internal.http;
/**
* 从应用程序代码到网络代码的桥梁。首先,它根据用户请求构建网络请求。
* 然后它继续调用网络。最后,它根据网络响应构建用户响应。
*/
public final class BridgeInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
// ...
if (contentType != null) { // 补充 contentType
requestBuilder.header("Content-Type", contentType.toString());
}
long contentLength = body.contentLength();
if (contentLength != -1) { // 补充请求体长度
requestBuilder.header("Content-Length", Long.toString(contentLength));
} else { // 拿不到请求体的长度,分块编码
requestBuilder.header("Transfer-Encoding", "chunked");
}
if (userRequest.header("Host") == null) { // 补充 Host
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
}
if (userRequest.header("Connection") == null) { // 补充 Connection
requestBuilder.header("Connection", "Keep-Alive");
}
// 允许服务器用 Gzip 压缩之后再响应回来
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
requestBuilder.header("Accept-Encoding", "gzip");
}
// 加载 Cookie 数据
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies));
}
if (userRequest.header("User-Agent") == null) { // 加入 okhttp 的版本
requestBuilder.header("User-Agent", Version.userAgent());
}
Response networkResponse = chain.proceed(requestBuilder.build()); // 拿到响应结果
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// ... (若同意 Gzip 压缩,则进行 Gzip 解压)
return responseBuilder.build();
}
}
经过 chian.proceed() 方法调用下一个拦截器:
3、缓存拦截器
网络的缓存比较复杂,在 okhttp 当中,对缓存判断封装到了 CacheStrategy 缓存策略里面
package okhttp3.internal.cache;
public final class CacheInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
// ....
CacheStrategy strategy = new CacheStrategy.Factory(now, chain.request(), cacheCandidate).get(); // 经过了缓存策略判断后返回的对象
Request networkRequest = strategy.networkRequest; // 请求
Response cacheResponse = strategy.cacheResponse; // 缓存响应
// ... (判空处理)
Response networkResponse = null;
try {
networkResponse = chain.proceed(networkRequest); // 拿到响应结果
} finally {
if (networkResponse == null && cacheCandidate != null) {
closeQuietly(cacheCandidate.body()); // 释放
}
}
if (cacheResponse != null) { // 如果服务器响应 304 且有缓存,则直接使用缓存
if (networkResponse.code() == 304) {
Response response = cacheResponse.newBuilder()
.headers(combine(cacheResponse.headers(), networkResponse.headers()))
.sentRequestAtMillis(networkResponse.sentRequestAtMillis())
.receivedResponseAtMillis(networkResponse.receivedResponseAtMillis())
.cacheResponse(stripBody(cacheResponse))
.networkResponse(stripBody(networkResponse))
.build();
networkResponse.body().close();
cache.trackConditionalCacheHit();
cache.update(cacheResponse, response);
return response;
} else {
closeQuietly(cacheResponse.body());
}
}
// ...
return response;
}
}
经过 chian.proceed() 方法调用下一个拦截器:
4、连接拦截器
这个拦截器就是帮我们获取一个连接,代码比较少:
package okhttp3.internal.connection;
// 打开到目标服务器的连接,并继续到下一个拦截器。
// 网络可以用于返回的响应,或者用条件GET验证缓存的响应。
public final class ConnectInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
// ...
HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks); // 查找链接
RealConnection connection = streamAllocation.connection(); // 获取连接
return realChain.proceed(request, streamAllocation, httpCodec, connection);
}
}
获取连接的核心代码是在 StreamAllocation.findConnection() 上,比较多(未完待续)
经过 chian.proceed() 方法调用下一个拦截器:
5、请求服务拦截器
最后一个拦截器,对服务器进行网络调用
package okhttp3.internal.http;
public final class CallServerInterceptor implements Interceptor {
@Override public Response intercept(Chain chain) throws IOException {
// ...
httpCodec.writeRequestHeaders(request); // 往服务器发送请求头
if (responseBuilder == null) {
responseBuilder = httpCodec.readResponseHeaders(false); // 解析服务器响应数据
}
Response response = responseBuilder
.request(request)
.handshake(streamAllocation.connection().handshake())
.sentRequestAtMillis(sentRequestMillis)
.receivedResponseAtMillis(System.currentTimeMillis())
.build();
// 如果连接断开,证明不是长连接
if ("close".equalsIgnoreCase(response.request().header("Connection"))
|| "close".equalsIgnoreCase(response.header("Connection"))) {
streamAllocation.noNewStreams(); // 不加入连接池
}
return response;
}
}
请求服务拦截器处理完毕后,整个请求过程就完成了。接下来,响应将被返回给调用方进行进一步处理,包括解析响应数据、处理状态码、处理错误等。