一.分类
按缓存的强势程度分为:

强缓存:有效期内,资源直接从本地缓存取(disk cache或memory cache);有效期外或强制刷新时,找server再要一份

协商缓存:有效期内,同上;有效期外或强制刷新时,带着本地版本号询问server资源是否有更新,得到回复304(更新过期时间等缓存状态,接着用本地版本)或200(把新版本缓存起来,本地版本扔掉)

其中,协商缓存可以细分为:

基于时间的:以资源修改时间(Last-Modified)为版本号

基于内容的:以资源内容hash(ETag)为版本号

协商是缓存失效(过期或弃用)之后才会发生的事情

二.相关Header字段
HTTP Header字段分为4类:

general-header(通用头):同时适用于请求和响应消息

request-header(请求头):允许client传递额外的信息给server,请求修饰符,作用相当于参数

response-header(响应头):允许server传递关于该响应的额外信息给client,额外信息包括server相关的,以及将来访问该资源需要的一些信息

entity-header(实体头):给出消息实体相关的meta信息,如果没有消息实体的话,就是与请求对应的资源的信息

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

Pragma
HTTP 1.0通用头字段,指定缓存策略

Pragma = "Pragma" ":" 1#pragma-directive
pragma-directive = "no-cache" | extension-pragma
extension-pragma = token [ "=" ( token | quoted-string ) ]
Pragma是一个含义模糊的字段,RFC仅指定了Pragma: no-cache出现在请求中时,即便缓存有效,也应该回源去取新的,与Cache-Control: no-cache等价。出现在响应中时,没有明确含义

P.S.关于Pragma的更多信息,请查看14.32 Pragma

Expires
HTTP 1.0实体头字段,表示资源的过期时间,指定过期策略

Expires = "Expires" ":" HTTP-date
一个精确的时间点,在此之前,缓存有效。这个时间点由server给出,如果client与server的时间不同步,缓存过期策略就不可靠了

无法保证Expires给出的时间点在client和srever对应同一个时刻,所以HTTP 1.1新增了可以通过Cache-Control: max-age=<seconds>来定义保质期,给一个时间段,从client拿到资源后,再过seconds秒缓存过期,这样就只依赖client时间,不要求一致性了

Cache-Control
通用头字段,指定缓存策略和过期策略

Cache-Control   = "Cache-Control" ":" 1#cache-directivecache-directive = cache-request-directive    | cache-response-directivecache-extension = token [ "=" ( token | quoted-string ) ]

响应头中可以出现9个值:

cache-response-directive =    ; 资源将被客户端和代理服务器缓存    "public"    ; 资源仅被客户端缓存,不允许代理服务器缓存    | "private" [ "=" <"> 1#field-name <"> ]    ; 不先回源检查的话,不允许复用资源    | "no-cache" [ "=" <"> 1#field-name <"> ]    ; 资源不允许被写入缓存    | "no-store"    ; 禁止代理服务器修改Content-Encoding,Content-Range,Content-Type字段    | "no-transform"    ; 不允许使用过期的资源,一旦过期,必须回源验证(即使客户端愿意接受过期资源)    | "must-revalidate"    ; 依赖public,类似于must-revalidate,仅适用于代理服务器    | "proxy-revalidate"    ; 缓存资源,但是在指定时间(单位为秒)后缓存过期    | "max-age" "=" delta-seconds    ; 依赖public,只在代理服务器上有效,覆盖max-age    | "s-maxage" "=" delta-seconds    ; 自定义扩展值    | cache-extension

请求头中可以出现7个值:

cache-request-directive =    ; 强制回源,不要来自缓存的内容    "no-cache"    ; 不允许把客户端请求相关信息写入缓存    | "no-store"    ; 客户端愿意接受age(代理服务器缓存时间)不超过delta秒的资源    | "max-age" "=" delta-seconds    ; 客户端愿意接受过期delta秒内的旧内容    | "max-stale" [ "=" delta-seconds ]    ; 客户端希望响应内容在delta秒内都是有效的    | "min-fresh" "=" delta-seconds    ; 客户端不接受经过转换的内容,例如Content-Type    | "no-transform"    ; 客户端只想要已缓存的资源,不重新请求资源    | "only-if-cached"    ; 自定义扩展值    | cache-extension

注意no-store, no-cache, must-revalidate描述间的细微差异,同一字段出现在请求头和响应头中的含义也都不同

Last-Modified
实体头字段,表示资源的最后修改时间,指定协商策略

Last-Modified = "Last-Modified" ":" HTTP-date
客户端拿到之后会保存起来,下一次向server请求资源时,会带上这个时间点作为版本号,验证本地缓存资源是否仍然可用

资源被修改过,但内容没变的话,发一份内容一样的响应就显得多余了,所以也提供了基于内容的协商缓存,避免这种情况

P.S.优先级低于Cache-Control: max-age,同时出现时,以max-age为准

If-Modified-Since
请求头字段,基于时间的协商策略实现需要,比较资源最后修改时间(Last-Modified,资源最后修改时间)是否一致

If-Modified-Since = "If-Modified-Since" ":" HTTP-date

把Last-Modified版本号作为字段值发回给server,资源没更新就返回304不给响应体,更新了就返回200,把新版本内容作为响应体

If-Unmodified-Since
同上,行为相反(比较资源最后修改时间是否不一致),如果不一致并且method为POST/PUT等更新操作时,返回412(Precondition Failed,条件不满足)表示更新执行失败

ETag
响应头字段,表示资源的内容hash,指定协商策略

ETag = "ETag" ":" entity-tag

客户端会记下这个值,下一次请求该资源时作为版本号传回给server

P.S.ETag优先级比Last-Modified高

If-Match
请求头字段,基于内容的协商策略实现需要,比较该字段的值(ETag,资源内容hash)是否一致

