寫在最前
在平時的前端開發中我們經常會遇到這種操作。明明我程式碼更新了,咋刷出來還是以前的呢?是不是快取了?快清下快取看看!你看頁面是304,怪不得沒更新!等等很多情況。作者起初也不是很瞭解,因為這個不由前端來控制,都是後端的操作。故這次使用node也來寫一個控制快取的服務來真正搞明白這裡的道道。歡迎關注我的部落格,不定期更新中——
瀏覽器快取機制
在說這個服務如何寫之前我們先要明白瀏覽器快取到底是個啥。來看下這個簡略示意圖:
可以看到瀏覽器的快取機制分為兩個部分。1、當前快取是否過期?2、伺服器中的檔案是否有改動?
第一步:判斷當前快取是否過期
這是判斷是否啟用快取的第一步。如果瀏覽器通過某些條件(條件之後再說)判斷出來,ok現在這個快取沒有過期可以用,那麼連請求都不會發的,直接是啟用之前瀏覽器快取下來的那份檔案:
圖中看到這個css檔案快取沒有過期,被瀏覽器直接通過快取讀取了出來,注意這個時候是不會向瀏覽器請求的! 如果過期了就會向伺服器重新發起請求,但是不一定就會重新拉取檔案!
第二步:判斷伺服器中的檔案是否有改動
1、快取過期,檔案有改動
如果伺服器發現這個檔案改變了那麼你肯定不能再用以前瀏覽器的快取了,那就返回個200並且帶上新的檔案:
2、快取過期,檔案無改動
同時如果發現雖然那個快取雖然過期了,可你在伺服器端的檔案沒有變過,那麼伺服器只會給你返回一個頭資訊(304),讓你繼續用你那過期的快取,這樣就節省了很多傳輸檔案的時間頻寬啥的。看下圖:
過期了的快取需要請求一次伺服器,若伺服器判斷說這個檔案沒有改變還能用,那就返回304。瀏覽器認識304,它就會去讀取過期快取。否則就真的傳一份新檔案到瀏覽器。
如何判斷快取的過期以及檔案的變動?
在剛才的敘述中作者沒有提到具體的判斷過期及變動的實現方式,這也是為了可以讓童鞋們現有一個整體的概念,無關乎程式碼,至少通過上面一段講述,可以認識到“哦瀏覽器的快取是這樣一個流程”,就夠了。下面我們來看下具體的如何操作:
判斷快取過期
主要的方式有兩種,這兩種都是設定請求頭中的某一個欄位來實現的:1、Expires;2、Cache-Control。由於Cache-Control設定後優先順序比前者高,這次作者就先說下通過Cache-Control來控制快取。
可以看到Cache-Control欄位有很多值,其他的值有興趣的同學可以自己嘗試,現在作者要說最後一個值max-age;如果在請求頭中設定了
var maxAgeTime = 60 //過期時間
res.writeHead(200, {
"Cache-Control": 'max-age=' + maxAgeTime
})複製程式碼
那麼在60s內,如果再去請求這個檔案的話,是不會發起請求的。因為還沒有過期呢!唯一例外是如果這個檔案是你在瀏覽器位址列輸入的地址來請求的(比如你請求localhost:3030/static/style.css),當你重新整理的時候就會讓當前的這個檔案所設定的過期時間失效,直接去請求伺服器來看是返回個304還是返回新檔案。一般這麼請求的都是我們常說的入口檔案,入口檔案一重新整理就會重新向伺服器請求,但是入口檔案裡面所引入的檔案如js,css等不會隨著重新整理而另過期時間失效。除非你單找出來那個引入連結,通過瀏覽器位址列去查詢它並重新整理 :)。
判斷檔案變動
常用的方式為Etag和Last-Modified,思路上差不多,這裡作者只介紹Last-Modified的用法。
Last-Modified方式需要用到兩個欄位:Last-Modified & if-modified-since。
先來看下這兩個欄位的形式:
- Last-Modified : Fri , 12 May 2006 18:53:33 GMT
- If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT
可以看出其實形式是一樣的,就是一個標準時間。那麼怎麼用呢?來看下圖:
當第一次請求某一個檔案的時候,就會傳遞回來一個Last-Modified 欄位,其內容是這個檔案的修改時間。當這個檔案快取過期,瀏覽器又向伺服器請求這個檔案的時候,會自動帶一個請求頭欄位If-Modified-Since,其值是上一次傳遞過來的Last-Modified的值,拿這個值去和伺服器中現在這個檔案的最後修改時間做對比,如果相等,那麼就不會重新拉取這個檔案了,返回304讓瀏覽器讀過期快取。如果不相等就重新拉取。
快取機制流程
本次使用了Cache-Control&Last-Modified來做為快取機制的判斷條件。當然還有多種方式可以使用,希望瞭解更全面的同學可以去讀讀這篇文章:Web瀏覽器的快取機制
總結前兩個部分可以得出以下的流程圖,現在再看這張圖應該還是很明瞭的了。
node實現可快取的服務
var http = require("http")
var fs = require("fs")
var url = require("url")
http.createServer(function(req,res){
var pathname = url.parse(req.url).pathname
var fsPath = __dirname + pathname
fs.access(fsPath, fs.constants.R_OK, function(err){ //fs.constants.R_OK - path 檔案可被呼叫程式讀取
if(err) {
console.log(err) //可返回404,在此簡略程式碼不再演示
}else {
var file = fs.statSync(fsPath) //檔案資訊
var lastModified = file.mtime.toUTCString()
var ifModifiedSince = req.headers['if-modified-since']
//傳回Last-Modified後,再請求伺服器會攜帶if-modified-since值來和伺服器中的Last-Modified比較
var maxAgeTime = 3 //設定超時時間
if(ifModifiedSince && lastModified == ifModifiedSince) { //客戶端修改時間和服務端修改時間對比
res.writeHead(304,"Not Modified")
res.end()
} else {
fs.readFile(fsPath, function(err,file){
if(err) {
console.log('readFileError:', err)
}else {
res.writeHead(200,{
"Cache-Control": 'max-age=' + maxAgeTime,
"Last-Modified" : lastModified
})
res.end(file)
}
})
}
}
})
}).listen(3030)複製程式碼
程式碼很簡單,看註釋即可。這只是一個微小的服務,我們只是關注在檔案快取的方面。
最後
慣例po作者的部落格,不定時更新中——
有問題歡迎在issues下交流,捂臉求star=。=