HTTP缓存控制
本文部分内容摘抄其他博客、文章,仅用于学习HTTP缓存控制
如有错误,欢迎批评指正
对于Web站点来说,资源来自于网络下载,在下载过程中,如果遇到延迟或者网络堵塞,会导致资源加载速度过慢,影响用户的体验。另一方面,如果每次访问都从服务器下载重复资源,会给服务器带宽带来不必要的浪费,因此通过缓存重用已经获取的资源,可以有效的提高Web站点的响应及减少服务器压力。以下就来学习HTTP缓存控制。
HTTP报文中与缓存相关的首部字段
首先,先了解一些与缓存相关的首部字段。
- 通用首部字段
字段名称 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Progma | HTTP1.0 ,值为"no-cache"时禁用缓存 |
- 请求首部字段
字段名称 | 说明 |
---|---|
if-Match | 比较Etag是否一致 |
If-None-Match | 比较Etag是否不一致 |
if-Modified-Since | 比较资源最后更新的时间是否一致 |
if-Unmodified-Since | 比较资源最后更新的时间是否不一致 |
- 响应首部字段
字段名称 | 说明 |
---|---|
Etag | 资源的匹配信息 |
- 实体首部字段
字段名称 | 说明 |
---|---|
Expires | HTTP1.0,用于设置实体主体过期时间 |
Last-Modified | 资源的最后一次修改时间 |
缓存控制
Pragma
HTTP1.0时代的字段,目前已很少使用,用于禁用缓存,通常设置为Pragma: no-cache
,当设置该字段时,服务器会知会客户端不缓存该资源。当客户端请求带有此字段时,会表示客户端禁用缓存,需要下载资源。
例如我们常用的chrome开发者工具中,开启Disable cache
时,客户端就会在所有请求中添加这个字段表示禁用缓存。
Prama:no-cache
Expires
HTTP1.0时代的字段,用于启用缓存及定义缓存时间。该字段的值对应绝对时间:格林尼治时间(GMT)。该时间用于告诉客户端资源缓存过期的时间,如果没有超过该时间点,则发送的请求不需要向服务器访问,直接使用缓存的资源。需要注意的是该字段的时间相对于服务器时间而言的。
Expires:Mon, 15 May 2017 02:34:13 GMT
Cache-Control
由于Expires
字段的时间是相对服务器的,在系统时间不准确不统一时,可能会有异常的情况,HTTP1.1中新增了Cache-Control
头用于区分缓存机制,通过它提供的不同值来定义具体的缓存策略。
该首部的值可以采用组合的形式,需要注意部分限制,比如no-cache
不能不能和 max-age、min-fresh、max-stale 一起搭配使用:
Cache-Control: public, max-age = 3600
// 表示任何情况下都得缓存资源,缓存时间为1小时,后续1小时内,用户访问该资源都无需发送请求
需要注意几个首部的优先级:Pragma > Cache-Control > Expires
- 作为请求首部时,value可选值有:
字段名称 | 说明 |
---|---|
no-cache | 告知(代理)服务器不直接使用缓存,要求向原服务器发起请求 |
no-store | 所有内容都不会保存到缓存或者Internet临时文件中 |
max-age=delta-seconds | 告知服务器,客户端希望接收一个存在时间(Age)不大于delta-seconds 秒的资源 |
max-stale[=delta-seconds] | 告知(代理)服务器客户端愿意接收一个草果缓存时间的资源,若有定义delta-seconds则为delta-seconds秒,若没有则为任意超出时间 |
min-fresh=delta-seconds | 告知(代理)服务器客户端希望接收一个在小雨delta-seconds秒内被更新过的资源 |
no-transform | 告知(代理)服务器客户端希望获取实体数据没有被转换(比如压缩)过的资源 |
only-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发去请求 |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略掉 |
- 作为响应首部时,value可选值有:
字段名称 | 说明 |
---|---|
public | 表明任何情况下都得缓存该资源(即使是需要HTTP认证的资源) |
Private[="field-name"] | 表明返回报文中全部或部分(若制定了field-name 则为field-name 的字段数据)仅开放给某些用户(服务器制定的share-user,如代理服务器)做缓存使用,其他用户不能缓存这些数据 |
no-cache | 不直接使用缓存,要向服务器发起(新鲜度校验)请求 |
no-store | 所有内容都不会被保存到缓存或Internet临时文件中 |
no-transform | 告知客户端缓存文件时不得对实体数据做任何改变 |
only-if-cached | 告知(代理)服务器客户端希望获取缓存的内容(若有),而不用向原服务器发起请求 |
must-revalidate | 当前资源一定是向原服务器发去验证请求的,若请求失败会返回504(而非代理服务器上的缓存) |
proxy-revalidate | 与must-revalidate类似,但仅能应用于共享缓存(如代理) |
max-age=delta-seconds | 告知客户端该资源在delta-seconds秒内是新鲜的,无需向服务器发请求 |
s-maxage=delta-seconds | 同max-age,但仅应用于共享缓存(如代理) |
cache-extension | 自定义扩展值,若服务器不识别该值将被忽略 |
Last-modified
该字段用于为资源标记一个最后修改时间,服务器可以在向客户端发送资源时,将资源最后修改时间以“Last-Modified: GMT”的形式加在实体首部上一起返回给客户端。
客户端在接收到包含字段的响应时,会为该资源标记该信息,并下一次请求该资源时,会把该信息负在请求报文中(字段为If-Modified-Since: Last-Modified-value
)一起发送给服务器,当该时间与服务器上最后修改时间一致时,则说明资源未被修改。通过这个判断,服务器应当返回304
状态码和响应报头,内容为空,以达到节省宽带的目的。
如果服务器发现未匹配到(资源最后更新时间变更了),则应当返回412
状态码给客户端。Last-Modified 存在一定问题,如果在服务器上,一个资源被修改了,但其实际内容根本没发生改变,会因为Last-Modified时间匹配不上而返回了整个实体给客户端(即使客户端缓存里有个一模一样的资源)。
Last-modified: Fri, 22 Jul 2016 01:47:00 GMT
Etag
该首部字段通过服务器给资源标记唯一标识符(例如md5),在响应中返回客户端,客户端在下一次请求时附带Etag
字段,服务器即可判断资源相对于客户端是否一致,决定是否返回新的资源,或通过304
告知客户端使用缓存。
Etag
字段与Last-Modified
类似:
Etag: 5d8c72a5edda8d6a
//
If-Match:Etag-value
// 匹配
If-None-Match:Etag-value
// 不匹配
需要注意Etag
优先级高于Last-Modified
。
总结
对于HTTP缓存相关的内容分三种情况:
- 可储存:HTTP响应内容决定资源是否可以被客户端缓存。
- 期限:当客户端再次请求资源前通过
Expires
的绝对时间或Cache-Control
的相对时间确认资源是否已经过期,当过期时才向服务端发起请求确认资源是否过期, - 有效性比对:服务端通过比对请求中的最近修改时间、标识符等方式对资源有效情况的判断,决定缓存的资源是否需要更新。
Steve Sounders
对于经常更新的资源来说,HTTP缓存控制可能会带来一些麻烦,尤其是需要经常变动尽快更新线上的资源。为此Web开发者发明了一种名为Steve Sounders的技术。对于不频繁更新的文件,在其URL(通常为文件名)后面添加上一个版本号,每个版本号就成了一个独立资源,这样该资源即获得了长时间的缓存时长,同时又可以在需要时通过替换链接的方式使资源更新。在web开发大量使用自动化构建工具的今天,替换链接不再是一件麻烦事。
对于css及JS这种具有相互依赖关系的文件来说,使用这个技术,解决了更新资源时缓存文件更新不一致的问题。
<script src="xxx.js?version=12345"></script>
// 比如使用查询字符串
参考来源:
HTTP缓存控制小结
HTTP缓存