一文搞懂如何自定义 OkHttp 拦截器

5,683 阅读5分钟

目的

关于 OkHttp 的拦截器的文章有很多,这篇文章旨在介绍:

  • 拦截器的基本原理
  • 如何自定义一个拦截器
  • Application Interceptors 和 Network Interceptors 的不同

自定义拦截器的写法

官方文档 中介绍了如何自定义一个 Interceptor,代码如下:

class HeaderInterceptor() : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        ...
        val response = chain.proceed(request)
        ...
        return response
    }
}

可以看到主要分为 requestresponse 两部分,并且 reponse 必须要要调用 chain.proceed(request)

然后使用下面的两个方法将拦截器添加到 OkHttp 中:

 OkHttpClient.Builder()
// 添加网络拦截器 
.addNetworkInterceptor(...)
// 添加应用拦截器
.addInterceptor(...)

使用方法非常的简单,至于为什么要这么写,我们不妨提出下面几个问题,解答了这几个问题就可以掌握自定义拦截器的相关知识了:

  • Chain 是什么,为什么还要调用 chain.proceed(request)?
  • Request 中都有什么信息,如何重写 Request
  • Response 中都有什么信息,如何重写 Response

Chain

chain 就是 链条 的意思,OkHttp 的拦截器采用了责任链设计模式,那么这个责任链是如何构造的呢?

责任链模式简介

我们先来了解一下什么是责任链模式。 要实现一个责任链需要先定义一个接口,比如我们设计一个上传功能责任链,会依次上传String,Image,File,接口实现如下:

interface Uploader {
    fun doUpload(data: Data, chain: UploadChain)
}

然后依次定义 String,Image,File 的上传器:

class StringUploader : Uploader {
    override fun doUpload(data: Data, chain: UploadChain) {
        ...
            chain.doUpload(data, chain)
            return
        }
}
class ImageUploader : Uploader {
    override fun doUpload(data: Data, chain: UploadChain) {
        ...
            chain.doUpload(data, chain)
            return
        }
}
class FileUploader : Uploader {
    override fun doUpload(data: Data, chain: UploadChain) {
        ...
            chain.doUpload(data, chain)
            return
        }
}

我们发现有一个 UploadChain 类,为什么要定义它呢?StringUploader、ImageUploader、FileUploader 可以想象成是责任链的节点,我们需要一个责任链的管理者来添加/依次调用这些节点:

class UploadChain() : Uploader {
    // 记录节点的角标
    private var position = 0
    private val uploaderList = mutableListOf<Uploader>()
    override fun doUpload(data: Data, chain: UploadChain) {
        if (position == uploaderList.size) {
            ...
            // 责任链执行完成
            return
        }
       // 执行下一个节点
       uploaderList[position++].doUpload(data, chain)
    }
    // 添加节点
 fun addUploader(uploader: Uploader) {
        uploaderList.add(uploader)
    }
}

使用的时候我们先创建一个链条,然后将 String,Image,File 节点依次添加进入,然后调用第一个节点:

val uploadChain = UploadChain()
uploadChain.addUploader(StringUploader())
uploadChain.addUploader(ImageUploader())
uploadChain.addUploader(FileUploader())

uploadChain.doUpload(data,uploadChain)

OkHttp 构造责任链

接口定义

OkHttp 拦截器接口定义如下:

interface Interceptor {
  @Throws(IOException::class)
  fun intercept(chain: Chain): Response

  interface Chain {
    fun request(): Request

    @Throws(IOException::class)
    fun proceed(request: Request): Response

    fun connection(): Connection?

    fun call(): Call

   ...
  }
}

Chain 的实现类是 RealInterceptorChain,它的源码结构和上面的 UploadChain 是类似的,有 Interceptor 的集合,当前节点的角标 index,以及触发方法 proceed。

RealInterceptorChain
class RealInterceptorChain(
  private val interceptors: List<Interceptor>,
  private val index: Int,
) : Interceptor.Chain {

  override fun proceed(request: Request): Response {
    return proceed(request, transmitter, exchange)
  }

  @Throws(IOException::class)
  fun proceed(request: Request, transmitter: Transmitter, exchange: Exchange?): Response {
    if (index >= interceptors.size) throw AssertionError()

    calls++
    ...
    // Call the next interceptor in the chain.
    val next = RealInterceptorChain(interceptors, transmitter, exchange,
        index + 1, request, call, connectTimeout, readTimeout, writeTimeout)
    val interceptor = interceptors[index]

    val response = interceptor.intercept(next) 
    ...
    return response
  }
}
chain.proceed 的作用
  • 将当前事件交给责任链的下一个拦截器处理。
  • 递归返回 response

