【OkHttp】拦截器解析
时间:2022-08-25 02:00:00
OkHttp拦截器
以前的文章【OkHttp中我已经对源码分析了OkHttp详细分析了整个过程及其分发器分发过程。感兴趣的老板可以观察和观察,并给弟弟一些建议~
今天的重点是把OkHttp拦截器部分的内容单独提出,因为它是OkHttp核心部分!
读过OkHttp源码的大佬应该都知道,不管是用的OkHttp同步请求或异步请求将通过一种方法进行getResponseWithInterceptorChain()
来获取Response。这种方法是通过责任链执行拦截器的入口,所以让我们从这种方法开始。
拦截器的执行过程
OkHttp典型的责任链设计模式用于内部,连接多个拦截器对象形成链,每个拦截器履行自己的职责。网络请求request把它放在链上自上而下,直到链上的拦截器对象决定处理网络请求,处理结果response然后从链下到上返回,这样每个拦截器都可以得到request和response了。
然后我们开始进入getResponseWithInterceptorChain()
看看方法的源码:
RealCall.java: Response response = getResponseWithInterceptorChain(); 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)); //构建一个chain对象 Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0, originalRequest, this, eventListener, client.connectTimeoutMillis(), client.readTimeoutMillis(), client.writeTimeoutMillis()); //执行chain.proceed方法,并把originalRequest传入 return chain.proceed(originalRequest); }
在getResponseWithInterceptorChain()
方法中,创建一个Interceptor.Chain
对象,注意创建的时候将参数interceptors(拦截器集合)、index=0(拦截器所在集合的索引)、originalRequest、call、等参数传了进去。最后调用chain.proceed(originalRequest)
方法得到响应并return。
这里,chain对象实际上是一个RealInterceptorChain,所以我们进入RealInterceptorChain.java看看它是怎么实现的:
RealInterceptorChain.java:
public final class RealInterceptorChain implements Interceptor.Chain {
private final List<Interceptor> interceptors;
private final StreamAllocation streamAllocation;
private final HttpCodec httpCodec;
private final RealConnection connection;
private final int index;
private final Request request;
private final Call call;
private final EventListener eventListener;
private final int connectTimeout;
private final int readTimeout;
private final int writeTimeout;
private int calls;
// 主要是对成员变量进行赋值
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) {
// 外部传进来的拦截器集合赋值给变量interceptors
this.interceptors = interceptors;
this.connection = connection;
this.streamAllocation = streamAllocation;
this.httpCodec = httpCodec;
this.index = index;
// 外部传进来的request赋值给变量request
this.request = request;
this.call = call;
this.eventListener = eventListener;
this.connectTimeout = connectTimeout;
this.readTimeout = readTimeout;
this.writeTimeout = writeTimeout;
}
// .....................................................................................
// 可以看到proceed返回的是一个Response对象
@Override
public Response proceed(Request request) throws IOException {
return proceed(request, streamAllocation, httpCodec, connection);
}
public Response proceed(Request request, StreamAllocation streamAllocation, HttpCodec httpCodec,
RealConnection connection) throws IOException {
if (index >= interceptors.size()) throw new AssertionError();
// ...........................................................................................
// 调用责任链的下一个拦截器
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);
// ..............................................................................................
return response;
}
}
注意到这里使用责任链进行处理的时候,
1、新建责任链对象next,并且第5个参数传入了index + 1
2、通过index从拦截器集合interceptors中取出一个拦截器对象
3、执行该拦截器的intercept(Chain chain)
方法,并将next对象传入
那么拦截器是怎样将请求传递到下一级的呢?又是怎样将响应往上一级回传呢?这就得看看拦截器的intercept(Chain chain)
方法又做了什么事情了,我们以桥接拦截器为例,看看里面的核心代码:
BridgeInterceptor.java:
@Override public Response intercept(Chain chain) throws IOException {
// 从chain对象中获取请求userRequest
Request userRequest = chain.request();
Request.Builder requestBuilder = userRequest.newBuilder();
// ..................................................................................
// 执行chain.proceed(Request request)方法,得到响应
Response networkResponse = chain.proceed(requestBuilder.build());
// ..................................................................................
Response.Builder responseBuilder = networkResponse.newBuilder()
.request(userRequest);
// ..................................................................................
// 将响应return
return responseBuilder.build();
}
在BridgeInterceptor的intercept(Chain chain)
中,核心的流程就是
1、先是从chain对象中获取请求userRequest
2、然后执行chain.proceed(Request request)
方法,得到响应
3、最后将响应return出去
我们细想,在第2点,执行chain.proceed(Request request)
方法,是不是又去执行了责任链的proceed方法。
也就是说,在执行getResponseWithInterceptorChain()
方法的最后,调用了proceed方法,而proceed方法内部,又执行了proceed方法,这很显然就是递归调用嘛!
当我们执行getResponseWithInterceptorChain()
方法,会得到一个拦截器集合,并且它们的顺序如下:
重试重定向拦截器 -> 桥接拦截器 -> 缓存拦截器 -> 连接拦截器 -> 请求服务器拦截器
将它们构建成一个Interceptor.Chain
对象,然后执行chain.proceed(originalRequest)
方法,那么后面的流程将会是这样的:
-
index加1,并创建一个RealInterceptorChain对象next,执行
重试重定向拦截器的intercept(next)方法
,并把next传入。重试重定向拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(桥接拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
桥接拦截器的intercept(next)方法
,并把next传入。桥接拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(缓存拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
缓存拦截器的intercept(next)方法
,并把next传入。缓存拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(连接拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
连接拦截器的intercept(next)方法
,并把next传入。连接拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,将请求传向下一级拦截器(请求服务器拦截器) -
index加1,并创建一个RealInterceptorChain对象next,执行
请求服务器拦截器的intercept(next)方法
,并把next传入。请求服务器拦截器的intercept(next)方法
内部会继续执行next.proceed(next.request())
方法,由于请求服务器拦截器没有下一级拦截器了,因此,它会直接将response返回。返回到哪呢?那肯定是上一次拦截器呀,也就是连接拦截器。 -
连接拦截器将response返回到缓存拦截器
-
缓存拦截器将response返回到桥接拦截器
-
桥接拦截器将response返回到重试重定向拦截器
-
重试重定向拦截器将response返回到
getResponseWithInterceptorChain()
方法
至此,拦截器的执行流程已经解释完毕了,是不是很简单,实际上就是一个递归的流程嘛。
重试重定向拦截器
重试重定向拦截器将请request交给下一级拦截器之前,会判断用户是否取消了请求,在获得了结果之后,会根据响应码判断是否需要重定向或者重试,如果满足条件那么会新建或者复用之前的连接在下一次循环中进行请求重试,重试或重定向会重启执行所有拦截器。
那么怎么判断当前链接是否需要重定向呢?我们知道Http请求的响应是包含响应头和响应体的,响应头中有一个参数Location,如果Location的值为重定向的值,则说明该请求链接需要重定向到另一个请求链接。
重试重定向拦截器的重定向次数最多为20次,如果超过这个次数,将抛出一个异常。
RetryAndFollowUpInterceptor.java:
@Override public Response intercept(Chain chain) throws IOException {
// 通过chain拿到request
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
// 通过chain拿到call对象
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
// 记录重定向的次数
int followUpCount = 0;
Response priorResponse = null;
while (true) {
if (canceled) {
streamAllocation.release();
throw new IOException("Canceled");
}
Response response;
boolean releaseConnection = true;
try {
// 这里从当前的责任链开始执行一遍责任链,是一种重试的逻辑
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
} catch (RouteException e) {
// 调用recover方法从失败中进行恢复,如果可以恢复就返回true,否则返回false
if (!recover(e.getLastConnectException(), streamAllocation, false, request)) {
throw e.getLastConnectException();
}
releaseConnection = false;
continue;
} catch (IOException e) {
// 重试与服务器进行连接
boolean requestSendStarted = !(e instanceof ConnectionShutdownException);
if (!recover(e, streamAllocation, requestSendStarted, request)) throw e;
releaseConnection = false;
continue;
} finally {
// 如果 releaseConnection 为 true 则表明中间出现了异常,需要释放资源
if (releaseConnection) {
streamAllocation.streamFailed(null);
streamAllocation.release();
}
}
// 使用之前的响应 priorResponse 构建一个响应,这种响应的响应体 body 为空
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build();
}
// 根据得到的响应进行处理,可能会增加一些认证信息、重定向或者处理超时请求
// 如果该请求无法继续被处理或者出现的错误不需要继续处理,将会返回 null
Request followUp = followUpRequest(response, streamAllocation.route());
// 无法重定向,直接返回之前的响应
if (followUp == null) {
if (!forWebSocket) {
streamAllocation.release();
}
return response;
}
// 关闭资源
closeQuietly(response.body());
// 达到了重定向的最大次数(20次),就抛出一个异常
if (++followUpCount > MAX_FOLLOW_UPS) {
streamAllocation.release();
throw new ProtocolException("Too many follow-up requests: " + followUpCount);
}
if (followUp.body() instanceof UnrepeatableRequestBody) {
streamAllocation.release();
throw new HttpRetryException("Cannot retry streamed HTTP body", response.code());
}
// 这里判断新的请求是否能够复用之前的连接,如果无法复用,则创建一个新的连接
if (!sameConnection(response, followUp.url())) {
streamAllocation.release();
streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(followUp.url()), call, eventListener, callStackTrace);
this.streamAllocation = streamAllocation;
} else if (streamAllocation.codec() != null) {
throw new IllegalStateException("Closing the body of " + response
+ " didn't close its backing stream. Bad interceptor?");
}
request = followUp;
priorResponse = response;
}
}
基本的流程在注释里都写得比较清楚了,这里还有个变量要留意一下:StreamAllocation
,它相当于一个管理类,维护了服务器连接、并发流和请求之间的关系,该类还会初始化一个Socket
连接对象,获取输入和输出流对象。
在重试和重定向拦截器中,是通过 client.connectionPool()
传入了一个连接池对象 ConnectionPool
来创建出一个StreamAllocation对象。而这里只是初始化了这个对象,并没有在当前方法中有真正用到这个类,而是把它们传递到下一级的拦截器里来从服务器中获取请求的响应。
桥接拦截器
桥接拦截器的逻辑比较简单,在将请求交给下一级拦截器之前,它负责将HTTP协议必备的请求头加入其中,比如Host、Content-Type、Accept-Encoding等等的请求头,并添加一些默认的行为,比如GZIP压缩,在获得结果后,调用保存cookie接口并解析GZIP数据。
@Override public Response intercept(Chain chain) throws IOException { // 通过chain拿到request Request userRequest = chain.request(); Request.Builder requestBuilder = userRequest.newBuilder(); RequestBody body = userRequest.body(); if (body != null) { MediaType contentType = body.contentType(); if (contentType != null) { requestBuilder.header("Content-Type", contentType.toString()); } long contentLength = body.contentLength(); if (contentLength != -1) { requestBuilder.header("Content-Length", Long.toString(contentLength)); requestBuilder.removeHeader("Transfer-Encoding"); } else { requestBuilder.header("Transfer-Encoding", "chunked"); requestBuilder.removeHeader("Content-Length"); } } if (userRequest.header("Host") == null) { requestBuilder.header("Host", hostHeader(userRequest.url(), false)); } if (userRequest.header("Connection") == null) { requestBuilder.header("Connection", "Keep-Alive"); } boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) { transparentGzip = true; requestBuilder.header("Accept-Encoding", "gzip"); } 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()); } Response networkResponse = chain.proceed(requestBuilder.build()); HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); Response.Builder responseBuilder = networkResponse.newBuilder() .request(userRequest); 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"