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) {
        // 处理请求失败
    }
})
相关推荐
2401_897916064 小时前
Android 自定义 View _ 扭曲动效
android
天花板之恋5 小时前
Android AutoMotive --CarService
android·aaos·automotive
susu10830189118 小时前
Android Studio打包APK
android·ide·android studio
2401_897907869 小时前
Android 存储进化:分区存储
android
Dwyane0316 小时前
Android实战经验篇-AndroidScrcpyClient投屏一
android
FlyingWDX16 小时前
Android 拖转改变视图高度
android
_可乐无糖16 小时前
Appium 检查安装的驱动
android·ui·ios·appium·自动化
一名技术极客18 小时前
Python 进阶 - Excel 基本操作
android·python·excel
我是大佬的大佬19 小时前
在Android Studio中如何实现综合实验MP3播放器(保姆级教程)
android·ide·android studio
lichong95119 小时前
【Flutter&Dart】MVVM(Model-View-ViewModel)架构模式例子-http版本(30 /100)
android·flutter·http·架构·postman·win·smartapi