在 OkHttp 中,Interceptor
是一个非常强大的功能,用于拦截、修改、观察或终止 HTTP 请求和响应的处理流程。它主要在 网络请求的生命周期 中插入自定义逻辑,从而实现灵活的操作:
Interceptor 的作用
-
拦截和修改请求:
- 在请求发送到服务器之前,对其进行修改。例如,添加公共的请求头、参数、认证信息或日志记录。
- 修改请求的 URL、方法(GET、POST 等)或请求体。
-
拦截和修改响应:
- 在服务器返回响应之后、传递给调用者之前,处理响应数据。例如,解密数据、缓存处理、或统一错误处理。
-
全局控制网络请求:
- 实现统一的网络请求日志、认证、重试策略等逻辑。
-
支持链式调用:
Interceptor
的设计模式基于责任链(Chain of Responsibility),可以在拦截器链中依次处理请求和响应,决定是否继续传递。
Interceptor 的类型
OkHttp 中有两种类型的拦截器:
1. Application Interceptor(应用拦截器)
-
在应用层级运行。
-
主要用于请求的整体修改或全局操作,例如添加公共头部信息、统一日志打印等。
-
特点:
- 不会处理重定向。
- 不会触发失败重试。
- 在缓存处理之前执行。
-
添加方法:
kotlinOkHttpClient.Builder() .addInterceptor { chain -> val request = chain.request().newBuilder() .addHeader("Authorization", "Bearer token") .build() chain.proceed(request) } .build()
2. Network Interceptor(网络拦截器)
-
运行在网络层级。
-
主要用于低级网络操作,例如查看和修改原始网络数据包、监控网络性能。
-
特点:
- 可以观察原始数据包(如头部和体)以及网络请求的实际传输。
- 能够查看响应从服务器返回到客户端的完整过程。
- 支持失败重试。
-
添加方法:
kotlinOkHttpClient.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) {
// 处理请求失败
}
})