基于node的微小服務(wù)——細(xì)說(shuō)緩存與304

寫在最前

在平時(shí)的前端開發(fā)中我們經(jīng)常會(huì)遇到這種操作。明明我代碼更新了,咋刷出來(lái)還是以前的呢?是不是緩存了?快清下緩存看看!你看頁(yè)面是304,怪不得沒更新!等等很多情況。作者起初也不是很了解,因?yàn)檫@個(gè)不由前端來(lái)控制,都是后端的操作。故這次使用node也來(lái)寫一個(gè)控制緩存的服務(wù)來(lái)真正搞明白這里的道道。歡迎關(guān)注我的博客,不定期更新中——

瀏覽器緩存機(jī)制

在說(shuō)這個(gè)服務(wù)如何寫之前我們先要明白瀏覽器緩存到底是個(gè)啥。來(lái)看下這個(gè)簡(jiǎn)略示意圖:

可以看到瀏覽器的緩存機(jī)制分為兩個(gè)部分。1、當(dāng)前緩存是否過(guò)期?2、服務(wù)器中的文件是否有改動(dòng)?

第一步:判斷當(dāng)前緩存是否過(guò)期

這是判斷是否啟用緩存的第一步。如果瀏覽器通過(guò)某些條件(條件之后再說(shuō))判斷出來(lái),ok現(xiàn)在這個(gè)緩存沒有過(guò)期可以用,那么連請(qǐng)求都不會(huì)發(fā)的,直接是啟用之前瀏覽器緩存下來(lái)的那份文件:


圖中看到這個(gè)css文件緩存沒有過(guò)期,被瀏覽器直接通過(guò)緩存讀取了出來(lái),注意這個(gè)時(shí)候是不會(huì)向?yàn)g覽器請(qǐng)求的! 如果過(guò)期了就會(huì)向服務(wù)器重新發(fā)起請(qǐng)求,但是不一定就會(huì)重新拉取文件!

第二步:判斷服務(wù)器中的文件是否有改動(dòng)

1、緩存過(guò)期,文件有改動(dòng)

如果服務(wù)器發(fā)現(xiàn)這個(gè)文件改變了那么你肯定不能再用以前瀏覽器的緩存了,那就返回個(gè)200并且?guī)闲碌奈募?/p>

2、緩存過(guò)期,文件無(wú)改動(dòng)

同時(shí)如果發(fā)現(xiàn)雖然那個(gè)緩存雖然過(guò)期了,可你在服務(wù)器端的文件沒有變過(guò),那么服務(wù)器只會(huì)給你返回一個(gè)頭信息(304),讓你繼續(xù)用你那過(guò)期的緩存,這樣就節(jié)省了很多傳輸文件的時(shí)間帶寬啥的??聪聢D:

過(guò)期了的緩存需要請(qǐng)求一次服務(wù)器,若服務(wù)器判斷說(shuō)這個(gè)文件沒有改變還能用,那就返回304。瀏覽器認(rèn)識(shí)304,它就會(huì)去讀取過(guò)期緩存。否則就真的傳一份新文件到瀏覽器。

如何判斷緩存的過(guò)期以及文件的變動(dòng)?

在剛才的敘述中作者沒有提到具體的判斷過(guò)期及變動(dòng)的實(shí)現(xiàn)方式,這也是為了可以讓童鞋們現(xiàn)有一個(gè)整體的概念,無(wú)關(guān)乎代碼,至少通過(guò)上面一段講述,可以認(rèn)識(shí)到“哦瀏覽器的緩存是這樣一個(gè)流程”,就夠了。下面我們來(lái)看下具體的如何操作:

判斷緩存過(guò)期

主要的方式有兩種,這兩種都是設(shè)定請(qǐng)求頭中的某一個(gè)字段來(lái)實(shí)現(xiàn)的:1、Expires;2、Cache-Control。由于Cache-Control設(shè)置后優(yōu)先級(jí)比前者高,這次作者就先說(shuō)下通過(guò)Cache-Control來(lái)控制緩存。

可以看到Cache-Control字段有很多值,其他的值有興趣的同學(xué)可以自己嘗試,現(xiàn)在作者要說(shuō)最后一個(gè)值max-age;如果在請(qǐng)求頭中設(shè)定了

