Nodejs 如何保证 HTTP 强制缓存的新鲜度
Nodejs 如何保证 HTTP 强制缓存的新鲜度
很多时候,我们的网站上都会对静态资源开启 HTTP 缓存,HTTP 缓存分为两种,但对于静态资源来说,我想,大部分人开的因该是强制缓存吧?
不过强制缓存有个缺点那就是没法保证资源的 新鲜度(最新的) ,只能等待缓存时间过期才能获取到最新的资源内容
于是我写了一个库 Cache-Hash 在线求 star✨ ,专门处理 HTTP 缓存破除的
我的博客已经用上了: https://blog.imlete.cn
原理
通过给网站引入的资源,打上 hash 版本号,一旦内容改变,hash 会随着改变,这样即可通过改变 url 地址来破除缓存,能够保证网站所引用的资源是最新的
例如以下形式
<script src="https://demo.com/js/main.js?v=5e74b42bf5"></script>
使用方式
可以通过使用 CLI (命令行工具) ,也可以使用 JavaScript API 来对静态资源的引用生成 hash
可以全局安装使用
npm install cache-hash -g
cache-hash --target source --output public
简写
cache-hash -t source -o public
如果你不想全局安装,可以使用 npx
npx cache-hash --target source --output public
它是如何工作的?
它通过读取你给定的 target 目录,检测目录内的所有 html 、css 、js ,并对这些文件生成 AST(Abstract Syntax Tree) 即抽象语法树 ,之后通过修改 ast 语法树的内容后,再通过 ast 语法树编译回源代码即可
真是个好想法 大赞
前端打包出来的产物不是可以自带 hash 的吗
这个包的应用场景是什么样的呢
一般不都是 index.html 一直刷新,引用的 js,css 跟着版本变 hash 吗?
主要应用于再没有打包工具的情况下,比如一些项目的文档(当然有些文档生成框架是支持生成 hash 的),还有比如 hexo 、hugo 等这些打包出来的产物它们并没有生成 hash 的功能
#3 我的意思是 index.html 不缓存
一般都直接用 cache-control 头来控制 index.html 就好了吧?
可是你的其它资源比如 css 、js 、img 、mp3 、font 、等用了缓存,你的 index.html 用的依然是缓存啊
没错
#8 是啊。。js 这些的路径自带 hash 呗
cdn 厂商不是帮忙做这些吗
我想起以前在 IIS 上写 silverlight 的时候,IE 的缓存策略非常的顽固,所以 HTTP HEAD 的那些 cache ,hint 都不识,我们发布的时候就给 app 名字后面加版本号,在一些配置文件后面.config?ramdon=xx
#10 怎么自带?手写?
关于这问题,我以前发过贴 https://v2ex.com/t/830203
看来历史的轮子总是重复的。
正解,现在用 webpack 等打包的基本都有配置打包后文件带 hash 值
cdn 有两种缓存
1. 缓存原服务器的静态资源,规则由你选择,在缓存期间,任何请求都只会从 cdn 的网络中响应资源给用户(你服务器的任何资源修改 cdn 都不会去刷新(除非你手动在 cdn 里刷新),只有当缓存时间过了之后才会向你服务器获取),在此期间不会对你的服务器有任何连接
2. 要么就是协商缓存,那么用户访问还是会去问服务器这个资源是不是最新的,要么就是强制缓存,这就是正常的强制缓存,无论服务器端怎么改变资源,浏览器都不会去访问服务器,只有过期了才会访问服务器
本文的 cache-hash 工具是给没有自带生成 hash 功能的场景使用,比如一些文档生成工具,它们只负责将 markdown 渲染出一个个文档页面,并没有生成 hash 的功能,当然有些文档生成工具也有自带的比如 vuepress
这样说的话,是可以理解的
看了一下,你列举的 3 个方法,第 1 个没看明白,但后两个方法存在问题,虽然都能解决你帖子的疑问
第 2 种: 使用 etag 实际上是协商缓存,每次请求都会向服务器确认资源有没有变化,如果服务器线路比较拉跨,那么这个请求到服务器的时间也会随之变长,浏览器再等待服务器响应回来也需要时间,如果是强制缓存,就没有那么多的步骤,直接从浏览器本地缓存读取
第 3 种: last-modifed-time ,它也是协商缓存,但区别在于 etag 判断的是标识(hash),last-modifed-time 判断的是最后修改时间,它同样需要把时间发送给服务器去判断
这个方案能解决 90%以上的情况,但我曾经遇到过,缓存机制不看参数,只看?之前的 url ,所以后来还是用文件名 hash 的方案。
我遇到的情况并非在普通浏览器中,好像是微信的 webview 还是哪个手机 app 环境里面。
我原贴的意思是,如果浏览器直接读 html 里的 etag 或者 last-modifed-time ,就可以不协商,仅仅本地对比。
现在浏览器没读,所以就只能发请求服务器进行协商。
早期的手机浏览器设计很奇葩,会自作主张在 url 请求后面加?作为一些特殊标识。然后服务器收到两个?的参数,就会出问题了。
哦哦,是我没看明白,哈哈哈
不过 w3c 没制定标准,浏览器厂商是不可能去做的,况且如果有浏览器厂商做了,其它厂商也不一定做
确实有些浏览器或套壳浏览器有这种问题
vue-cli vite-cli nuxt-cli cra-cli nextjs-cli build 出来文件都自带 hash , 一般都是 index.html 设置 no-store 。index.html 里面引用的 js css images 跟着自动变 。感觉现在都用这种脚手架了吧
这种常见问题,应该都有方案
这方案十几年前就普遍使用了
有的 CDN ignore query string 的,你这白操作
你可能理解错了,query string 主要是给浏览器辨别这个连接有没有缓存的,如果 url 变了就会像服务器索要新的资源,没变就直接充浏览器缓存中读取
#28 你改 query string 刷了浏览器的缓存,但是 CDN 的缓存还是可能没刷啊。。。不如乖乖配 Cache Control
不设置 CDN 的缓存不就可以了,直接 Cache-Control 设置强制缓存
我的天啊,真是这么玩的吗 ?如果是 HTML 的变化,一般前段打包器就已经做了这个工作,比如 webpack ,只要打包内部的索引( hash )就自动换了。
难道我理解错了 楼主 的意思吗
这不就是协商缓存吗。。。
不是协商缓存呀
在Node.js中,保证HTTP强制缓存的新鲜度主要通过设置适当的HTTP响应头来实现,其中最重要的是Cache-Control
和Expires
头。以下是具体的实现方法:
-
使用
Cache-Control
头:Cache-Control
头提供了更细粒度的缓存控制。例如,max-age
指令可以指定资源在客户端缓存的最大时间(秒)。const http = require('http'); http.createServer((req, res) => { res.setHeader('Cache-Control', 'max-age=3600'); // 缓存1小时 res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World!\n'); }).listen(3000, '127.0.0.1', () => { console.log('Server running at http://127.0.0.1:3000/'); });
-
使用
Expires
头:Expires
头指定了资源过期的绝对时间。虽然Cache-Control
更现代和灵活,但Expires
在某些旧浏览器或代理中仍然有效。const http = require('http'); const oneHourLater = new Date(Date.now() + 3600 * 1000); // 当前时间+1小时 http.createServer((req, res) => { res.setHeader('Expires', oneHourLater.toUTCString()); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello, World!\n'); }).listen(3000, '127.0.0.1', () => { console.log('Server running at http://127.0.0.1:3000/'); });
通过这两种方法,Node.js服务器可以确保HTTP资源在客户端被强制缓存,并在指定的时间内保持新鲜。