2. OkHttp 核心架构与请求拦截器机制:Interceptor 链、RealCall、责任链模式

OkHttp 的请求执行并非一个简单的函数调用,而是由 RealCall 实例协调一组 Interceptor 按照顺序逐个处理,最终到达网络层。这种设计完全遵循 责任链(Chain of Responsibility) 模式,使每个拦截器只关心自己的职责,并在不修改核心执行流程的情况下实现插拔式扩展。对于安全防护来说,拦截器链是插入身份校验、加密、日志埋点、证书绑定等逻辑的唯一正确位置。

2.1 从 RealCall 到请求执行

当调用 OkHttpClient.newCall(request) 时,OkHttp 返回一个 RealCall 实例。RealCall 持有 OkHttpClient 和原始 Request,其内部通过 Dispatcher 决定立即执行还是排队,最终调用 getResponseWithInterceptorChain() 构建拦截器链并开始执行。

组件职责
RealCall封装一次 HTTP 请求的全生命周期;提供 execute()(同步)和 enqueue()(异步)入口
Dispatcher管理异步请求的并发度(最大 64 个并发,默认每个 Host 最大 5 个)
Interceptor.Chain责任链的纽带;每个拦截器通过 chain 获取 Request 并调用 proceed() 将请求传递到下一拦截器
// RealCall 核心执行路径(简化)
Response getResponseWithInterceptorChain() throws IOException {
    List<Interceptor> interceptors = new ArrayList<>();
    // 用户自定义应用拦截器
    interceptors.addAll(client.interceptors());
    // OkHttp 内置拦截器
    interceptors.add(new RetryAndFollowUpInterceptor(client));
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.cache()));
    interceptors.add(new ConnectInterceptor(client));
    // 网络拦截器(用户自定义)
    if (client.networkInterceptors() != null) 
        interceptors.addAll(client.networkInterceptors());
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest,
        this, client, callTimeout, readTimeout, writeTimeout);
    return chain.proceed(originalRequest);
}

2.2 Interceptor 链与责任链模式

责任链模式在此场景的含义是:每个拦截器都可以决定是否继续传递请求(短路),或者在响应返回时对 Response 进行后处理。 拦截器接口只有两个方法:

public interface Interceptor {
    Response intercept(Chain chain) throws IOException;

    interface Chain {
        Request request();
        Response proceed(Request request) throws IOException;
        // 连接、超时等获取方法
        Connection connection();
        Call call();
        int connectTimeoutMillis();
        // …
    }
}

每个拦截器内部的典型实现模式:

  1. 读取 chain.request() 得到当前请求。
  2. 对请求进行前置处理(例如添加 Header、加密 Body)。
  3. 调用 chain.proceed(modifiedRequest) 将请求传递给下一个拦截器。
  4. 对返回的 Response 进行后置处理(例如解密 Body、校验签名)。
  5. 返回最终 Response。
关键安全知识:应用拦截器(Application Interceptor)在重定向和重试之前执行一次;网络拦截器(Network Interceptor)在每次真实的网络请求前后执行,适合做请求/响应的篡改监测。安全拦截器通常注册为应用拦截器,避免重复执行。
API 对比:client.interceptors().add() 添加应用拦截器;client.networkInterceptors().add() 添加网络拦截器。两种拦截器的执行时机和次数不同,务必根据安全需求选择。

2.3 Interceptor 链的结构图

下图清晰展示了从 RealCall 到网络请求的完整拦截器责任链,以及每个阶段的处理边界。

OkHttp 拦截器责任链结构图 ▸ OkHttp 拦截器责任链(从 RealCall 到网络层) RealCall 构建链 应用拦截器(自定义) 应用拦截器(自定义) 应用拦截器(安全插件) proceed RetryAndFollowUp BridgeInterceptor proceed CacheInterceptor ConnectInterceptor 网络拦截器(自定义) 网络拦截器(日志/监控) CallServerInterceptor Socket I/O 网络执行(Socket/HTTP) ⚠ 执行顺序 请求从上往下传递 响应从下往上返回 应用层 网络层

2.4 拦截器编写与 API 实操