var maxAgeTime = 60 //過(guò)期時(shí)間
res.writeHead(200, {
    "Cache-Control": 'max-age=' + maxAgeTime
})

那么在60s內(nèi),如果再去請(qǐng)求這個(gè)文件的話,是不會(huì)發(fā)起請(qǐng)求的。因?yàn)檫€沒有過(guò)期呢!唯一例外是如果這個(gè)文件是你在瀏覽器地址欄輸入的地址來(lái)請(qǐng)求的(比如你請(qǐng)求localhost:3030/static/style.css),當(dāng)你刷新的時(shí)候就會(huì)讓當(dāng)前的這個(gè)文件所設(shè)定的過(guò)期時(shí)間失效,直接去請(qǐng)求服務(wù)器來(lái)看是返回個(gè)304還是返回新文件。一般這么請(qǐng)求的都是我們常說(shuō)的入口文件,入口文件一刷新就會(huì)重新向服務(wù)器請(qǐng)求,但是入口文件里面所引入的文件如js,css等不會(huì)隨著刷新而另過(guò)期時(shí)間失效。除非你單找出來(lái)那個(gè)引入鏈接,通過(guò)瀏覽器地址欄去查詢它并刷新 :)。

判斷文件變動(dòng)

常用的方式為Etag和Last-Modified,思路上差不多,這里作者只介紹Last-Modified的用法。

Last-Modified方式需要用到兩個(gè)字段:Last-Modified & if-modified-since。
先來(lái)看下這兩個(gè)字段的形式:

  • Last-Modified : Fri , 12 May 2006 18:53:33 GMT
  • If-Modified-Since : Fri , 12 May 2006 18:53:33 GMT

可以看出其實(shí)形式是一樣的,就是一個(gè)標(biāo)準(zhǔn)時(shí)間。那么怎么用呢?來(lái)看下圖:



當(dāng)?shù)谝淮握?qǐng)求某一個(gè)文件的時(shí)候,就會(huì)傳遞回來(lái)一個(gè)Last-Modified 字段,其內(nèi)容是這個(gè)文件的修改時(shí)間。當(dāng)這個(gè)文件緩存過(guò)期,瀏覽器又向服務(wù)器請(qǐng)求這個(gè)文件的時(shí)候,會(huì)自動(dòng)帶一個(gè)請(qǐng)求頭字段If-Modified-Since,其值是上一次傳遞過(guò)來(lái)的Last-Modified的值,拿這個(gè)值去和服務(wù)器中現(xiàn)在這個(gè)文件的最后修改時(shí)間做對(duì)比,如果相等,那么就不會(huì)重新拉取這個(gè)文件了,返回304讓瀏覽器讀過(guò)期緩存。如果不相等就重新拉取。

緩存機(jī)制流程

本次使用了Cache-Control&Last-Modified來(lái)做為緩存機(jī)制的判斷條件。當(dāng)然還有多種方式可以使用,希望了解更全面的同學(xué)可以去讀讀這篇文章:Web瀏覽器的緩存機(jī)制

總結(jié)前兩個(gè)部分可以得出以下的流程圖,現(xiàn)在再看這張圖應(yīng)該還是很明了的了。


node實(shí)現(xiàn)可緩存的服務(wù)

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 文件可被調(diào)用進(jìn)程讀取
        if(err) {
          console.log(err) //可返回404,在此簡(jiǎn)略代碼不再演示
        }else {
          var file = fs.statSync(fsPath) //文件信息
          var lastModified = file.mtime.toUTCString()
          var ifModifiedSince = req.headers['if-modified-since']
          //傳回Last-Modified后,再請(qǐng)求服務(wù)器會(huì)攜帶if-modified-since值來(lái)和服務(wù)器中的Last-Modified比較
          var maxAgeTime = 3 //設(shè)置超時(shí)時(shí)間
          if(ifModifiedSince && lastModified == ifModifiedSince) { //客戶端修改時(shí)間和服務(wù)端修改時(shí)間對(duì)比
              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)

代碼很簡(jiǎn)單,看注釋即可。這只是一個(gè)微小的服務(wù),我們只是關(guān)注在文件緩存的方面。

最后

慣例po作者的博客,不定時(shí)更新中——
有問(wèn)題歡迎在issues下交流,捂臉求star=。=

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容