上一篇我們介紹了 OkHttp 的責任鏈以及第一個內置攔截器 —— 重試與重定向攔截器。本篇我們將剩余四個攔截器的解析做完。
1、橋接攔截器
BridgeInterceptor 作為請求準備和實際發送之間的橋梁,自動處理 HTTP 請求頭等繁瑣工作。比如設置請求內容長度,編碼,gzip 壓縮,Cookie 等,獲取響應后保存 Cookie 等。它的設計目的是為了解決開發者手動處理 HTTP 協議細節的麻煩,特別是那些必須做但很繁瑣或難以實現的工作。
它的攔截代碼 intercept() 如下:
class BridgeInterceptor(private val cookieJar: CookieJar) : Interceptor {@Throws(IOException::class)override fun intercept(chain: Interceptor.Chain): Response {// 1.前置工作:從責任鏈上獲取請求,添加相關請求頭val userRequest = chain.request()val requestBuilder = userRequest.newBuilder()val body = userRequest.bodyif (body != null) {val contentType = body.contentType()if (contentType != null) {requestBuilder.header("Content-Type", contentType.toString())}// 請求體內容長度如果不是 -1 意味著使用 Content-Length 這個請求頭展示內容大小,// 否則就是要使用 Transfer-Encoding: chunked 分塊傳輸的方式。這兩個頭互斥val contentLength = body.contentLength()if (contentLength != -1L) {requestBuilder.header("Content-Length", contentLength.toString())requestBuilder.removeHeader("Transfer-Encoding")} else {requestBuilder.header("Transfer-Encoding", "chunked")requestBuilder.removeHeader("Content-Length")}}if (userRequest.header("Host") == null) {requestBuilder.header("Host", userRequest.url.toHostHeader())}// Connection 頭自動開啟了長連接if (userRequest.header("Connection") == null) {requestBuilder.header("Connection", "Keep-Alive")}// 在沒有 Accept-Encoding 與 Range 這兩個請求頭的情況下,自動添加 gzip 壓縮數據var transparentGzip = falseif (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {transparentGzip = truerequestBuilder.header("Accept-Encoding", "gzip")}val cookies = cookieJar.loadForRequest(userRequest.url)if (cookies.isNotEmpty()) {requestBuilder.header("Cookie", cookieHeader(cookies))}if (userRequest.header("User-Agent") == null) {requestBuilder.header("User-Agent", userAgent)}// 2.中置工作:啟動責任鏈的下一個節點,做接力棒交接val networkResponse = chain.proceed(requestBuilder.build())// 3.后置工作:修改響應cookieJar.receiveHeaders(userRequest.url, networkResponse.headers)val responseBuilder = networkResponse.newBuilder().request(userRequest)// 如果在第 1 步中使用了 gzip 壓縮,那么這里在拿到響應 networkResponse 后,需要將響應體// responseBody 解壓后放到新的響應體 responseBuilder.body() 中if (transparentGzip &&"gzip".equals(networkResponse.header("Content-Encoding"), ignoreCase = true) &&networkResponse.promisesBody()) {val responseBody = networkResponse.bodyif (responseBody != null) {val gzipSource = GzipSource(responseBody.source())val strippedHeaders = networkResponse.headers.newBuilder().removeAll("Content-Encoding").removeAll("Content-Length").build()responseBuilder.headers(strippedHeaders)val contentType = networkResponse.header("Content-Type")responseBuilder.body(RealResponseBody(contentType, -1L, gzipSource.buffer()))}}return responseBuilder.build()}
}
橋接攔截器的攔截邏輯還是很清晰的,三步走:
- 前置工作為請求添加請求頭。當請求體長度 contentLength 不為 -1 時,添加 Content-Length 請求頭填入請求體的完整長度;否則意味著要使用分塊傳輸,添加
Transfer-Encoding: chunked
請求頭。這兩個頭互斥,只能存在一個 - 中置工作啟動下一個責任鏈節點,進而觸發緩存攔截器
- 后置工作就一項,如果在前置工作中啟動了 gzip 數據壓縮,那么在拿到響應后,要把響應體解壓放到新的響應中