Android 网络全栈攻略(五)—— 从 OkHttp 拦截器来看 HTTP 协议二

上一篇我们介绍了 OkHttp 的责任链以及第一个内置拦截器 ------ 重试与重定向拦截器。本篇我们将剩余四个拦截器的解析做完。

1、桥接拦截器

BridgeInterceptor 作为请求准备和实际发送之间的桥梁,自动处理 HTTP 请求头等繁琐工作。比如设置请求内容长度,编码,gzip 压缩,Cookie 等,获取响应后保存 Cookie 等。它的设计目的是为了解决开发者手动处理 HTTP 协议细节的麻烦,特别是那些必须做但很繁琐或难以实现的工作。

它的拦截代码 intercept() 如下:

kotlin 复制代码
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.body
    if (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 = false
    if (userRequest.header("Accept-Encoding") == null && userRequest.header("Range") == null) {
      transparentGzip = true
      requestBuilder.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.body
      if (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()
  }
}

桥接拦截器的拦截逻辑还是很清晰的,三步走:

  1. 前置工作为请求添加请求头。当请求体长度 contentLength 不为 -1 时,添加 Content-Length 请求头填入请求体的完整长度;否则意味着要使用分块传输,添加 Transfer-Encoding: chunked 请求头。这两个头互斥,只能存在一个
  2. 中置工作启动下一个责任链节点,进而触发缓存拦截器
  3. 后置工作就一项,如果在前置工作中启动了 gzip 数据压缩,那么在拿到响应后,要把响应体解压放到新的响应中
相关推荐
誰能久伴不乏17 分钟前
深入解析 TCP 连接状态与进程挂起、恢复与关闭
服务器·网络·tcp/ip
wkj00124 分钟前
php中调用对象的方法可以使用array($object, ‘methodName‘)?
android·开发语言·php
Dreamboat¿25 分钟前
小皮面板搭建pikachu
运维·服务器·网络
wkj00131 分钟前
php use 命名空间与 spl_autoload_register的关系
android·php·android studio
fatiaozhang952738 分钟前
浪潮CD1000-移动云电脑-RK3528芯片-2+32G-开启ADB ROOT破解教程
android·adb·电视盒子·机顶盒rom·魔百盒刷机·外贸盒子
hudawei9961 小时前
kotlin中withContext,async,launch几种异步的区别
android·开发语言·kotlin
消失的旧时光-19431 小时前
Kotlin 常用语法糖完整整理
android·开发语言·kotlin
coderlin_9 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
古希腊数通小白(ip在学)9 小时前
stp拓扑变化分类
运维·服务器·网络·智能路由器
2501_9159184110 小时前
Fiddler中文版全面评测:功能亮点、使用场景与中文网资源整合指南
android·ios·小程序·https·uni-app·iphone·webview