OkHttp 系列五:桥接拦截器与缓存拦截器

241 阅读5分钟

桥接拦截器与缓存拦截器

本文概述:

  • 本文探究了OkHttp 中的桥接拦截器与缓存拦截器;在桥接拦截器中介绍了其工作职责、工作流程,以及两个源码细节(拿不到的响应体长度、Cookie 的处理);在缓存拦截器中介绍了Http 缓存策略及执行逻辑,深入源码讨论了OkHttp 是如何判断缓存可用;

桥接拦截器:

职责:帮助我们去补全请求头(例如,在OkHttp 中不必须要手动配置Host )

  • 将用户的请求对象作为参数,实例化新的Request 对象,并为其补全请求头后,将这个新的请求对象交给下一个拦截器处理并得到响应结果;

  • 拿到响应结果后:

    • 保存Cookie:如果有,就保存Cookie,回调之前配置OkHttpClient 整的CookieJar 中的saveFromCookie;
    • 解析GZIP:如果服务端使用了GZIP 压缩数据,那么此时就会进行解压
    • 最后将处理好的response 返回给上一个拦截器
  • 示意图:

    image-20220730204020184

  • 源码细节一:如果拿不到的响应体长度 ---> 使用分块编码(Http 协议的东西)

     if (contentLength != -1L) {
         requestBuilder.header("Content-Length", contentLength.toString())
         requestBuilder.removeHeader("Transfer-Encoding")
     } 
    
  • 源码细节二:Cookie 的处理

     //如果说,在配置OkHttpClient 设置了CookieJar(这是一个接口,内部两个方法,保存Cookie以及加载Cookie),那么在桥接拦截器中就会回调这个加载Cookie 的方法,将这个Cookie 设置到请求头Cookie 中去;
     val cookies = cookieJar.loadForRequest(userRequest.url)
     if (cookies.isNotEmpty()) {
         requestBuilder.header("Cookie", cookieHeader(cookies))
     }
    

缓存拦截器:

Http 的缓存是怎么玩的?

  • 强缓存:浏览器不会向服务器发送请求而是将本地的缓存发给用户

  • 怎么判断是强缓存:根据Http 响应头的字段

    • Expires:缓存过期时间

      • 如果当前时间小于缓存过期时间 ---> 将本地缓存返回给用户
    • Cache-Controller:是否使用强缓存的字段,有并且符合条件就使用强缓存

      • max - age:秒,资源最大有效时间

      • immutable:响应资源不会变

        • 有这个字段,直接使用强缓存
      • min-fresh:请求缓存最小新鲜度(用户任务这个缓存)

      • no - cache:不使用缓存

      • no - store:不允许资源被缓存

  • 协商缓存:浏览器会向服务端发请求,根据服务端响应头字段判断是否命中协商缓存,命中返回304,客户端使用协商缓存返回给用户;没有命中,服务端回传数据,不使用缓存;

    • 服务端响应304 :此时是没有响应体的

      • 告诉客户端,去使用本地缓存
    • 协商缓存的响应头字段:举例说明第一套

      • 客户端的请求字段中包含If - Modified (这是一个时间),服务端拿到这个时候,判断自己在指定的时间内有没有修改资源;如果没有,服务端返回304(没有响应体 ---> 让客户端去使用本地的协商缓存);如果修改了,服务端正常返回响应体;
      • 注意服务端的响应头中会有一个字段:Last - Modified (代表资源最后的修改时间)
      • 这两个是配套使用的将Last - Modified 拿给If - Modified
    • 协商缓存的响应头字段:举例说明第二套

      • 服务端响应头包含字段ETag (表示资源在服务端的唯一标识)
      • 客户端请求体包含字段If-None - Match (服务端拿到这个东西跟ETag 值进行比较,如果匹配,就返回304 ---> 客户端使用协商缓存)

深入探究OkHttp 缓存拦截器

  • 怎么实现的:将缓存的判定封装在缓存策略中,通过compute 方法判断是否可以使用缓存

  • 这个缓存指的是强缓存,协商缓存是需要去请求服务器的(这个是拿给下一个拦截器处理的)

  • 源码分析:不仅实现了Http 缓存,自己还搞了自己的

    • 拿到缓存策略对象:调用compute 方法

       val strategy = CacheStrategy.Factory(now, chain.request(), cacheCandidate).compute()
      
    • 对类属性进行赋值:

      • 业务逻辑:

        image-20220730213013925

       val networkRequest = strategy.networkRequest
       val cacheResponse = strategy.cacheResponse
      
      • 什么情况下会两个都为空?

        • 如果说请求头携带字段XXX (代表强制使用缓存),但是此时没有缓存或者缓存已经失效了,那么就会两个都为空;此时OkHttp 就会在本地创建一个Response 并且将响应码置为504

          image-20220730213746298

        • 指定本次请求一定要使用缓存

          image-20220730213522810

      • 当两个都不为空时:使用协商缓存
  • OkHttp 是怎么判断缓存可用的?

    1. 首选判断有没有缓存:

      • 有:继续向下

      • 没有:判断是否指定了必须使用缓存

         Cache-Control:only-if-cached 
        
        • 是:OkHttp 响应504
        • 没有:请求服务端(不能使用缓存都会走到这里)
    2. 缓存存在:判断是否为Https 请求 :

      • 是:在缓存的响应中包含握手信息(咩有握手信息这个缓存不能用)
    3. 判断缓存响应码与响应头:

      • 为200、203、204、300、301、404、405、410、414、501、308、302 中的一个

        • 如果是临时重定向:需要包含一些特殊的字段
      • 并且:请求头和缓存的响应头不包含Cache - Control : no-store

        • 意味着缓存能用 ---> 那么继续向下走
    1. 判断用户请求配置:

      • 请求头中没有设置Cache - Control : no-cache (客户端说明了,我不适用缓存),没有设置only-if-cached (表示这次用的是协商缓存),没有设置If - Modified (表示这次用的是协商缓存)

        • 表示我还是希望直接使用缓存 (强缓存)
    2. 如果说响应资源不变:

      • immutable:响应资源不会变

        • 有这个字段,可以直接使用强缓存
        • 没有:那么去判断资源是否失效(缓存存活时间 < 缓存有效时间)