HTTP缓存

HTTP 缓存不是必须的,但重用缓存的资源通常是必要的。它可以减少服务器的压力,如果不使用缓存,每次发起请求都要求服务器发送相应数据,很多时候服务器发来的内容并没有发生变化,就会“浪费”服务器带宽。可以在客户端设置缓存,给缓存加上过期时间,如果期限没到就是用本地缓存的内容。然而常见的 HTTP 缓存只能存储 GET 响应,对于其他类型的响应则无能为力。

缓存头部

HTTP 相关的缓存头部一般有:

  • Cache-Control 通用的首部,它是缓存控制字段;
  • Expires 响应首部,代表资源过期时间;
  • Last-Modified 响应首部,表示资源的最新修改时间,由服务器告诉浏览器;
  • If-Modified-Since 请求首部,表示资源的最新修改时间,由浏览器告诉服务器。它和 Last-Modified 是一对,它俩用来做对比;
  • Etag 响应首部,用于资源标识,由服务器告诉浏览器;
  • If-None-Match 请求首部,缓存资源标识由服务器告诉服务器(就是上一次服务器给的 Etag)。它和 Etag 是一对,它俩用来做对比;

除了头部,有些状态码与缓存也有些关系:

  • 200 则表示为成功。一个包含例如 HTML 文档,图片,或者文件的响应。
  • 304 说明无需再次传输请求的内容,也就是说可以使用缓存的内容。
  • 206 不完全的响应,只返回局部的信息,常用在断点续传中。

Expires 响应首部很好理解,就是设置一个过期时间,值是一个 http 时间戳,如:

Expires: Wed, 21 Oct 2019 07:28:00 GMT

设置后,当客户端再次发送请求时就会检查 Expires 的过期时间,如果过期了就去向服务端发起请求,没过期就是用本地的缓存。如果不想使用缓存,可以将值设置成 0,即该资源已经过期。

Expires 有一个问题,假如缓存时间到了,需要重新向服务端获取数据,而服务端并没有更新内容,这就会造成“浪费”。最好需要一种比较“精确”的方式,当服务端真正更新数据时才让客户端使用新的内容,不然就让它使用缓存。

Last-ModifiedIf-Modified-Since 就是为了解决这个问题的。在客户端第一次请求某个资源时,服务器会发来一个 Last-Modified 头部,它与 Expires 头部的值很像,不过它表示的是资源做出修改的日期和时间。它可以与 Expires 头部一起使用。

当再次发起网络请求时,客户端会向服务器提供一个 If-Modified-Since 请求首部,如果之前响应带有 Expires 头部,会先检查缓存时间到了没,如果没到继续使用,过期了就请求服务器。服务器收到请求首部,拿到 If-Modified-Since 的日期,它是上一次响应首部 Last-Modified 的值,与现在文件的最新修改日期做对比,如果文件修改了,就返回修改后的文件内容和新的 Last-Modified 响应首部,状态码为 200,而如果发现文件并没有修改,就返回状态码 304,且不带有消息主体。

last-modified

last-modified 的不足

Last-Modified 响应首部的精确度不高,它只能精确到一秒。一个日访问量很大的网站,后端在某个时间修改了文件,在这个时间点可能会有很多人在访问该资源,总会有一些人收不到最新的资源(几毫秒内很多人访问,但 Last-Modified 精度却是“一秒”)。ETag 可以做到更“精确”。

浏览器与服务器在过期时间 Expires + Last-Modified 的基础上,增加一对文件内容的唯一对比标记 —— ETagIf-None-Match

如果资源更改,则一定要生成新的 Etag 值,Etag 类似于指纹。客户端再请求时,如果设定的 Expires 过期了,就会使用 If-None-Match 头将上一次响应时的 Etag 值带到后端(这个值之所以能获取到是因为浏览器把这个响应首部缓存了),后端用该值与最新的文件变动后的 ETag 值做对比,如果两个值不相同,就返回资源内容和新的 Etag 值,响应码为200;如果值相同,说明资源还没更新,就返回 304 状态码。

