OkHttp源码学习之Interceptor

OkHttp 中,Interceptor 是一个非常强大的功能,用于拦截、修改、观察或终止 HTTP 请求和响应的处理流程。它主要在 网络请求的生命周期 中插入自定义逻辑,从而实现灵活的操作:

Interceptor 的作用

  1. 拦截和修改请求:

    • 在请求发送到服务器之前,对其进行修改。例如,添加公共的请求头、参数、认证信息或日志记录。
    • 修改请求的 URL、方法(GET、POST 等)或请求体。
  2. 拦截和修改响应:

    • 在服务器返回响应之后、传递给调用者之前,处理响应数据。例如,解密数据、缓存处理、或统一错误处理。
  3. 全局控制网络请求:

    • 实现统一的网络请求日志、认证、重试策略等逻辑。
  4. 支持链式调用:

    • Interceptor 的设计模式基于责任链(Chain of Responsibility),可以在拦截器链中依次处理请求和响应,决定是否继续传递。

Interceptor 的类型

OkHttp 中有两种类型的拦截器:

1. Application Interceptor(应用拦截器)

  • 在应用层级运行。

  • 主要用于请求的整体修改或全局操作,例如添加公共头部信息、统一日志打印等。

  • 特点:

    • 不会处理重定向。
    • 不会触发失败重试。
    • 在缓存处理之前执行。
  • 添加方法:

    kotlin 复制代码
    OkHttpClient.Builder()
        .addInterceptor { chain ->
            val request = chain.request().newBuilder()
                .addHeader("Authorization", "Bearer token")
                .build()
            chain.proceed(request)
        }
        .build()

2. Network Interceptor(网络拦截器)

  • 运行在网络层级。

  • 主要用于低级网络操作,例如查看和修改原始网络数据包、监控网络性能。

  • 特点:

    • 可以观察原始数据包(如头部和体)以及网络请求的实际传输。
    • 能够查看响应从服务器返回到客户端的完整过程。
    • 支持失败重试。
  • 添加方法:

    kotlin 复制代码
    OkHttpClient.Builder()
        .addNetworkInterceptor { chain ->
            val request = chain.request()
            val response = chain.proceed(request)
            // 打印响应时间等
            response
        }
        .build()

3.源码解读

通过源码看出,Interceptor是一个抽象接口,有五个实现类,看名称分别对应桥接、缓存、请求拦截,链接拦截、重试拦截。

此拦截包含请求发送前的相关拦截,以及服务端响应体返回的拦截处理,如果我们对资源以及网络请求做处理,那么就需要使用拦截器,并且处理该事件。

以BridgeInterceptor为例子,源码如下,其他的Interceptor也是类似

java 复制代码
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
    val userRequest = chain.request() // 获取原始请求
    val requestBuilder = userRequest.newBuilder() // 创建请求构建器

    val body = userRequest.body // 获取请求体
    if (body != null) {
        val contentType = body.contentType() // 获取内容类型
        if (contentType != null) {
            requestBuilder.header("Content-Type", contentType.toString()) // 设置Content-Type请求头
        }

        val contentLength = body.contentLength() // 获取内容长度
        if (contentLength != -1L) {
            requestBuilder.header("Content-Length", contentLength.toString()) // 设置Content-Length请求头
            requestBuilder.removeHeader("Transfer-Encoding") // 移除Transfer-Encoding请求头
        } else {
            requestBuilder.header("Transfer-Encoding", "chunked") // 设置Transfer-Encoding请求头为chunked
            requestBuilder.removeHeader("Content-Length") // 移除Content-Length请求头
        }
    }

    if (userRequest.header("Host") == null) {
        requestBuilder.header("Host", userRequest.url.toHostHeader()) // 设置Host请求头
    }

    if (userRequest.header("Connection") == null) {
        requestBuilder.header("Connection", "Keep-Alive") // 设置Connection请求头为Keep-Alive
    }

    // 如果添加了"Accept-Encoding: gzip"请求头,那么我们需要解压传输流
    var transparentGzip = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
        transparentGzip = true
        requestBuilder.header("Accept-Encoding", "gzip") // 设置Accept-Encoding请求头为gzip
    }

    val cookies = cookieJar.loadForRequest(userRequest.url) // 加载请求的cookie
    if (cookies.isNotEmpty()) {
        requestBuilder.header("Cookie", cookieHeader(cookies)) // 设置Cookie请求头
    }

    if (userRequest.header("User-Agent") == null) {
        requestBuilder.header("User-Agent", userAgent) // 设置User-Agent请求头
    }

    val networkResponse = chain.proceed(requestBuilder.build()) // 继续执行链中的下一个拦截器

    cookieJar.receiveHeaders(userRequest.url, networkResponse.headers) // 接收响应头

    val responseBuilder = networkResponse.newBuilder() // 创建响应构建器
        .request(userRequest) // 设置请求

    // 如果响应使用了gzip压缩,并且请求头中指定了接受gzip压缩,并且响应体存在
    if (transparentGzip &&
        "gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
        networkResponse.promisesBody()) {
        val responseBody = networkResponse.body // 获取响应体
        if (responseBody != null) {
            val gzipSource = GzipSource(responseBody.source()) // 创建gzip源
            val strippedHeaders = networkResponse.headers.newBuilder()
                .removeAll("Content-Encoding") // 移除Content-Encoding响应头
                .removeAll("Content-Length") // 移除Content-Length响应头
                .build()
            responseBuilder.headers(strippedHeaders) // 设置修改后的响应头
            val contentType = networkResponse.header("Content-Type") // 获取Content-Type响应头
            responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer())) // 设置解压后的响应体
        }
    }

    return responseBuilder.build() // 构建并返回响应
    }

  /** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */
  private fun cookieHeader(cookies: List<Cookie>): String = buildString {
    cookies.forEachIndexed { index, cookie ->
      if (index > 0) append("; ")
      append(cookie.name).append('=').append(cookie.value)
    }
  }
}