自定义拦截器需要实现 Interceptor 接口。以下是一个完整的安全拦截器示例,它在请求头中加入认证 ,在响应中校验签名。

public class AuthSignatureInterceptor implements Interceptor {
    private static final Charset UTF8 = Charset.forName("UTF-8");
    private final String ;

    public AuthSignatureInterceptor(String ) {
        this. = ;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        // 1. 前置处理:读取原始请求,添加身份头
        Request original = chain.request();
        Request.Builder builder = original.newBuilder()
            .header("X-", )
            .header("X-Timestamp", String.valueOf(System.currentTimeMillis()));

        // 2. 对 Body 进行摘要签名(示例仅对字符串 Body 签名)
        RequestBody body = original.body();
        if (body != null && body.contentLength() > 0) {
            Buffer buffer = new Buffer();
            body.writeTo(buffer);
            String bodyString = buffer.readString(UTF8);
            String signature = HmacSha256.sign(bodyString, );
            builder.header("X-Signature", signature);
        }

        Request signedRequest = builder.build();

        // 3. 传递给下一拦截器
        Response response = chain.proceed(signedRequest);

        // 4. 后置处理:校验响应签名
        String respSignature = response.header("X-Response-Signature");
        if (respSignature != null) {
            ResponseBody respBody = response.body();
            if (respBody != null) {
                String respStr = respBody.string();
                String expected = HmacSha256.sign(respStr, );
                if (!expected.equals(respSignature)) {
                    throw new IOException("响应签名验证失败,数据可能被篡改");
                }
            }
        }
        return response;
    }
}

注册方式:

OkHttpClient client = new OkHttpClient.Builder()
    .addInterceptor(new AuthSignatureInterceptor("sk-xxxx"))
    .addInterceptor(new LoggingInterceptor()) // 调试日志
    .build();

2.5 调试与验证

在拦截器内部打印日志是最直接的调试手段。使用 Logging Interceptor 可观察每个拦截器对请求/响应的修改效果:

// 内置日志拦截器(需要手动注册)
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
client = client.newBuilder()
    .addInterceptor(loggingInterceptor) // 应用拦截器级别
    .build();
调试技巧:在开发阶段将 LoggingInterceptor 置于拦截器链最前面,可查看原始请求;置于最后面(网络拦截器位置)可查看最终发往服务器的请求和返回的原始响应。

2.6 常见错误与避坑

错误场景原因解决
拦截器中多次调用 chain.proceed()违反责任链约定,抛出 IllegalStateException确保每个拦截器只调用一次 proceed()
忘记关闭 Body 导致连接泄漏后置处理中调用了 body.string() 后未重新包装使用 ResponseBody.create() 重新构建响应
拦截器中抛出 RuntimeException导致整个请求失败且无法重试拦截器内使用 try-catch 处理安全异常,转为 IOException
拦截器顺序混乱导致安全逻辑被跳过自定义拦截器未加到最前面或被内置拦截器覆盖安全拦截器优先注册,放在应用拦截器列表头部
性能警告:每个拦截器的 intercept() 方法都在 OkHttp 的调度线程中执行。如果安全拦截器中包含阻塞操作(如网络校验、文件 I/O),必须使用异步方式或交给后台线程,否则会阻塞整个请求池。

2.7 性能与稳定性优化建议

  • 减少 Body 读取次数:签名验证时尽量读取一次 Body,使用 BufferedSource 边读取边计算。
  • 缓存签名结果:对于相同请求体在短时间内不应重复计算签名。
  • 超时分离:关键安全拦截器可单独设置更短的超时,避免被慢请求拖死。
  • 使用 Network Interceptor 做无状态监控:重试场景下网络拦截器会多次执行,适合记录网络耗时,但不宜做重操作。
// 优化示例:在 ConnectInterceptor 后使用专用超时
OkHttpClient safeClient = client.newBuilder()
    .addInterceptor(chain -> {
        Request request = chain.request()
            .newBuilder()
            .header("X-Debug", "1")
            .build();
        return chain.withConnectTimeout(3, TimeUnit.SECONDS).proceed(request);
    })
    .build();

2.8 本章小结