HTTP浏览器缓存策略和相关首部字段

本文参考资源:

HTTP强缓存和协商缓存 - JavaScript学习笔记 - SegmentFault 思否

(七)条件请求If-Match、If-Modified-Since、If-None-Match、If-Range、If-Unmodified-Since_Python_FixedStar 的博客-CSDN博客

强制缓存(200)和协商缓存(304)整理_网络_zlong的博客-CSDN博客

在一次HTTP通信中,最接近用户的缓存应该就是浏览器的缓存了,而浏览器到底什么时候走缓存,什么时候请求服务器呢?和服务器有关吗?这篇文章里,就来谈一谈浏览器的缓存策略。

浏览器缓存概述

浏览器缓存是指浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时,浏览器就可以直接从本地磁盘加载文档。

浏览器缓存有以下优点:
1. 减少冗余数据传输
2. 减少服务器负担
3. 加载客户端加载网页的速度

浏览器请求资源流程图

HTTP浏览器缓存策略和相关首部字段

这张图里涉及到两个概念:强缓存协商缓存,浏览器HTTP缓存可以分为强缓存和协商缓存。强缓存和协商缓存最大也是最根本的区别是:强缓存命中的话不会发请求到服务器(比如 chrome 中的 200 from memory cache),协商缓存一定会发请求到服务器,通过资源的请求首部字段验证资源是否命中协商缓存,如果协商缓存命中,服务器会将这个请求返回,但是不会返回这个资源的实体,而是通知客户端可以从缓存中加载这个资源(304 not modified)。

强缓存

什么时候会使用强缓存?打开页面请求资源(或前进、后退、重定向)的时候会先获取该资源缓存的header信息,然后根据header中的Cache-ControlExpires来判断是否过期,如果发现本地缓存还没过期,则使用强缓存。

强缓存利用http头中的ExpiresCache-Control两个字段来控制,用来表示资源缓存时间。但只要刷新就不会使用强缓存,普通刷新(F5或点击刷新按钮)会忽略它,但是不会清除它,并且请求会带上Cache-Control: max-age=0。强制刷新(Ctrl+F5)则会清除缓存。并且请求会带上Cache-Control:no-cache,代表该请求不会使用缓存,且不会记录缓存。

控制强缓存的字段(选其一使用即可):

Expires

Expires是HTTP/1.0的规范,它的值是一个绝对时间的GMT格式的时间字符串,代表资源的失效时间,只要发送的请求在Expires之前,那么本地缓存始终有效,则在缓存中读取数据。所以这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当服务器与客户端时间偏差较大时,就会导致缓存混乱。如果同时出现Cache-Control:max-age和Expires,那么max-age的优先级更高

Cache-Control

Cache-Control 是一个通用首部字段(请求头和响应头都可以用),也是 HTTP/1.1 控制浏览器缓存的主流字段。和浏览器缓存相关的是如下几个响应指令:

  • max-age(单位为 s):设置缓存的存在时间,相对于发送请求的时间(即响应头中的Date字段)。只有响应报文首部设置Cache-Control非0的max-age或者设置了大于请求日期的 Expires才有可能命中强缓存。
  • no-cache 表示请求必须先与服务器确认缓存的有效性,如果有效才能使用缓存(协商缓存),无论是响应报文首部还是请求报文首部出现这个字段均一定不会命中强缓存。浏览器强制刷新就会带上Cache-Control:no-cache
  • no-store:不允许缓存,用于某些变化非常频繁的数据,例如秒杀页面,一定不会出现强缓存和协商缓存
  • must-revalidate:如果缓存不过期就可以继续使用,但过期了如果还想用就必须去服务器验证。这个值通常配合max-age使用。
  • public:表明响应可以被浏览器、CDN 等等缓存。
  • private:响应只作为私有的缓存,不能被 CDN 等缓存。如果要求 HTTP 认证,响应会自动设置为 private。

协商缓存

协商缓存就是由服务器来确定缓存的资源是否可用,所以客户端与服务器要通过某种标识来进行通信,从而让服务器判断请求资源是否可以缓存访问。