这段代码的主要逻辑如下:

  • 它首先获取原始请求,并创建一个请求构建器。

  • 如果请求有请求体,它会设置Content-Type和Content-Length请求头,或者设置Transfer-Encoding为chunked并移除Content-Length。

  • 如果请求中没有Host请求头,它会添加一个Host请求头。

  • 如果请求中没有Connection请求头,它会添加一个Connection请求头为Keep-Alive。

  • 如果请求中没有指定接受gzip压缩,并且没有Range请求头,它会添加一个Accept-Encoding请求头为gzip。

  • 从cookieJar中加载请求的cookie,并添加到请求头中。

  • 如果请求中没有User-Agent请求头,它会添加一个User-Agent请求头。

  • 继续执行链中的下一个拦截器,获取网络响应。

  • 接收网络响应的头部信息。

  • 如果网络响应使用了gzip压缩,它会创建一个解压后的响应体,并移除Content-Encoding和Content-Length响应头。

  • 最后构建并返回响应。

这个拦截器用于确保请求和响应的完整性和正确性,包括设置必要的请求头,处理内容编码和压缩,以及处理cookie。

Interceptor使用

  • 创建自定义的 Interceptor 类:
java 复制代码
class CustomInterceptor : Interceptor {
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        // 对请求进行操作,例如添加头部信息
        val newRequest = request.newBuilder()
            .header("New-Header", "New-Value")
            .build()

        val response = chain.proceed(newRequest)
        // 对响应进行操作,例如处理错误响应
        if (response.code() == 401) {
            // 处理401未授权错误
            // 可以在这里重试请求或者返回一个自定义的响应
        }
        return response
    }
}
  • 将 Interceptor 添加到 OkHttpClient 中:
kotlin 复制代码
val client = OkHttpClient.Builder()
    .addInterceptor(CustomInterceptor())
    .build()

val request = Request.Builder()
    .url("https://example.com")
    .build()

val call = client.newCall(request)

call.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
        // 处理响应
    }

    override fun onFailure(call: Call, e: IOException) {
        // 处理请求失败
    }
})
相关推荐
拭心13 分钟前
Google 提供的 Android 端上大模型组件:MediaPipe LLM 介绍
android
带电的小王3 小时前
WhisperKit: Android 端测试 Whisper -- Android手机(Qualcomm GPU)部署音频大模型
android·智能手机·whisper·qualcomm
梦想平凡3 小时前
PHP 微信棋牌开发全解析:高级教程
android·数据库·oracle
元争栈道3 小时前
webview和H5来实现的android短视频(短剧)音视频播放依赖控件
android·音视频
阿甘知识库4 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
元争栈道5 小时前
webview+H5来实现的android短视频(短剧)音视频播放依赖控件资源
android·音视频
MuYe5 小时前
Android Hook - 动态加载so库
android
居居飒5 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
Henry_He8 小时前
桌面列表小部件不能点击的问题分析
android
工程师老罗9 小时前
Android笔试面试题AI答之Android基础(1)
android