寫在最前
在平時的前端開發中我們經常會遇到這種操作。明明我代碼更新了,咋刷出來還是以前的呢?是不是緩存了?快清下緩存看看!你看頁面是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=。=