在 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); // 组装chainInterceptor 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) { // 补充 HostrequestBuilder.header("Host", hostHeader(userRequest.url(), false));}if (userRequest.header("Connection") == null) { // 补充 ConnectionrequestBuilder.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;}
}
请求服务拦截器处理完毕后,整个请求过程就完成了。接下来,响应将被返回给调用方进行进一步处理,包括解析响应数据、处理状态码、处理错误等。