如何利用node實(shí)現(xiàn)靜態(tài)文件緩存詳解
緩存
瀏覽器緩存(Brower Caching)是瀏覽器對(duì)之前請(qǐng)求過(guò)的文件進(jìn)行緩存,以便下一次訪問(wèn)時(shí)重復(fù)使用,節(jié)省帶寬,提高訪問(wèn)速度,降低服務(wù)器壓力
緩存位置分類
memory cache:內(nèi)存中的緩存,關(guān)閉瀏覽器則清空,一般存儲(chǔ)一些js庫(kù)

disk cache:硬盤中的緩存,關(guān)閉瀏覽器不會(huì)馬上清空,一般存儲(chǔ)大文件,比如 圖片資源,iconFont這類的圖標(biāo)文件庫(kù)

兩者的區(qū)別:
1. 讀取速度 :memory cache緩存的是當(dāng)前解析過(guò)了的文件在瀏覽器tab進(jìn)程里,下次運(yùn)行使用時(shí)的可以快速讀??;
disk cache直接將緩存寫入硬盤文件中,讀取緩存需要對(duì)該緩存存放的硬盤文件進(jìn)行I/O(讀取)操作,然后重新解析緩存內(nèi)容,速度比內(nèi)存緩存慢
2. 時(shí)效性:memory cache是存在tab的進(jìn)程里,tab關(guān)閉,則清空;
disk cache:被清空的時(shí)機(jī)我還不知道(希望有人可以補(bǔ)充)
3. 優(yōu)先級(jí):memory cache大于disk cache
對(duì)于大文件來(lái)說(shuō),大概率是不存儲(chǔ)在memory中的,反之優(yōu)先,代碼角度目前好像也無(wú)法控制瀏覽器緩存位置
緩存設(shè)置header
cache-control
1. cache-control:max-age=10//10秒內(nèi)重新發(fā)的請(qǐng)求都直接命中強(qiáng)緩存,無(wú)需向服務(wù)器發(fā)起請(qǐng)求,讀取瀏覽器緩存即可
2. Cache-Control:no-cache //禁止強(qiáng)制緩存,每次都向服務(wù)器發(fā)起請(qǐng)求,同時(shí)也會(huì)存在瀏覽器緩存中 (走協(xié)商緩存了基本)
3. Cache-Control:no-store //每次都請(qǐng)求服務(wù)器,且不緩存在瀏覽器中,等同于沒(méi)有緩存
復(fù)制代碼
Expires:
兼容低版本瀏覽器,這個(gè)就是設(shè)置絕對(duì)時(shí)間,獲取的是服務(wù)器的當(dāng)前時(shí)間和瀏覽器當(dāng)前時(shí)間做比對(duì)(通常存在偏差,是http1.0的產(chǎn)物),和 cache-control同時(shí)存在時(shí),cache-control優(yōu)先級(jí)更高
- last-modified:協(xié)商緩存的時(shí)候用 和If-Modified-Since,成對(duì)出現(xiàn);If-Modified-Since請(qǐng)求頭的值對(duì)應(yīng)上一次服務(wù)器的響應(yīng)頭last-modified的值,擁有提供服務(wù)器比對(duì)請(qǐng)求資源修改時(shí)間,相等,則命中協(xié)商緩存返回304,瀏覽器讀取緩存即可
- Etag:資源標(biāo)識(shí)(也有說(shuō)時(shí)指紋,通常是一個(gè)md5值),協(xié)商緩存時(shí)候用,比較文件是否修改;和If-None-Match 成對(duì)出現(xiàn)
Etag主要為了解決 Last-Modified 無(wú)法解決的一些問(wèn)題。
1. 一些文件也許會(huì)周期性的更改,但是他的內(nèi)容并不改變(僅僅改變的修改時(shí)間),這個(gè)時(shí)候我們并不希望客戶端認(rèn)為這個(gè)文件被修改了,而重新GET;
2. 某些文件修改非常頻繁,比如在秒以下的時(shí)間內(nèi)進(jìn)行修改,(比方說(shuō)1s內(nèi)修改了N次),If-Modified-Since無(wú)法檢查到如此精細(xì)
3. 某些服務(wù)器不能精確的得到文件的最后修改時(shí)間;
4.Etag與Last-modify同時(shí)存在 Etag優(yōu)先級(jí)比較
實(shí)際項(xiàng)目:html不允許緩存,html里引用的js有唯一的版本號(hào)做依據(jù),再次訪問(wèn)的時(shí)候 訪問(wèn)最新的html,引用的js或其他文件版本號(hào)未修改則直接用本地緩存
node實(shí)現(xiàn)靜態(tài)文件緩存
文件結(jié)構(gòu)