If-Match = "If-Match" ":" ( "*" | 1#entity-tag )

如果不一致,并且method为POST/PUT等更新操作时,返回412表示更新失败

If-None-Match
同上,行为相反(比较该字段的值是否不一致),如果一致,返回304告诉客户端可以沿用缓存版本,否则返回新资源

Age
响应头字段,表示资源在代理服务器上已缓存的时间

Age = "Age" ":" age-valueage-value = delta-seconds

计算方式为:

/* * age_value *      is the value of Age: header received by the cache with *              this response. * date_value *      is the value of the origin server's Date: header * request_time *      is the (local) time when the cache made the request *              that resulted in this cached response * response_time *      is the (local) time when the cache received the *              response * now *      is the current (local) time */apparent_age = max(0, response_time - date_value);corrected_received_age = max(apparent_age, age_value);response_delay = response_time - request_time;corrected_initial_age = corrected_received_age + response_delay;resident_time = now - response_time;current_age   = corrected_initial_age + resident_time;

Age:0表示刚从源server取过来,正值表示上次从源取过来到现在经过的秒数

三.强缓存与协商缓存
分别发生在缓存的不同阶段,缓存生效时走强缓存,不发请求,缓存失效后才走协商缓存,发请求询问资源更新与否

强缓存
响应内容命中强缓存后,缓存有效期内,浏览器不会向server发起请求,而是直接从本地缓存(disk cache或memory cache)读取

只要本地有该资源的缓存版本,并且Cache-Control: max-age 或Expires没有过期,就能命中强缓存

协商缓存
缓存过期之后,再次访问该资源,浏览器会带上本地缓存版本号去询问server,server检查客户端递过来的ETag或Last-Modified值,告诉客户端要不要更新缓存

响应头中的ETag和Last-Modified是协商缓存的开关,协商缓存的好处是内容没变的话,直接返回304,不用传输响应体

四.启发式缓存
一种比较特殊的情况是响应头没有提供任何缓存相关的信息,此时浏览器会使用一个启发式算法来确定资源缓存期限:

max-age = Date - Last-Modified / 10

默认的缓存策略,就叫启发式缓存,启发式是说基于经验构造的,没有严格的依据

五.刷新行为
浏览器有3种不同的刷新行为,在验证HTTP缓存时很容易被迷惑:

开新页面:打开新tab或者窗口,访问页面

普通刷新:点击刷新按钮、地址栏回车、CMD + R

强制刷新:CMD + Shift + R、Chrome长按刷新按钮,选择硬性重新加载

禁用缓存再刷新:勾选Disable cache设置,再开新页面/刷新

开新页面
请求头不带缓存相关字段,如果本地缓存版本有效,从缓存读取,不发请求,并显示个假请求头:

Request Headers    Provisional headers are shown    Upgrade-Insecure-Requests:1    User-Agent:...

响应头沿用缓存的那份

普通刷新
不从缓存取,一定会向服务发起请求,请求头会带上If-Modified-Since和If-None-Match等缓存头(如果有的话),此外还会擅自添上:

Cache-Control:max-age=0

要求代理服务器检查缓存是否过期

P.S.普通刷新行为发生时,浏览器一定会发起请求,即便资源缓存仍然有效,理应处于强缓存状态。因为用户要求刷新内容,希望看到新的,而关联的资源(比如该页面含有的CSS,JS等资源)不会被强制发起请求

强制刷新
同样会强制发起请求,带上缓存相关信息,还会擅自添上:

Cache-Control:max-age=0Pragma:no-cache

要求回源去取新的,即便缓存没过期

禁用缓存再刷新
禁用缓存后,后续所有请求都会被添上:

Cache-Control:max-age=0Pragma:no-cache

相当于全都走强制刷新,包括关联资源

P.S.Cache-Control:max-age=0,Pragma:no-cache的具体行为依赖serve

实现,实际上代理服务器不一定会回源或者检查过期

参考资料
Hypertext Transfer Protocol — HTTP/1.1:RFC 2616

浏览器缓存机制剖析:缓存机制流程图不错,Header字段含义描述不正确

HTTP缓存控制小结:内容很准确,且较全面

Increasing Application Performance with HTTP Cache Headers

浏览器的刷新和缓存

Difference between no-cache and must-revalidate

MDN | Cache-Control

MDN | Age

Why is this response being cached?

What heuristics do browsers use to cache resources not explicitly set to be cachable?

更多相关文章

  1. 学习资源大汇总
  2. 小程序静态资源如何设置防盗链?
  3. 全栈资源共享 一起成长,努力成为你想成为的样子
  4. 10 行 Java 代码实现最近被使用( LRU )缓存
  5. 用 cURL 请求测试 ETag 浏览器缓存[每日前端夜话0xCC]
  6. 用 NodeJS 充分利用多核 CPU 的资源[每日前端夜话0xCB]
  7. 在 HTML 中包含资源的新思路[每日前端夜话0xC3]
  8. 如何防止缓存.NET JSON源
  9. 如何防止jQuery Ajax请求在Internet Explorer中缓存?

随机推荐

  1. Linux学习:unix的标准化的实现(Linux中各种
  2. 艿艿的 Spring Cloud Alibaba!开整~
  3. 37000 字 + 代码,艿艿肝的 Shiro 从入门到
  4. Linux学习:Linux基础简介。
  5. HTTP/2:更快的页面加载时间[每日前端夜话0
  6. 最强 Spring Cloud 注册中心 Nacos,和艿艿
  7. 答应我,别再if/else走天下了可以吗
  8. 2021-1-17
  9. 在 Node.js 中转换 SVG 图像格式[每日前
  10. 可能是第二好的 Spring OAuth 2.0 文章,艿