Android Okhttp3 拦截器源码解析

news/2024/10/28 20:27:18/

 在 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;}
}

请求服务拦截器处理完毕后,整个请求过程就完成了。接下来,响应将被返回给调用方进行进一步处理,包括解析响应数据、处理状态码、处理错误等。


http://www.ppmy.cn/news/737111.html

相关文章

如何使用ThinkPHP框架的控制器和视图进行数据渲染?

我来给你们介绍一下ThinkPHP框架中的控制器和视图&#xff0c;让你们在敲代码的时候能够更加得心应手&#xff0c;像一位真正的魔法师一样&#xff01; 首先&#xff0c;我们来谈谈控制器。控制器是ThinkPHP中的核心部分&#xff0c;它负责处理用户的请求并返回响应。想象一下…

View:蓝牙真的无音质?浅谈蓝牙音频传输协议01.

蓝牙应用层中A2DP下的Aptx,AAC,MP3,LDAC编码对比及浅谈。 随着无线技术发展&#xff0c;蓝牙已经发布了它的4.0版本。带来了高速.低功耗等种种特点。 不过随着技术的完善&#xff0c;带宽逐渐变大&#xff0c;为什么还是总说蓝牙无音质呢&#xff1f; 这边文章将通过对蓝牙音频…

Apikit 自学日记:Mock 内置函数

Mock内置函数教程 通过编写Javascript脚本设置响应内容&#xff0c;还可以直接使用内置函数设置“请求体触发条件”相关内容&#xff0c;设置的信息等同于在“请求体触发条件”输入框中的设置&#xff0c;如设置Header参数或者请求体参数等&#xff0c;设置完成后&#xff0c;…

NFC无线近场通讯技术

NFC技术 1 简介 1.1技术背景 无线近场通讯技术&#xff08;Near Field Communication&#xff0c;NFC&#xff09;&#xff0c;最早是Sony和Philip这两家公司共同开发的一种非接触式识别和互联技术&#xff0c;现在已经发展为国际性的非盈利组织 NFC Forum。该组织相当于蓝牙…

计算机网络自顶向下方法(第六版)第一章复习题中文答案

计算机网络自顶向下方法&#xff08;第六版&#xff09;第一章复习题中文答案 R1R2R3R4R5R6R7R8R9R10R11R12R13R14R15R16R17&#xff08;时延&#xff1f;未访问配套web网站这题答案存疑&#xff09;R18R19R20R21&#xff08;同样未访问配套网站&#xff09;R22R23R24R25R26R27…

若干物联网无线技术 - NB-IOT、LoRa、433、GPRS、2.4G、PKE近场通信,基础理论与开发点滴总结

在项目实践学习中记录的点滴笔记&#xff0c;整理成章&#xff0c;希望能给大家提供工作与学习思路。 往期文章 1、无线通信项目开发 - NB-IOT、LoRa、433、GPRS、2.4G、PKE近场通信&#xff0c;基础理论与开发点滴总结 2、蓝牙无线技术(BLE)与开发点滴总结 3、Zigbee无线技…

无线攻击笔记

第11章 无线攻击 任务50&#xff1a;无线渗透.exe 802.11只涵盖了下面黄字部分的2层&#xff0c;802.2定义了LLC层 802.11 下面的协议组&#xff0c;F是正式标准&#xff0c;大写&#xff0c;abcde小写是在之前标准之上进行修正&#xff0c;修正版。 网络速度一般都谈论bit …

为什么TCP在高时延和丢包的网络中传输效率差?

说明&#xff1a;有同学私信问到&#xff0c;为什么TCP在高时延和丢包的网络中传输效率差? Google可以搜到很多的信息&#xff0c;这里转译了部分IBM Aspera fasp技术白皮书的第一章节内容&#xff0c;作为参考。 在这个数字世界中&#xff0c;数字数据的快速和可靠移动&#…