关于资源验证
- 如果给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');
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',
'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';
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("");
} else {
res.writeHead(200, {
'Content-Type': ct,
'Cache-Control': ccv,
'Last-Modified': lm,
'Etag': et
});
res.end("alert('script loaded')");
}
}
-
修改完程序,重启服务,刷新浏览器一次进行程序的初始化,当第二次刷新浏览器时(也就是再次请求服务端时) -
关于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
- 每次从服务端读取完数据之后, 都做一个签名以及和接收到请求的签名进行对比,这是最简单的方式
- 当然还会有其他更好的方式来提升服务器的性能, 看你怎么设计了
|