前言
BridgeInterceptor主要做了两件事,第一,请求发出之前补全请求头;第二,响应收到之后解析cookie,并保存本地(cookieJar需要调用方自己实现存储和获取),如果是使用gzip返回的数据,则使用 GzipSource 包装便于解析。我们直接进入intercept方法源码。
intercept
分析全在注释中了。
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val userRequest = chain.request()
//通过newBuilder得到的requestBuilder,所包含的参数和userRequest是相同的
val requestBuilder = userRequest.newBuilder()
//包含请求体,说明是post请求
val body = userRequest.body
if (body != null) {
//请求的内容的类型,如传输json字符串:application/json; charset=utf-8
val contentType = body.contentType()
if (contentType != null) {
requestBuilder.header("Content-Type", contentType.toString())
}
//body请求体的长度,okhttp内部会进行计算,然后设置请求头Content-Length
val contentLength = body.contentLength()
if (contentLength != -1L) {
//contentLength不是-1,表示是一次性传输说有数据,通过Content-Length设置长度值
requestBuilder.header("Content-Length", contentLength.toString())
//Transfer-Encoding: chunked表示分块传输数据,设置这个字段后会自动产生两个效果:
//1.Content-Length 字段会被忽略
//2.基于长连接持续推送动态内容
//所以响应头Transfer-Encoding和响应头Content-Length二者是不共存的,移除
requestBuilder.removeHeader("Transfer-Encoding")
} else {
//contentLength等于-1表示分块传输,比如常用的大文件上传,此时要移除Content-Length
//而对于普通的一次请求来说,都是直接计算出请求体长度,通过Content-Length设置长度值
//然后发起请求的
requestBuilder.header("Transfer-Encoding", "chunked")
requestBuilder.removeHeader("Content-Length")
}
}
//如果请求头中没有host,添加host
if (userRequest.header("Host") == null) {
requestBuilder.header("Host", userRequest.url.toHostHeader())
}
//如果请求头中没有Connection,添加Connection
//从HTTP/1.1起,默认使用长连接,用以保持连接特性,Keep-Alive不会永久保持连接,
//它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。
//实现长连接要客户端和服务端都支持长连接。长连接中关闭连接通过Connection:closed
//头部字段。如果请求或响应中的Connection被指定为closed,表示在当前请求或相应完成后将关闭TCP连接。
if (userRequest.header("Connection") == null) {
requestBuilder.header("Connection", "Keep-Alive")
}
//如果请求头中没有Accept-Encoding,并且没有Range,则添加Accept-Encoding为gzip,
//以支持gzip压缩,请求头Range表示分块请求,一次请求资源中的一部分,这种情况下不设置压缩
//服务端是否支持Range?客户端发起请求,收到响应后在response的header中
//如果有Accept-Ranges:bytes,表示支持Range。
var transparentGzip = false
if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
transparentGzip = true
requestBuilder.header("Accept-Encoding", "gzip")
}
//加载本地缓存的cookie,cookie需要自己实现,cookie就是一连串的key-value,见1
val cookies = cookieJar.loadForRequest(userRequest.url)
if (cookies.isNotEmpty()) {
requestBuilder.header("Cookie", cookieHeader(cookies))
}
//如果请求头中没有User-Agent, 添加,这里userAgent的值是okhttp/4.11.0
if (userRequest.header("User-Agent") == null) {
requestBuilder.header("User-Agent", userAgent)
}
//进入下一个拦截器
val networkResponse = chain.proceed(requestBuilder.build())
//从响应头中取出cookie缓存到本地
cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)
//通过返回的networkResponse构建一个新的responseBuilder,responseBuilder
//已经将所有的response信息拷贝过来了,然后将request引用赋给response,将二者关联起来
val responseBuilder = networkResponse.newBuilder()
.request(userRequest)
//如果请求头中添加过Accept-Encoding:gizp,并且响应头中包含响应头Content-Encoding,
//并且响应中含有body,则对body进行gzip压缩
//(服务端返回了Content-Encoding响应头,说明服务端接受了客户端要求的压缩方式,
//如果不接受,则不会存在这个响应头)
if (transparentGzip &&
"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&
networkResponse.promisesBody()) {
val responseBody = networkResponse.body
if (responseBody != null) {
val gzipSource = GzipSource(responseBody.source())
//移除header
val strippedHeaders = networkResponse.headers.newBuilder()
.removeAll("Content-Encoding")
.removeAll("Content-Length")
.build()
responseBuilder.headers(strippedHeaders)
val contentType = networkResponse.header("Content-Type")
//设置压缩后的body到response中
responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))
}
}
return responseBuilder.build()
}
1.cookie
.cookieJar(new CookieJar() {
@Override
public void saveFromResponse(@NonNull HttpUrl httpUrl, @NonNull List<Cookie> list) {
}
@NonNull
@Override
public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {
return null;
}
})
总结
BridgeInterceptor拦截器的实现逻辑是五大拦截器中最简答的,它做的事情仅仅是在请求前为请求设置请求头,并在请求完成后处理gzip压缩的逻辑。下一次我们会进入缓存拦截器CacheInterceptor的分析。