Android开源框架系列-OkHttp4.11.0(kotlin版)-拦截器分析之BridgeInterceptor

177 阅读3分钟

前言

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的分析。