ETag 的行为与 Last-Modified 的行为很相似,都是做对比然后做出反馈。但 ETag 更精确,只要文件变更,唯一标识也会变更。这个唯一标识可以有多种方式生成,比如生成资源内容的散列值、最后修改时间的时间戳的哈希值或者简单的使用自己定义的版本号。

如果 If-None-MatchIf-Modified-Since 同时出现,If-None-Match 的优先级更高。

ETag 的值有强弱之分,强 ETag 值无论发生多么细微的变化都会改变其值。 弱 ETag 值比较宽松,只有资源发生了根本变化,产生差异时才会改变ETag的值。要将 ETag 值设置成弱比较需在字段值的最开始处附加 W/ 标记。如:

ETag: W/"as463c"

条件请求

形如 If-xxx 格式的请求首部字段可称之为条件请求,服务器在接收到这些条件请求时,只有判断条件为真才执行请求。除了上面用于缓存的 If-Modified-SinceIf-None-Match 两个条件请求之外,还有三个常见的条件请求:

  • If-Match 在请求方法为 GETHEAD 的情况下,它的值与 ETag 的值匹配一致时服务器才接受请求。而对于 PUT 或其他非安全方法来说,只有在满足条件的情况下才可以将资源上传。如果匹配不一致,则返回状态码 412(Precondition Failed,先决条件失败)的响应。
  • If-Range 这个请求首部的值也会与 ETag 值或更新的日期时间(Last-Modified)进行匹配,如果一致,那么就作为范围请求处理If-Range 应与 Range 请求首部一起使用。
  • If-Unmodified-Since 功能与 If-Modified-Since 相反,作用是告知服务器,在指定的日期事件之后资源如果 未发生更新,才处理请求 。如果在指定日期后发生了更新,则以状态码 412 作为相应返回。

If-Range 请求首部可以让 Range 头在满足一定条件时才起作用,而且服务器回复 206 部分内容状态码,以及 Range 首部字段请求的资源的相应部分。如果条件不满足,服务器将会返回 200 OK 状态码,并返回完整的请求资源。If-Range 头通常用于断点续传的下载过程中,如果上一次下载时中断了,这一次下载时确保资源没有发生改变(如果发生改变 ETag 或者 Last-Modified 就会变化,If-Range 与之对比发现不一致,就会重新下载,而不是接着上一次接着下载)

If-Match 请求首部通常也是搭配 Range 首部一起使用。这样可以保证新请求的范围与之前请求的范围是对同一份资源的请求,如果 ETagIf-Match 值不一致,说明不是同一份资源,或者这个资源已经被修改。If-Match 的值还可以是星号*,这表示服务器会忽略 ETag 的值,只要资源存在就处理请求。带有 If-Match 请求头时,服务器是无法使用弱ETag值的。

Expires 与 max-age

Expires 的值是“绝对”时间,哪一年哪一月都写得很清楚。服务端发来的 Expires 日期会缓存到客户端。这有一个问题,因为 Expires 用的是服务端的时间,如果客户端的时间与服务器的时间相差很大(客户端与服务端的时间不一致),就会出现很大的误差。比如服务端发去的 Expires 是四月一号,而客户端的日期已经是四月三号了,一对比就是过期的内容。

Cache-Control 有一个 max-age 指令,它是相对时间,如:

Cache-Control: max-age=604800

单位是秒,上面表示再过 604800 秒后该资源会被认为过期。因为是相对时间,即使客户端与服务端时间不一致也没关系。

如果 Expiresmax-age 同时设置,会优先处理 max-age,忽略掉 Expires 首部字段。

no-cache 与 no-store

Cache-Control 是通用的消息头部,通过指定指令来实现缓存机制,可以指定多个指令,指令以逗号分隔。有些指令前端、后端都可以去设置。no-cacheno-store 这两个指令就是“通用”的指令。

no-cache

