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-Modified
和 If-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
精度却是“一秒”)。ETag
可以做到更“精确”。
浏览器与服务器在过期时间 Expires
+ Last-Modified
的基础上,增加一对文件内容的唯一对比标记 —— ETag
和 If-None-Match
。
如果资源更改,则一定要生成新的 Etag
值,Etag
类似于指纹。客户端再请求时,如果设定的 Expires
过期了,就会使用 If-None-Match
头将上一次响应时的 Etag
值带到后端(这个值之所以能获取到是因为浏览器把这个响应首部缓存了),后端用该值与最新的文件变动后的 ETag
值做对比,如果两个值不相同,就返回资源内容和新的 Etag
值,响应码为200
;如果值相同,说明资源还没更新,就返回 304
状态码。
ETag
的行为与 Last-Modified
的行为很相似,都是做对比然后做出反馈。但 ETag
更精确,只要文件变更,唯一标识也会变更。这个唯一标识可以有多种方式生成,比如生成资源内容的散列值、最后修改时间的时间戳的哈希值或者简单的使用自己定义的版本号。
如果 If-None-Match
和 If-Modified-Since
同时出现,If-None-Match
的优先级更高。
ETag
的值有强弱之分,强 ETag 值无论发生多么细微的变化都会改变其值。
弱 ETag 值比较宽松,只有资源发生了根本变化,产生差异时才会改变ETag
的值。要将 ETag 值设置成弱比较需在字段值的最开始处附加 W/
标记。如:
ETag: W/"as463c"
形如 If-xxx
格式的请求首部字段可称之为条件请求,服务器在接收到这些条件请求时,只有判断条件为真才执行请求。除了上面用于缓存的 If-Modified-Since
和 If-None-Match
两个条件请求之外,还有三个常见的条件请求:
GET
和 HEAD
的情况下,它的值与 ETag
的值匹配一致时服务器才接受请求。而对于 PUT
或其他非安全方法来说,只有在满足条件的情况下才可以将资源上传。如果匹配不一致,则返回状态码 412
(Precondition Failed,先决条件失败)的响应。ETag
值或更新的日期时间(Last-Modified
)进行匹配,如果一致,那么就作为范围请求处理,If-Range
应与 Range
请求首部一起使用。If-Modified-Since
相反,作用是告知服务器,在指定的日期事件之后资源如果 未发生更新,才处理请求 。如果在指定日期后发生了更新,则以状态码 412
作为相应返回。If-Range
请求首部可以让 Range
头在满足一定条件时才起作用,而且服务器回复 206
部分内容状态码,以及 Range
首部字段请求的资源的相应部分。如果条件不满足,服务器将会返回 200 OK
状态码,并返回完整的请求资源。If-Range
头通常用于断点续传的下载过程中,如果上一次下载时中断了,这一次下载时确保资源没有发生改变(如果发生改变 ETag
或者 Last-Modified
就会变化,If-Range
与之对比发现不一致,就会重新下载,而不是接着上一次接着下载)
If-Match
请求首部通常也是搭配 Range
首部一起使用。这样可以保证新请求的范围与之前请求的范围是对同一份资源的请求,如果 ETag
与 If-Match
值不一致,说明不是同一份资源,或者这个资源已经被修改。If-Match
的值还可以是星号*
,这表示服务器会忽略 ETag
的值,只要资源存在就处理请求。带有 If-Match
请求头时,服务器是无法使用弱ETag
值的。
Expires
的值是“绝对”时间,哪一年哪一月都写得很清楚。服务端发来的 Expires
日期会缓存到客户端。这有一个问题,因为 Expires
用的是服务端的时间,如果客户端的时间与服务器的时间相差很大(客户端与服务端的时间不一致),就会出现很大的误差。比如服务端发去的 Expires
是四月一号,而客户端的日期已经是四月三号了,一对比就是过期的内容。
Cache-Control 有一个 max-age
指令,它是相对时间,如:
Cache-Control: max-age=604800
单位是秒,上面表示再过 604800
秒后该资源会被认为过期。因为是相对时间,即使客户端与服务端时间不一致也没关系。
如果 Expires
与 max-age
同时设置,会优先处理 max-age
,忽略掉 Expires
首部字段。
Cache-Control
是通用的消息头部,通过指定指令来实现缓存机制,可以指定多个指令,指令以逗号分隔。有些指令前端、后端都可以去设置。no-cache
和 no-store
这两个指令就是“通用”的指令。
中如果包含 no-cache
指令,表示客户端可以缓存资源,每次使用缓存资源前都必须重新验证其有效性。这意味着每次都会发起 HTTP 请求。设置 max-age=0
的功能与之类似。以客户端角度看,no-cache
表示强制向源服务器再次验证有效期,以服务端角度看,no-cache
表示资源可以缓存,但在每次使用前都要由服务端确认一下。
no-store
在客户端与服务端功能一样,表示不缓存请求或者响应的任何内容。这意味着每次请求都会发起网络请求拿到数据。对于机密或敏感的文件(如包含银行账户的 HTML 页面)最好使用这个指令。
这个指令通常与 max-age
一起使用,当设定的 max-age
到期后,客户端会向服务端发起网络请求,验证缓存资源是否还有效。它像是延迟版的 no-cache
。
Cache-Control: max-age:600, must-revalidate
这两个指令都有值,单位是秒,而且都是请求指令才拥有。
max-stale
表明客户端愿意接收一个已经过期的资源,即使已经过期也照常使用。如果不指定参数值,过期之后就会发起请求,接受响应,而如果设置了参数值,在指定的时间内,缓存仍会被接受。
min-fresh
表示客户端希望获取一个能在指定的秒数内保持其最新状态的响应。例如:
Cache-Control: min-fresh=100
在 100 秒内,资源的有效期限到了,这资源就无法作为响应返回。
它是 HTTP/1.0 的通用头,它用来向后兼容只支持 HTTP/1.0 协议的缓存服务器。它有一个 no-cache
指令,效果与 Cache-Control
中的 no-cache
一致。
缓存的处理过程可以简单地分为几步:
If-None-Match
),检测文档是否过期,如果不新鲜就执行第三步;304 Not Modified
);