public對(duì)應(yīng)我們測(cè)試用的靜態(tài)資源
強(qiáng)緩存
思路
- 創(chuàng)建服務(wù)
- 首次請(qǐng)求 解析請(qǐng)求路徑, fs.createReadStream().pipe() 讀取文件
- 設(shè)置響應(yīng)頭Cache-Contro:max-age=10 強(qiáng)緩存的相對(duì)時(shí)間
代碼實(shí)現(xiàn)
const http = require("http");
const url = require("url");
const fs = require("fs");
const path = require("path");
// 接收文件路徑 返回該文件對(duì)應(yīng)的文件類型格式
const mime = require("mime");//npm i mime
const server = http.createServer((req, res) => {
let { pathname, query } = url.parse(req.url, true);
//__dirname 當(dāng)前文件所在的文件夾所處的絕對(duì)路徑 和請(qǐng)求路徑拼接
let filePath = path.join(__dirname, "public", pathname);
console.log(req.url);//10s內(nèi)反復(fù)刷新頁(yè)面,查看是否持續(xù)打印,命中強(qiáng)緩存則10s打印一次
// 設(shè)置頭部 緩存信息,規(guī)定的緩存時(shí)間內(nèi),客戶端無(wú)需再向服務(wù)器發(fā)起請(qǐng)求
res.setHeader("Cache-Control", "max-age=10"); // 設(shè)置緩存時(shí)常;請(qǐng)求的當(dāng)前時(shí)間+max-age 的相對(duì)時(shí)間內(nèi),優(yōu)先級(jí)比Expires高
res.setHeader("Expires", new Date(Date.now() + 10).toUTCString()); //兼容低版本瀏覽器,這個(gè)就是設(shè)置絕對(duì)時(shí)間,獲取的是服務(wù)器的當(dāng)前時(shí)間
// 獲取請(qǐng)求路徑 判斷是文件還是文件目錄
fs.stat(filePath, function (err, statObj) {
// url解析錯(cuò)誤,則請(qǐng)求錯(cuò)誤 沒(méi)有找到對(duì)應(yīng)url資源 返回404
if (err) {
res.statusCode = 404;
res.end("NOT FOUND");
} else {
// 如果是文件,用可讀流+管道 pipe 進(jìn)行文件內(nèi)容讀取,利用mime 獲取文件內(nèi)容格式,并設(shè)置編碼規(guī)范為utf-8
if (statObj.isFile()) {
fs.createReadStream(filePath).pipe(res);
res.setHeader(
"Content-Type",
mime.getType(filePath) + ";charset=utf-8"
);
} else {
// 如果是文件目錄 找到 目錄下對(duì)應(yīng)的index.html
let htmlPath = path.join(filePath, "index.html");
// fs.access判斷拼接的路徑是否可訪問(wèn)
fs.access(htmlPath, function (err) {
if (err) {
// 不可訪問(wèn) 設(shè)置 狀態(tài)碼404
res.statusCode = 404;
res.end("NOT FOUND");
} else {
//可訪問(wèn),用可讀流加管道 pipe 進(jìn)行文件內(nèi)容讀取
fs.createReadStream(htmlPath).pipe(res);
res.setHeader("Content-Type", "text/html;charset=utf-8");
}
});
}
}
});
// 寫到這里 可以 nodemon cache.js 啟動(dòng)服務(wù) 查看 http://localhost:3000/
});
server.listen(3000, () => {
console.log("server start 3000");
});
效果展示