中如果包含 no-cache 指令,表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求。设置 max-age=0 的功能与之类似。以客户端角度看,no-cache 表示强制向源服务器再次验证有效期,以服务端角度看,no-cache 表示资源可以缓存,但在每次使用前都要由服务端确认一下。

no-store

no-store 在客户端与服务端功能一样,表示不缓存请求或者响应的任何内容。这意味着每次请求都会发起网络请求拿到数据。对于机密或敏感的文件(如包含银行账户的 HTML 页面)最好使用这个指令。

must-revalidate

这个指令通常与 max-age 一起使用,当设定的 max-age 到期后,客户端会向服务端发起网络请求,验证缓存资源是否还有效。它像是延迟版的 no-cache

Cache-Control: max-age:600, must-revalidate

max-stale 与 min-fresh

这两个指令都有值,单位是秒,而且都是请求指令才拥有。

max-stale 表明客户端愿意接收一个已经过期的资源,即使已经过期也照常使用。如果不指定参数值,过期之后就会发起请求,接受响应,而如果设置了参数值,在指定的时间内,缓存仍会被接受。

min-fresh 表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。例如:

Cache-Control: min-fresh=100

在 100 秒内,资源的有效期限到了,这资源就无法作为响应返回。

Pragma

它是 HTTP/1.0 的通用头,它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器。它有一个 no-cache 指令,效果与 Cache-Control 中的 no-cache 一致。

总结

缓存的处理过程可以简单地分为几步:

  1. 首先在缓存中搜索指定资源的副本,如果命中就执行第二步;
  2. 对资源副本进行新鲜度检测(If-None-Match),检测文档是否过期,如果不新鲜就执行第三步;
  3. 客户端与服务器进行再验证,验证通过(即没有过期)就更新资源副本的新鲜度,再返回这个资源副本(此时的响应码为 304 Not Modified);
  4. 如果服务端验证不通过,就从服务器返回资源,再将最新资源的副本放入缓存中;

本文分享自微信公众号 - Neptune丶(Neptune_mh_0110),作者:多云转晴

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-05-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • HTTP缓存

    P.S.关于HTTP Header的更多信息,请查看4.2 Message Headers

    ayqy贾杰
  • HTTP 缓存

    先判断 Etag, 再判断 last-modified. 但是结果会由服务器决策.

    lucifer210
  • HTTP 缓存

    当某一个硬件要读取数据时候,会首先从缓存中查找数据,如果有,直接将数据返回,如果没有再从内存中获取数据。缓存获取数据的速度远比内存快。所以HTTP请求都采用缓存...

    Yif
  • 【HTTP】缓存

    随着用户访问量越来越大,缓存变得越来越重要。HTTP文件缓存可以减少冗余数据的传输;缓解网络瓶颈;降低对原始服务器的请求;以及降低距离延迟。

    奋飛
  • java http缓存

    HTTP/1.1中缓存的目的是为了在很多情况下减少发送请求,也即直接返回缓存;同时在许多情况下可以不需要发送完整响应。前者减少了网络回路的数量,挺高响应速度,H...

    xiangzhihong
  • 详解HTTP缓存

    HTTP缓存是一项重要且常见的web性能优化手段。当通过浏览器发送HTTP请求时,如果浏览器本地有所要请求的文档副本,那么浏览器可以直接从本地存储中读取该文档,...

    Bug开发工程师
  • 扒扒HTTP缓存

    摘要: 本文会从理论和实战两方面描述http缓存。理论层面会介绍:缓存命中、缓存丢失、Revalidations(重新验证)、命中率(Hit Rate)、字节...

    ImportSource
  • 图解 HTTP 缓存

    本文首发于政采云前端团队博客:图解 HTTP 缓存 https://www.zoo.team/article/http-cache

    前端劝退师
  • 图解 HTTP 缓存

    HTTP 的缓存机制,可以说这是前端工程师需要掌握的重要知识点之一。本文将针对 HTTP 缓存整体的流程做一个详细的讲解,争取做到大家读完整篇文章后,对缓存有一...

    政采云前端团队

扫码关注云+社区

领取腾讯云代金券