一文搞懂 HTTP 缓存

HTTP 缓存可谓前端工程师必备技能之一,在最近的面试中,也经常被问到,那么为什么要有HTTP 缓存,以及它是如何工作的,下面咱们展开来好好聊聊。

还不知道什么是HTTP?可以跳转到我的前一篇文章------ 捋一下 HTTP 发展史 - 掘金 (juejin.cn)

为什么要有 HTTP 缓存

缓存缓存,顾名思义就是将一些资源保存在本地,后续请求时就可以直接在本地读取,而不用请求服务器。使用HTTP缓存是提高性能优化和提升用户体验的关键技术,它主要体现在几个方面:

  1. 减少延迟:当资源被缓存后,用户可以直接从浏览器缓存或更近的网络节点(如CDN)获取数据,而不必每次都请求原始服务器。网络延迟和数据传输时间都大大缩短了。
  2. 减轻服务器负担:通过缓存常用资源(如Logo),服务器不必每次都处理相同的请求。
  3. 节约带宽:缓存可以显著减少需要通过网络传输的数据量,降低用户和供应商的流量成本。
  4. 改善用户体验:缓存使得网页、图片等内容的加载更快,从而直接提高了用户的浏览体验。
  5. 离线浏览:HTTP缓存在一些场景可以让用户在没有网络连接的情况下,仍然能够访问先前加载过的内容。(如网易云音乐可以离线听之前缓存过的歌)
  6. 负载均衡:通过在多个地理位置缓存内容,避免所有请求都集中在一个服务器上。可以有效处理高流量和大规模分布的用户。

总之,HTTP缓存是前端开发者必备一项至关重要的技能,话不多说,开始介绍!

HTTP 缓存

HTTP缓存是一种客户端缓存,当浏览器向服务器发送资源请求时,服务器可以在响应头中查看是否包含缓存相关的信息。浏览器依据这些信息缓存响应资源,再次请求时如果命中缓存则直接读取本地缓存资源,而不再发请求。HTTP缓存分为强缓存协商缓存

强缓存

强缓存直接决定了浏览器是否需要向服务器发起请求,只需要设置缓存的过期时间。如果强缓存有效,浏览器将直接从本地缓存中读取资源,不会与服务器进行任何交互。

Cache-Control

Cache-Control是通用消息头字段,被用于在 HTTP 请求和响应中,通过指定指令来实现缓存机制,通过设置max-age=xxx来指定资源在缓存中可以存活的最大时间(单位是秒)。

举个栗子🌰:

这里我们缓存一个图片资源,过期时间为 86400 秒即一天

js 复制代码
res.writeHead(200,{
    "Content-Type": mime.lookup(ext),
    'cache-control':'max-age=86400',//缓存一天
})

当我们第一次请求时,会正常发请求拿到资源

但当我们刷新页面再次发请求时发现,此时的履行者变为memory cache,大小和时间都变为0,说明这次的资源并不是从服务器请求回来的,而是来自本地内存缓存。

并且我们可以在响应头中查看Cache-Control字段和我们设置的过期时间。

当然Cache-Control不止只允许我们设置max-age过期时间,还有其他字段,详情可参考 Cache-Control - HTTP | MDN (mozilla.org)

Expires

Expires响应头是一个具体的日期/时间,告诉浏览器在这个时间点之前不需要重新请求资源。

⚠️注意:如果响应中有指令为 max-ages-maxageCache-Control标头,则 Expires 标头会被忽略。

举个栗子🌰:

js 复制代码
 res.writeHead(200,{
    "Content-Type": mime.lookup(ext),
    'Expires': 'Mon Apr 22 2024 21:03:31 GMT+0800' //过期时间戳自己选择
})

Cache-Control用法相似,就不过多赘述。

协商缓存

当强缓存过期或无效时,浏览器就会与服务器进行交互,通过发送带有特定验证头的请求来检查资源是否被修改。如果服务器确认缓存资源仍然是最新的,则返回 304 状态码,浏览器会从本地缓存加载资源。如果资源不是最新的,则返回 200 状态码和新的资源数据。

Last-Modified 和 If-Modified-Since

服务器在响应中提供Last-Modified日期,表明资源最后修改时间。浏览器在后续请求中使用If-Modified-Since头包含这个日期,如果服务器上的资源自那以后未修改,就会返回304状态码。

举个栗子🌰:

js 复制代码
const stats = fs.statSync(filePath) //获取文件文件修改时的时间戳
const timeStamp = req.headers['if-modified-since'] //获取请求头字段
let status = 200 //资源修改
if(timeStamp && Number(timeStamp) === stats.mtimeMs) { //时间戳不变
    status = 304 //资源未修改
}

res.writeHead(status,{
    "Content-Type": mime.lookup(ext),
    'last-modified': stats.mtimeMs //时间戳
})

通过对比这次请求头的if-modified-since和响应头的last-modified判断资源是否修改

当资源未修改,返回 304 状态码,从缓存中拿资源。

现在我们试试手动修改资源,再次刷新页面:

资源修改,两次时间戳不同,返回 200 状态码,和新的资源。

ETag 和 If-None-Match

ETag是资源的唯一标识符,反映资源的内容状态。浏览器存储这个标识并在后续请求中通过If-None-Match头发送它。如果ETag未改变,服务器返回304状态码。

js 复制代码
if (req.headers['if-none-match'] === etag) {
    res.writeHead(304, {// 未修改,返回304
        'Content-Type':  mime.lookup(ext),
        'ETag': etag,
    });
    return res.end();
}

res.writeHead(200, { //修改,返回200
    'Content-Type':  mime.lookup(ext),
    'ETag': etag
});
return res.end(content);

两种协商缓存的区别是一个比较时间戳一个比较哈希值。

总结

强缓存通过设置过期时间来控制资源的缓存;而协商缓存则是通过与服务器验证资源的状态来决定是否使用缓存。协商缓存可以避免强缓存的不足之处,比如当资源在有效期内但发生了变化时,协商缓存可以保证客户端获取到最新的资源。

最后配一张个人觉得总结十分到位的图(图片地址在文末)

参考

最后

码字不易,感谢点赞评论收藏!

已将学习代码上传至 github,欢迎大家学习指正!

技术小白记录学习过程,有错误或不解的地方还请评论区留言,如果这篇文章对你有所帮助请 "点赞 收藏+关注" ,感谢支持!!

相关推荐
向阳12181 小时前
Dubbo HTTP接入之triple协议
网络协议·http·dubbo
IT女孩儿1 小时前
CSS查缺补漏(补充上一条)
前端·css
吃杠碰小鸡2 小时前
commitlint校验git提交信息
前端
虾球xz2 小时前
游戏引擎学习第20天
前端·学习·游戏引擎
我爱李星璇3 小时前
HTML常用表格与标签
前端·html
疯狂的沙粒3 小时前
如何在Vue项目中应用TypeScript?应该注意那些点?
前端·vue.js·typescript
小镇程序员3 小时前
vue2 src_Todolist全局总线事件版本
前端·javascript·vue.js
野槐3 小时前
前端图像处理(一)
前端
程序猿阿伟3 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
疯狂的沙粒3 小时前
对 TypeScript 中函数如何更好的理解及使用?与 JavaScript 函数有哪些区别?
前端·javascript·typescript