協(xié)商緩存
成功
思路
- 創(chuàng)建服務(wù)
- 首次請(qǐng)求 解析請(qǐng)求路徑, fs.createReadStream().pipe() 讀取文件
- 設(shè)置響應(yīng)頭Last-modified 返回瀏覽器
- 再次請(qǐng)求,比較瀏覽器if-last-modified 和當(dāng)前資源修改時(shí)間,相等則命中協(xié)商緩存,返回響應(yīng)碼304,反之返回路徑對(duì)應(yīng)的最新資源,和響應(yīng)碼200
代碼實(shí)現(xiàn)
const http = require("http");
const url = require("url");
const fs = require("fs");
const path = require("path");
const mime = require("mime");
let filePath = path.join(__dirname, "public", pathname);
console.log(req.url);
fs.stat(filePath, function (err, statObj) {
if (err) {
res.statusCode = 404;
res.end("NOT FOUND");
} else {
if (statObj.isFile()) {
// 判斷 瀏覽器請(qǐng)求的文件路徑 的change 時(shí)間 通過(guò)statObj.ctime
const ctime = statObj.ctime.toUTCString();
// 瀏覽器請(qǐng)求頭if-modified-since ===文件上次的修改時(shí)間 ,命中協(xié)商緩存,則返回 304 瀏覽器緩存中請(qǐng)求資源
if (req.headers["if-modified-since"] === ctime) {
res.statusCode = 304; //去瀏覽器緩存中找
res.end(); //
} else {
// if-modified-since !==文件上次的修改時(shí)間,響應(yīng)頭Last-modified 設(shè)置 當(dāng)前請(qǐng)求文件的 修改時(shí)間 做下次 瀏覽器請(qǐng)求的last-modify-since的對(duì)應(yīng)值
res.setHeader("Last-modified", ctime);
fs.createReadStream(filePath).pipe(res);
res.setHeader(
"Content-Type",
mime.getType(filePath) + ";charset=utf-8"
);
}
} else {
fs.access(htmlPath, function (err) {
if (err) {
// 不可訪問(wèn) 設(shè)置 狀態(tài)碼404
res.statusCode = 404;
res.end("NOT FOUND");
} else {
fs.createReadStream(htmlPath).pipe(res);
res.setHeader("Content-Type", "text/html;charset=utf-8");
}
});
}
}
});
// 寫到這里 可以 nodemon cache2.js 啟動(dòng)服務(wù) 查看 http://localhost:3000/
});
server.listen(3000, () => {
console.log("server start 3000");
});
效果展示
每次刷新頁(yè)面都會(huì)執(zhí)行 console.log(req.url); 請(qǐng)求了服務(wù)器但服務(wù)器返回304 命中協(xié)商緩存 瀏覽器直接讀取緩存資源即可

成功
總結(jié)
到此這篇關(guān)于如何利用node實(shí)現(xiàn)靜態(tài)文件緩存的文章就介紹到這了,更多相關(guān)node靜態(tài)文件緩存內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
nodejs 實(shí)現(xiàn)MQTT協(xié)議的服務(wù)器端和客戶端的雙向交互的過(guò)程
這篇文章主要介紹了nodejs 實(shí)現(xiàn)MQTT協(xié)議的服務(wù)器端和客戶端的雙向交互的過(guò)程,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11
可能是全網(wǎng)最詳細(xì)的nodejs卸載和安裝教程
npm的中文意思為"node包管理器",是Node.js平臺(tái)的默認(rèn)包管理工具,會(huì)隨著Nodejs一起安裝,npm管理對(duì)應(yīng)node.js的第三方插件,下面這篇文章主要給大家介紹了關(guān)于nodejs卸載和安裝教程的相關(guān)資料,這可能全網(wǎng)最詳細(xì)的教程了,需要的朋友可以參考下2023-05-05
node.js中的console.timeEnd方法使用說(shuō)明
這篇文章主要介紹了node.js中的console.timeEnd方法使用說(shuō)明,本文介紹了console.timeEnd的方法說(shuō)明、語(yǔ)法、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12
Nodejs 獲取時(shí)間加手機(jī)標(biāo)識(shí)的32位標(biāo)識(shí)實(shí)現(xiàn)代碼
本文給大家分享nodejs獲取時(shí)間加手機(jī)標(biāo)識(shí)的32位標(biāo)識(shí)實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下2017-03-03
npm?ERR!?Node.js?v20.11.0錯(cuò)誤的解決
在使用?npm?進(jìn)行包管理和構(gòu)建項(xiàng)目的過(guò)程中,有時(shí)會(huì)遇到錯(cuò)誤信息?npm?ERR!?Node.js?v20.11.0,本文就來(lái)介紹一下如何解決,感興趣的可以了解一下2024-02-02
基于NodeJS的前后端分離的思考與實(shí)踐(一)全棧式開(kāi)發(fā)
這個(gè)話題最近被討論得比較多,阿里有些BU也在進(jìn)行一些嘗試。討論了很久之后,我們團(tuán)隊(duì)決定探索一套基于NodeJS的前后端分離方案,過(guò)程中有一些不斷變化的認(rèn)識(shí)以及思考,記錄在這里,也希望看到的同學(xué)參與討論,幫我們完善。2014-09-09
docker中編譯nodejs并使用nginx啟動(dòng)
這篇文章主要介紹了docker中編譯nodejs并使用nginx啟動(dòng)的相關(guān)資料,需要的朋友可以參考下2017-06-06
基于豆瓣API+Angular開(kāi)發(fā)的web App
這篇文章主要介紹了基于豆瓣API+Angular開(kāi)發(fā)的web App的方法和示例代碼,效果非常棒,有需要的小伙伴參考下2015-01-01
玩轉(zhuǎn)NODE.JS(四)-搭建簡(jiǎn)單的聊天室的代碼
本篇文章主要介紹了利用NODE.JS搭建簡(jiǎn)單的聊天室的代碼,有需要的可以了解一下。2016-11-11