RealInterceptorChain 和上面 UploadChain 有一些不同的地方,UploadChain 只是顺序执行了责任链的每个节点,而 RealInterceptorChain 每个节点不止要处理 Request 还需要返回 Response,所以下一个拦截器会通过递归将 Response 返回给上一个拦截器。

添加拦截器的顺序

我们再来看一下是如何将 Interceptor 添加到 Chain 中的,在 RealCall 每次实例化一个 Chain 的时候都会添加一系列的拦截器:

fun getResponseWithInterceptorChain(): Response {
    val interceptors = mutableListOf<Interceptor>()
    // 添加自定义的应用拦截器
    interceptors += client.interceptors
    interceptors += RetryAndFollowUpInterceptor(client)
    interceptors += BridgeInterceptor(client.cookieJar)
    interceptors += CacheInterceptor(client.cache)
    interceptors += ConnectInterceptor
    // 添加自定义的网络拦截器
    if (!forWebSocket) {
      interceptors += client.networkInterceptors
    }
    interceptors += CallServerInterceptor(forWebSocket)
    // 实例化一个 Chain
    val chain = RealInterceptorChain(interceptors, transmitter, null, 0, originalRequest, this,
        client.connectTimeoutMillis, client.readTimeoutMillis, client.writeTimeoutMillis)
    // 开始触发第一个拦截器    
    val response = chain.proceed(originalRequest)
     ...
      return response
    } 
  }

OkHttp 默认实现的拦截器这里不再讲述,可以参考这篇文章 okhttp3 拦截器源码分析 这里我们只关心拦截器的添加顺序。

Request

Request 包含了请求头(header),请求体(body),访问的的 url。

对 Request 比较常规的操作:

  • 添加公共 header:
class HeaderInterceptor(private val headers: Map<String, String>?) : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {
        var request = chain.request()
        if (!headers.isNullOrEmpty()) {
            val builder = request.newBuilder()
            headers.forEach {
                builder.header(it.key, it.value)
            }
            request = builder.build()
        }
        val response = chain.proceed(request)
        return response
    }
}
  • 打印请求日志,可以直接参考官方提供的 HttpLoggingInterceptor
  • Gzip 加密
  • 抓包:参考 steho
  • DNS 相关
  • 修改 url 进行重定向
  override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        request.newBuilder().url(customUrl).build()
        ...
        }

此外,比较有趣的是 request 还有一个 tag 字段,如果你使用 retrofit 的话,它有一个实用的用法,有兴趣的可以看一下 使用拦截器扩展 retrofit 的注解类型

Response

Response 中的信息和 Request 比较类似,包含了服务器或者本地返回响应头,响应体。 对 Resposne 的操作一般有:

  • Gzip 解压
  • 加载本地缓存:不调用 chain.proceed, 返回自己的 response 短路拦截器
 override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        // 获取本地数据
        val mockData = getMockData(mock)
        // 重写一个 Response 进行短路操作
        if (!mockData.isNullOrEmpty()) {
            return Response.Builder()
                .protocol(Protocol.HTTP_1_0)
                .code(200)
                .request(request)
                .message("ok")
                .body(mockData.toResponseBody(null))
                .build()
        }
        return chain.proceed(request)
    }

Application Interceptors 和 Network Interceptors 的不同

执行顺序

借用上面文章的一张图,发现 OkHttp 会先执行应用拦截器,再执行网络拦截器:

调用次数

应用拦截器只会调用一次,网络拦截器可能调用多次。

其实很好理解,网络拦截器是在重试重定向拦截器之后执行的,如果重试了又会重新调用一次,所以应用拦截器不受重试的影响,网络拦截器受重试的影响。

所以像添加 header,加密,Gzip 压缩 这种修改 request 的操作就可以使用应用拦截器,而像打印 log 的这种操作就需要使用网络拦截器。

短路操作

应用拦截器允许不调用 Chain.proceed() 进行短路操作,所以像读取缓存的这种拦截器就可以使用应用拦截器。

Connection 参数

Chain 有一个 connect 方法,只有网络拦截器才会返回值,应用拦截器会返回 null:

 interface Chain {
    ...
    /**
     * Returns the connection the request will be executed on. This is only available in the chains
     * of network interceptors; for application interceptors this is always null.
     */
    fun connection(): Connection?
    }

Connection 包含了本次网络请求比较底层的 socket,握手操作等,目前只看到抓包工具 Steho 在使用。

参考

okhttp3 拦截器源码分析