聊一聊HTTP之强制缓存和协商缓存
什么是HTTP缓存
HTTP缓存是一种提高网页性能的技术,它可以减少服务器的负载,加快客户端的响应速度,节省网络带宽。HTTP缓存分为两种类型:强制缓存和协商缓存。
强制缓存
强制缓存是指客户端在一定时间内直接使用本地缓存的资源,不需要向服务器发送请求。强制缓存的控制由服务器端的响应头来指定,主要有两个字段:Cache-Control
和Expires
。
Cache-Control
Cache-Control
是一个通用的字段,它可以指定缓存的最大有效期(max-age
),是否可以被共享(public
或private
),是否可以被修改(no-cache
或no-store
)。
举个例子:
Cache-Control: max-age=3600
上面表示资源在3600秒内有效,可以被缓存。
Expires
Expires
是一个过时的字段,它可以指定缓存的绝对过期时间。
举个例子:
Expires: Wed, 23 Aug 2023 03:36:26 GMT
上面表示资源在2023年8月23日3点36分26秒过期。如果同时存在Cache-Control和Expires,那么Cache-Control优先级更高。
协商缓存
协商缓存是指客户端在每次请求时都要向服务器询问资源是否有更新,如果没有更新,则返回304状态码和空响应体,表示可以继续使用本地缓存;如果有更新,则返回200状态码和新的资源,表示需要替换本地缓存。协商缓存的控制由服务器端和客户端共同参与,主要有两组字段:Last-Modified/If-Modified-Since和ETag/If-None-Match。
Last-Modified/If-Modified-Since
Last-Modified是服务器端返回的字段,它表示资源的最后修改时间。例如:
Last-Modified: Tue, 22 Aug 2023 02:36:26 GMT
表示资源在2023年8月22日2点36分26秒被修改过。
If-Modified-Since是客户端发送的字段,它表示资源的上次获取时间。例如:
If-Modified-Since: Tue, 22 Aug 2023 02:36:26 GMT
表示客户端在2023年8月22日2点36分26秒获取过资源。如果两者相等或者Last-Modified
更早,则表示资源没有更新;如果Last-Modified
更晚,则表示资源有更新。
ETag/If-None-Match
ETag是服务器端返回的字段,它表示资源的唯一标识符。例如:
ETag: "5d3a9f6d-1f86"
表示资源的标识符为"5d3a9f6d-1f86"。
If-None-Match是客户端发送的字段,它表示资源的期望标识符。例如:
If-None-Match: "5d3a9f6d-1f86"
表示客户端期望资源的标识符为"5d3a9f6d-1f86"
。如果两者相等,则表示资源没有更新;如果不相等,则表示资源有更新。
HTTP缓存实践策略
结合使用协商缓存和强制缓存,可以有效地减少不必要的网络请求,同时确保用户始终能够获取到最新的内容。
整体思路:
-
强制缓存:首先,我们为静态资源(如 CSS、JS、图片等)设置一个长时间的缓存。这样,当用户再次访问这些资源时,浏览器可以直接从本地缓存中获取,而不需要向服务器发送请求。
-
协商缓存:对于可能会更改的资源,我们使用协商缓存。这意味着浏览器会向服务器发送一个请求,询问资源是否已经更改。如果资源没有更改,服务器只会返回一个 304 Not Modified 响应,告诉浏览器继续使用本地缓存。如果资源已经更改,服务器会返回新的资源和 200 OK 响应。
具体实现代码:
假设我们使用 Express.js
作为后端框架:
const express = require('express');
const app = express();
// 强制缓存:为静态资源设置缓存
app.use('/static', express.static('public', {
maxAge: '1y' // 缓存时间为1年
}));
// 协商缓存:使用 ETag 和 Last-Modified
app.get('/resource', (req, res) => {
const content = '...'; // 获取资源内容
const etag = generateETag(content); // 生成 ETag
// 检查 If-None-Match 头
if (req.headers['if-none-match'] === etag) {
res.status(304).end(); // 资源没有更改,返回 304
} else {
res.setHeader('ETag', etag);
res.send(content); // 资源已更改,返回新内容
}
});
function generateETag(content) {
return require('crypto').createHash('md5').update(content).digest('hex');
}
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
注意事项:
-
版本控制:为了最大化强制缓存的效果,你可以在资源的 URL 中包含版本信息,例如
/static/js/main.2023082301.js
。这样,当资源更改时,你只需更改版本号,确保用户总是获取到最新的资源。 -
协商缓存的开销:虽然协商缓存可以减少不必要的数据传输,但它仍然需要一个往返的网络请求。因此,对于不经常更改的资源,使用强制缓存可能更为高效。
转载自:https://juejin.cn/post/7270334229597683766