当缓存的max-age过期或浏览器普通刷新,就会启用协商缓存。浏览器会向服务器端发送请求,这个请求会携带第一次请求返回的有关缓存的header字段信息,比如客户端会通过If-None-Match头将先前服务器端发送过来的Etag发送给服务器,服务会对比这个客户端发过来的Etag是否与服务器的相同,若相同,就将If-None-Match的值设为false,返回状态304,客户端继续使用本地缓存,不解析服务器端发回来的数据,若不相同就将If-None-Match的值设为true,返回状态为200,客户端重新解析服务器端返回的数据;客户端还会通过If-Modified-Since头将先前服务器端发过来的最后修改时间戳(Last-Modified)发送给服务器,服务器端通过这个时间戳判断客户端的页面是否是最新的,如果不是最新的,则返回最新的内容,如果是最新的,则返回304,客户端继续使用本地缓存。

控制协商缓存的字段(选其一使用即可):

Last-Modified/If-Modified-Since

If-Modified-Since 是一个请求首部字段,并且只能用在 GET 或者 HEAD 请求中。Last-Modified 是一个响应首部字段,包含服务器认定的资源作出修改的日期及时间。当带着 If-Modified-Since 头访问服务器请求资源时,服务器会检查 Last-Modified,如果 Last-Modified 的时间早于或等于If-Modified-Since 则会返回一个不带主体的 304 响应,否则将重新返回资源。

ETag/If-None-Match

ETag 是一个响应首部字段,它是根据实体内容生成的一段 hash 字符串,标识资源的状态,由服务端产生。If-None-Match 是一个条件式的请求首部。如果请求资源时在请求首部加上这个字段,值为之前服务器端返回的资源上的 ETag,则当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的时候,服务器才会返回带有所请求资源实体的 200 响应,否则服务器会返回不带实体的 304 响应。 ETag 优先级比 Last-Modified 高,同时存在时会以 ETag 为准。

HTTP浏览器缓存策略和相关首部字段

ETag 还有“强”“弱”之分:强 ETag 要求资源在字节级别必须完全相符,弱 ETag 在值前有个“W/”标记,只要求资源在语义上没有变化,但内部可能会有部分发生了改变(例如 HTML 里的标签顺序调整,或者多了几个空格)。

下面是一次etag命中(你可以打开开发者工具,尝试刷新我的博客,观察结果):

请求头

响应头

因为 ETag 的特性,所以相较于 Last-Modified 有一些优势:

  1. 某些情况下服务器无法获取资源的最后修改时间
  2. 资源的最后修改时间变了但是内容没变,使用 ETag可以正确缓存
  3. 如果资源修改非常频繁,在秒以下的时间进行修改,Last-Modified 只能精确到秒

协商缓存属于HTTP条件请求的一部分,条件请求还有If-Unmodified-SinceIf-MatchIf-Range等字段(用于断点续传,和206状态码有关)

详细总结流程图

HTTP浏览器缓存策略和相关首部字段

注意:Pragma已被废弃。

下表是用户操作对强缓存/协商缓存的影响:

用户操作 Expires/Cache-Control Last-Modified/Etag
地址栏回车 有效 有效
页面链接跳转 有效 有效
新开窗口 有效 有效
前进后退 有效 有效
F5刷新 无效 有效
Ctrl+F5强制刷新 无效 无效

需要注意的是,我在chrome中实验发现,当服务器没有响应Cache-Control、Expire相关字段的时候,一定时间内再次访问网站依然会走200(from disk cache)强缓存,可能是和浏览器本身的策略有关,或是Cache-Control有默认值?我在网络上没有查询到资料明确指出Cache-Control是否有默认值,或者max-age的默认值为多少。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/http%e6%b5%8f%e8%a7%88%e5%99%a8%e7%bc%93%e5%ad%98%e7%ad%96%e7%95%a5%e5%92%8c%e7%9b%b8%e5%85%b3%e9%a6%96%e9%83%a8%e5%ad%97%e6%ae%b5/

发表评论

电子邮件地址不会被公开。