1. 概述

在开发 Web 应用时,我们经常需要对 HTTP 请求和响应的生命周期进行干预,比如添加一些前置或后置处理逻辑。OkHttp 是一个高效、支持 HTTP/2 的 Android 和 Java 客户端库,它提供了强大的拦截器(Interceptor)机制来实现这一点。

在本文中,我们将深入探讨如何使用 OkHttp 的拦截器来处理请求与响应。

2. 拦截器简介

拦截器是一种可插拔的组件,允许我们在请求被发送之前或响应返回之后对其进行拦截和处理。这种机制非常适合以下场景:

✅ 添加请求头(如认证信息)
✅ 修改请求体或响应体
✅ 日志记录
✅ 错误统一处理

拦截器的核心优势在于 逻辑集中化复用性高,避免了在每个接口中重复编写相同的逻辑。

3. 典型应用场景

拦截器常见用途包括:

  • 请求参数日志打印
  • 添加认证头(Authorization)
  • 响应体压缩
  • 响应头增强(如添加 Cookie)
  • 错误统一包装

这些功能都可以通过拦截器优雅地实现。

4. 依赖配置

首先,添加 OkHttp 核心依赖:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>5.0.0-alpha.12</version>
</dependency>

测试时可以使用 mockwebserver 来模拟服务端行为:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>mockwebserver</artifactId>
    <version>5.0.0-alpha.12</version>
    <scope>test</scope>
</dependency>

5. 自定义日志拦截器

我们先来实现一个简单的日志拦截器,打印请求头和 URL:

public class SimpleLoggingInterceptor implements Interceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class);

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();

        LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url());

        return chain.proceed(request);
    }
}

⚠️ 注意:每个拦截器必须调用 chain.proceed(request),否则请求不会继续执行。

5.1. 注册拦截器

将拦截器注册到 OkHttpClient 中:

OkHttpClient client = new OkHttpClient.Builder() 
  .addInterceptor(new SimpleLoggingInterceptor())
  .build();

✅ 可以多次调用 addInterceptor 添加多个拦截器,它们会按添加顺序执行。

5.2. 编写测试用例

使用 MockWebServer 进行测试:

@Rule
public MockWebServer server = new MockWebServer();

@Test
public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException {
    server.enqueue(new MockResponse().setBody("Hello Baeldung Readers!"));
        
    OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(new SimpleLoggingInterceptor())
      .build();

    Request request = new Request.Builder()
      .url(server.url("/greeting"))
      .header("User-Agent", "A Baeldung Reader")
      .build();

    try (Response response = client.newCall(request).execute()) {
        assertEquals("Response code should be: ", 200, response.code());
        assertEquals("Body should be: ", "Hello Baeldung Readers!", response.body().string());
    }
}

5.3. 测试输出示例

运行测试后,控制台会输出类似日志:

16:07:02.644 [main] INFO  c.b.o.i.SimpleLoggingInterceptor - Intercepted headers: User-Agent: A Baeldung Reader
 from URL: http://localhost:54769/greeting

5.4. 使用内置的日志拦截器

OkHttp 提供了官方的日志拦截器,可以直接使用:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>logging-interceptor</artifactId>
    <version>5.0.0-alpha.12</version>
</dependency>

使用方式如下:

HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.HEADERS);

6. 自定义响应头拦截器

下面这个拦截器用于修改响应头,比如设置 Cache-Control

public class CacheControlResponeInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        return response.newBuilder()
          .header("Cache-Control", "no-store")
          .build();
    }
}

7. 使用拦截器统一处理错误

我们可以用拦截器来统一包装错误响应,返回 JSON 格式的错误信息:

定义错误信息类:

public class ErrorMessage {

    private final int status;
    private final String detail;

    public ErrorMessage(int status, String detail) {
        this.status = status;
        this.detail = detail;
    }
    
    // Getters and setters
}

拦截器实现:

public class ErrorResponseInterceptor implements Interceptor {
    
    public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8");

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        
        if (!response.isSuccessful()) {
            Gson gson = new Gson();
            String body = gson.toJson(
              new ErrorMessage(response.code(), "The response from the server was not OK"));
            ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);

            ResponseBody originalBody = response.body();
            if (originalBody != null) {
                originalBody.close();
            }
            
            return response.newBuilder().body(responseBody).build();
        }
        return response;
    }
}

最终返回的 JSON 结构如下:

{
    "status": 500,
    "detail": "The response from the server was not OK"
}

8. 网络拦截器

除了应用层拦截器,OkHttp 还支持网络层拦截器。两者区别如下:

特性 应用拦截器 网络拦截器
注册方式 addInterceptor addNetworkInterceptor
缓存命中时是否执行 ✅ 总是执行 ❌ 不执行
是否能看到重定向/重试 ❌ 不可见 ✅ 可见
是否可获取 IP/TLS 信息 ❌ 否 ✅ 是

注册网络拦截器的方式:

OkHttpClient client = new OkHttpClient.Builder()
  .addNetworkInterceptor(new SimpleLoggingInterceptor())
  .build();

大多数情况下,应用拦截器已经足够使用。

9. 总结

本文介绍了 OkHttp 中拦截器的基本使用方式,包括:

✅ 自定义日志拦截器
✅ 修改响应头
✅ 统一错误处理
✅ 应用拦截器与网络拦截器的区别

拦截器是 OkHttp 中一个非常强大且灵活的功能,合理使用可以极大提升代码的可维护性和扩展性。

完整源码请参考 GitHub 项目


原始标题:Adding Interceptors in OkHTTP

» 下一篇: Java周报,376期