HTTP缓存

一.分类

按缓存的强势程度分为:

  • 强缓存:有效期内,资源直接从本地缓存取(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-directive
cache-directive = cache-request-directive
   | cache-response-directive
cache-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-value
age-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-ageExpires没有过期,就能命中强缓存

协商缓存

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

响应头中的ETagLast-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-SinceIf-None-Match等缓存头(如果有的话),此外还会擅自添上:

Cache-Control:max-age=0

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

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

强制刷新

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

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

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

禁用缓存再刷新

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

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

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

P.S.Cache-Control:max-age=0Pragma:no-cache的具体行为依赖server实现,实际上代理服务器不一定会回源或者检查过期

参考资料

  • 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?

本文分享自微信公众号 - 前端向后(backward-fe)

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

原始发表时间:2017-09-10

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • CentOS7上利用packstack快速部署OpenStack Queens测试环境

    版权声明:本文为木偶人shaon原创文章,转载请注明原文地址,非常感谢。 https://b...

    shaonbean
  • 挑战程序竞赛系列(17):3.1最大化平均值

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    用户1147447
  • 书籍《深入理解Spring Cloud 与微服务构建》勘误、源码下载

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接 ,博主地址:http://blog.csd...

    方志朋
  • 统计学习方法资源汇总

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    用户1147447
  • HTTP协议基础学习

    版权声明:本文为木偶人shaon原创文章,转载请注明原文地址,非常感谢。 https://b...

    shaonbean
  • Jumpserver 1.0 安装脚本

    版权声明:本文为木偶人shaon原创文章,转载请注明原文地址,非常感谢。 https://b...

    shaonbean
  • CentOS7 安装并使用Ovirt 4.2

    版权声明:本文为木偶人shaon原创文章,转载请注明原文地址,非常感谢。 https://b...

    shaonbean
  • CentOS7 安装GlusterFS

    版权声明:本文为木偶人shaon原创文章,转载请注明原文地址,非常感谢。 https://b...

    shaonbean
  • 挑战程序竞赛系列(64):4.7字符串上的动态规划(2)

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    用户1147447
  • SpringBoot非官方教程 | 终章:文章汇总

    版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接 ,博主地址:http://blog.csd...

    方志朋

扫码关注云+社区

领取腾讯云代金券