关于HTTP缓存验证Last-Modified和Etag的使用
关于资源验证
- 如果给Cache-Control设置no-cache之后,每次浏览器发起对设置了Cache-Control的资源请求的时候
- 都要去到服务器端进行一个资源的验证, 验证如果资源可以使用,那么才会读取本地的缓存
缓存验证流程
- 1 ) 浏览器创建了一个请求, 首先请求到达的地方是在本地缓存, 当然是建立在有Cache-Control头的情况下
- 如果在本地缓存里查找,如果找到,则直接返回给浏览器渲染页面
- 这种情况下, 不会经过任何网络的传输,也就是from memory cache的效果
- 2 ) 如果没有找到,就会去网络请求,在网络请求中,如果经过代理服务器(代理缓存)
- 那么首先会在代理服务器上查找相关缓存信息, 如果找到就会返回给客户端
- 经过本地缓存,再返回给浏览器渲染页面
- 3 ) 如果在代理服务器上没有找到该缓存信息,那么就直接去源服务器上查找资源
- 获取了新的内容之后,再逐级向下进行返回和二次缓存
- 最终返回给浏览器进行页面的渲染
如何进行缓存验证
- 在HTTP里主要有两个验证的HTTP头:Last-Modified, Etag
- Last-Modified: 上次修改时间
- 也就是说给这个资源设置了上次是什么时间修改的
- 主要配合If-Modified-Since或If-Unmodified-Since使用
- 如果请求了一个资源,其返回头信息中有Last-Modified字段,其指定了一个时间
- 在下一次浏览器发起请求的时候,就会带上这个Last-Modified字段信息
- 通过If-Modified-Since或If-Unmodified-Since
- 通常浏览器的实现会选择If-Modified-Since
- 在请求中将其带到服务器上, 服务器就可以读取头信息中的If-Modified-Since对比该资源上次修改时间
- 如果时间一样,那么代表资源没有被重新修改过, 服务器就可以告诉浏览器可以直接使用缓存的信息
- 这就是对比上次修改时间以验证资源能否使用缓存,是否需要更新的过程
- Etag: 是更加严格的一个验证
- 其验证主要是通过数据签名:对资源内容产生一个唯一的签名
- 如果资源数据进行了修改, 签名就会变成一个新的,任何修改就会造成两次签名不一样
- 最典型的方法是对资源内容进行哈希计算, 得到一个唯一值作为签名来标记这个资源
- 下一次浏览器发起请求的时候,就会带上If-Match或If-Non-Match字段,其值就是服务端返回的Etag值
- 对比资源签名,判断是否使用缓存
程序示例
-
test.html
<script src="/script.js"></script>
-
server.js
const http = require('http'); const fs = require('fs'); http.createServer((req, res) => { console.log('req come: ', req.url); if(req.url === '/') { const html = fs.readFileSync('test.html', 'utf8'); // 默认是下面这个writeHead设置,不写也可以 res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); } if(req.url === '/script.js') { res.writeHead(200, { 'Content-Type': 'text/javascript', 'Cache-Control': 'max-age=2000000000, no-cache', // 设置一个较长的值和no-cache 'Last-Modified': '12345', 'Etag': '666' }); res.end("alert('script loaded')"); } }).listen(8000, () => { console.log('server is running on port 8000'); });
-
启动程序:$
node server.js
-
访问:http://localhost:8000/
-
检查浏览器关于script.js中返回的头信息:Cache-Control, Last-Modified, Etag测试成功
-
刷新浏览器,通过面板检查发现这个script.js还是发送了请求,并没有从memery cache中读取
-
而且它的Request Headers请求头上携带了
If-Modified-Since:12345, If-Non-Match:666
这样的信息 -
这就是在服务器设置了
Cache-Control:no-cache, 'Last-Modified': '12345', 'Etag': '666'
-
浏览器在下次发送请求的时候把对应的验证缓存的头信息给带上, 以验证是否需要更新的策略
-
但是我们发现浏览器的Response是有内容的, 而且我们知道内容并没有任何的更改
-
这时候我们当然希望服务器不需要返回实际的内容,而是直接从缓存中读取信息
-
为了更好解释这一策略,我们稍微修改下程序
if(req.url === '/script.js') { // 通用字段的设置 let ct = 'text/javascript'; let ccv = 'max-age=2000000000, no-cache'; // 设置一个很长的时间, 同时设置no-cache let lm = '12345'; // 先随便设置一个值, 测试浏览器返回头中是否携带 let et = '666'; // 同上 // 读取请求头信息的判断返回内容 const etag = req.getHeader['if-none-match']; // 根据请求信息判断返回内容 if(etag === '777') { // 此条件没有修改,使用缓存设置 res.writeHead(304, { 'Content-Type': ct, 'Cache-Control': ccv, 'Last-Modified': lm, 'Etag': et }); res.end(""); // 不更新任何东西, 即使返回任何东西,此处都不会真正起作用(不会返回给浏览器客户端),因为设置了304的HTTP Code } else { // 更新 res.writeHead(200, { 'Content-Type': ct, 'Cache-Control': ccv, 'Last-Modified': lm, 'Etag': et }); res.end("alert('script loaded')"); // 返回js脚本内容 } }
-
修改完程序,重启服务,刷新浏览器一次进行程序的初始化,当第二次刷新浏览器时(也就是再次请求服务端时)
-
关于script.js的请求信息中的状态码就变成了304 Not Modified
-
而且发送请求头上的信息有
If-Modified-Since
和If-Non-Match
字段 -
而且其Response还是之前的内容,和服务端程序中
res.end("")
的设置无关(即便设置res.end("xxxyyyzzz")
也还是原来的内容) -
如果在浏览器检查面板上勾选上Disable cache, 那么在Request Headers请求头上不会发送任何缓存验证相关的头信息了
-
这是Chrome浏览器Disable cache的作用
扩展
- 1 ) 同样的,如果去除了no-cache的Cache-Control设置
- 经过重启服务,清理浏览器缓存,刷新浏览器等一系列初始化的工作
- 当再次刷新浏览器时, script.js文件就会直接从memory cache中读取(即:from memory cache)
- 2 ) 如果设置了no-store的Cache-Control
- 即:
'Cache-Control': no-store
- 它会忽略任何和缓存相关的设置,每次访问都是200, 直接从服务器端请求最新的数据
- 即:
- 3 ) 至于服务端如何真正做缓存验证,这就涉及到服务端设计的问题了,不涉及HTTP的内容了,此处不再详解
- 关于Last-Modified这个时间字段
- 可以使用文件本身的属性,如上次修改时间属性
- 如果是存储在服务器中的资源,可以通过设置一个字段,如
update_at
用于标识和更新即可
- 关于Etag
- 每次从服务端读取完数据之后, 都做一个签名以及和接收到请求的签名进行对比,这是最简单的方式
- 当然还会有其他更好的方式来提升服务器的性能, 看你怎么设计了
- 关于Last-Modified这个时间字段
上一篇: el-table动态合并单元格
下一篇: 剑指 Offer 24. 反转链表