依然在學習node的艱辛過程當中,最近學習了http相關的知識,學到了東西固然第一時間就來和你們分享分享,今天呢就教你們來看看利用node中的http模塊去實現不一樣的緩存策略!!!javascript
咱們都知道,對於咱們前端開發來講,緩存是一個十分重要的東西,即但願用戶不能每次請求過來都要重複下載咱們的頁面內容,但願爲用戶節省流量,而且能提升咱們頁面的瀏覽流暢度,可是同時當咱們修改了一個bug後,又但願線上可以及時更新,這時候就要求爺爺告奶奶讓運維小哥哥幫咱們刷新一下緩存了,那麼有沒有一些比較好的緩存策略能夠針對咱們修改bug又能不麻煩運維及時更新呢,今天咱們就利用node來看一下後端中的緩存策略是如何設置的。css
一般咱們對於強制緩存的設置是服務端告訴客戶端你剛剛已經請求過一次了,咱們約定好十分鐘內你再過來請求都直接讀取緩存吧,意思也就是當客戶端在十分鐘內屢次請求的話只有第一次會下載頁面內容,其餘的請求都是直接走緩存,無論咱們頁面在這期間有沒有變化都不會影響客戶端讀取緩存。 那咱們來看一下代碼的實現html
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
// 建立一個服務
let server = http.createServer();
// 監聽請求
server.on('request',(req,res)=>{
// 獲取到請求的路徑
let {pathname,query} = url.parse(req.url,true);
// 將路徑拼接成服務器上對應得文件路徑
let readPath = path.join(__dirname, 'public',pathname);
console.log(readPath)
try {
// 獲取路徑狀態
let statObj = fs.statSync(readPath);
// 服務端設置響應頭 Cache-Control 也就是緩存多久以秒爲單位
res.setHeader('Cache-Control','max-age=10');
// 服務器設置響應頭Expires 過時時間 獲取當前時間加上剛剛設置的緩存秒數
res.setHeader('Expires',new Date(Date.now()+10*1000).toGMTString());
//判斷若是路徑是一件文件夾 就默認查找該文件下的index.html
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
console.log(p);
// 判斷是否有index.html 沒有就返回404
fs.statSync(p);
// 建立文件可讀流 而且pipe到響應res可寫流中
fs.createReadStream(p).pipe(res)
}else{
// 若是請求的就是一個文件 那麼久直接返回
fs.createReadStream(readPath).pipe(res)
}
} catch (error) {
// 讀取不到 返回404
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未發現文件`)
}
})
// 監聽3000端口
server.listen(3000)
複製代碼
經過上面代碼測試咱們會發現當咱們在10秒內進行對同一文件的請求,那麼咱們瀏覽器就會直接走緩存 經過上圖能夠看到咱們重複請求的時候咱們會看到css變成from memory cache,咱們也看到咱們剛剛的響應頭也被設置上了 前端
上面的強制緩存咱們就發現了 就是咱們平時改完bug上線要苦苦等待的一個緣由了,那麼有沒有其餘的好的緩存處理方法呢,咱們設想一下 假如咱們可以知道咱們文件有沒有修改,假如咱們修改了服務器就返回最新的內容假如沒有修改 就一直默認緩存 ,這樣是否是聽起來十分的棒!那咱們就想若是咱們可以知道文件的最後修改時間是否是就能夠實現了!java
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let server = http.createServer();
server.on('request',(req,res)=>{
// 獲取到請求的路徑
let {pathname,query} = url.parse(req.url,true);
// 將路徑拼接成服務器上對應得文件路徑
let readPath = path.join(__dirname, 'public',pathname);
try {
// 獲取路徑狀態
let statObj = fs.statSync(readPath);
// 爲了方便測試 咱們告訴客戶端不要走強制緩存了
res.setHeader('Cache-Control','no-cache');
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
let statObj = fs.statSync(p);
// 咱們經過獲取到文件狀態來拿到文件的最後修改時間 也就是ctime 咱們把這個時間經過響應頭Last-Modified來告訴客戶端,客戶端再下一次請求的時候會經過請求頭If-Modified-Since把這個值帶給服務端,咱們只要判斷這兩個值是否相等,假如相等那麼也就是說 文件沒有被修改那麼咱們就告訴客戶端304 你直接讀緩存吧
res.setHeader('Last-Modified',statObj.ctime.toGMTString());
if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
res.statusCode = 304;
res.end();
return
}
// 修改了那麼咱們就直接返回新的內容
fs.createReadStream(p).pipe(res)
}else{
res.setHeader('Last-Modified',statObj.ctime.toGMTString());
if(req.headers['if-modified-since'] === statObj.ctime.toGMTString()){
res.statusCode = 304;
res.end();
return
}
fs.createReadStream(readPath).pipe(res)
}
} catch (error) {
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未發現文件`)
}
})
server.listen(3000)
複製代碼
咱們經過請求能夠看到,當咱們第一次請求事後,不管怎麼刷新請求都是304 直接讀取的緩存,假如咱們在服務端把這個文件修改了 那麼咱們就能看到又能請求到最新的內容了,這就是咱們經過協商緩存來處理的,咱們經過獲取到文件狀態來拿到文件的最後修改時間 也就是ctime 咱們把這個時間經過響應頭Last-Modified來告訴客戶端,客戶端再下一次請求的時候會經過請求頭If-Modified-Since把這個值帶給服務端,咱們只要判斷這兩個值是否相等,假如相等那麼也就是說 文件沒有被修改那麼咱們就告訴客戶端304 你直接讀緩存吧
再再再再再假如咱們在文件中刪除了字符a而後又還原了,那麼這時候保存咱們的文件的修改時間其實也發生了變化,可是其實咱們文件的真正內容並無發生變化,因此這時候其實客戶端繼續走緩存也是能夠的 ,咱們來看看這樣的緩存策略如何實現。node
let http = require('http');
let path = require('path');
let fs = require('fs');
let url = require('url');
let crypto = require('crypto');
let server = http.createServer();
server.on('request',(req,res)=>{
// 獲取到請求的路徑
let {pathname,query} = url.parse(req.url,true);
// 將路徑拼接成服務器上對應得文件路徑
let readPath = path.join(__dirname, 'public',pathname);
try {
// 獲取路徑狀態
let statObj = fs.statSync(readPath);
// 爲了方便測試 咱們告訴客戶端不要走強制緩存了
res.setHeader('Cache-Control','no-cache');
if(statObj.isDirectory()){
let p = path.join(readPath,'index.html');
let statObj = fs.statSync(p);
// 咱們經過流把文件讀取出來 而後對讀取問來的內容進行md5加密 獲得一個base64加密hash值
let rs = fs.createReadStream(p);
let md5 = crypto.createHash('md5');
let arr = [];
rs.on('data',(data)=>{
arr.push(data);
md5.update(data);
})
rs.on('end',(data)=>{
let r = md5.digest('base64');
// 而後咱們將這個hash值經過響應頭Etag傳給客戶端,客戶端再下一次請求的時候會把上一次的Etag值經過請求頭if-none-match帶過來,而後咱們就能夠繼續比對文件生成的hash值和上次產生的hash是否同樣 若是同樣說明文件內容沒有發生變化 就告訴客戶端304 讀取緩存
res.setHeader('Etag',r);
if(req.headers['if-none-match']===r){
res.statusCode=304;
res.end();
return;
}
res.end(Buffer.concat(arr))
})
}else{
let rs = fs.createReadStream(readPath);
let md5 = crypto.createHash('md5');
let arr = [];
rs.on('data',(data)=>{
arr.push(data);
md5.update(data);
})
rs.on('end',(data)=>{
let r = md5.digest('base64');
res.setHeader('Etag',r);
if(req.headers['if-none-match']===r){
res.statusCode=304;
res.end();
return;
}
res.end(Buffer.concat(arr))
})
}
} catch (error) {
console.log(error)
res.setHeader('Content-Type','text/html;charset=utf8')
res.statusCode = 404;
res.end(`未發現文件`)
}
})
server.listen(3000)
複製代碼
經過控制檯咱們能夠看出來 請求頭和響應頭中都有咱們上面所說的對應的值,可是從代碼裏咱們也能看出來,咱們每次在請求到來的時候都會把文件所有讀取出來而且進行加密生產hash而後再作對比,這樣其實十分的消耗性能,所以這種緩存方式也有他本身的缺點後端
咱們經過node來親自實現了三種緩存方式,咱們能夠總結出每種緩存方式對應的實現:瀏覽器