最近在看面試題的時候總會看到有一些關於http快取的題目,但是總是一知半解,不甚理解;尤其是http頭資訊中有一大堆的字段,什麼if-modified-since,什麼if-none-match,真是令人頭疼。後來突然想到,要是能通過自己構建乙個伺服器,自己新增頭資訊,然後看實現的效果,不就更好了麼。說幹就幹,在網上各種找資料,然後再使用expressjs新增各種頭資訊,就能夠很好的理解http快取了。
個人部落格了解下謝小飛的部落格瀏覽器和伺服器之間通訊是通過http協議,http協議永遠都是客戶端發起請求,伺服器回送響應。模型如下:
http報文就是瀏覽器和伺服器間通訊時傳送及響應的資料塊。瀏覽器向伺服器請求資料,傳送請求(request)報文;伺服器向瀏覽器返回資料,返回響應(response)報文。報文資訊主要分為兩部分:
資料主體部分:http請求真正想要傳輸的資料內容
欄位名稱
字段所屬
pragma
通用頭expires
響應頭cache-control
通用頭last-modified
響應頭if-modified-sice
請求頭etag
響應頭if-none-match
請求頭
http快取可以分為兩大類,強制快取(也稱強快取)和協商快取。兩類快取規則不同,強制快取在快取資料未失效的情況下,不需要再和伺服器發生互動;而協商快取,顧名思義,需要進行比較判斷是否可以使用快取。
兩類快取規則可以同時存在,強制快取優先順序高於協商快取,也就是說,當執行強制快取的規則時,如果快取生效,直接使用快取,不再執行協商快取規則。
我們先簡單搭建乙個express的伺服器,不加任何快取資訊頭。
})複製**我們可以看到請求結果如下:
請求過程如下:
看得出來這種請求方式的流量與請求次數有關,同時,缺點也很明顯:
接下來我們開始在頭資訊中新增快取資訊。
強制快取分為兩種情況,expires和cache-control。
expires的值是伺服器告訴瀏覽器的快取過期時間(值為gmt時間,即格林尼治時間),即下一次請求時,如果瀏覽器端的當前時間還沒有到達過期時間,則直接使用快取資料。下面通過我們的express伺服器來設定一下expires響應頭資訊。
//其他**...
const moment = require('moment');
let jspath = path.resolve(__dirname,'./static/js/demo.js');
let cont = fs.readfilesync(jspath);
res.setheader('expires', getglnz()) //2分鐘
res.end(cont)
})function
getglnz()
//其他**...
複製**
我們在demo.js中新增了乙個expires響應頭,不過由於是格林尼治時間,所以通過momentjs轉換一下。第一次請求的時候還是會向伺服器發起請求,同時會把過期時間和檔案一起返回給我們;但是當我們重新整理的時候,才是見證奇蹟的時刻:
可以看出檔案是直接從快取(memory cache)中讀取的,並沒有發起請求。我們在這邊設定過期時間為兩分鐘,兩分鐘過後可以重新整理一下頁面看到瀏覽器再次傳送請求了。
雖然這種方式新增了快取控制,節省流量,但是還是有以下幾個問題的:
不過expires 是http 1.0的東西,現在預設瀏覽器均預設使用http 1.1,所以它的作用基本忽略。
針對瀏覽器和伺服器時間不同步,加入了新的快取方案;這次伺服器不是直接告訴瀏覽器過期時間,而是告訴乙個相對時間cache-control=10秒,意思是10秒內,直接使用瀏覽器快取。
let jspath = path.resolve(__dirname,'./static/js/demo.js');
let cont = fs.readfilesync(jspath);
res.setheader('cache-control', 'public,max-age=120') //2分鐘
res.end(cont)
})複製**
強制快取的弊端很明顯,即每次都是根據時間來判斷快取是否過期;但是當到達過期時間後,如果檔案沒有改動,再次去獲取檔案就有點浪費伺服器的資源了。協商快取有兩組報文結合使用:
last-modified和if-modified-since
etag和if-none-match
為了節省伺服器的資源,再次改進方案。瀏覽器和伺服器協商,伺服器每次返回檔案的同時,告訴瀏覽器檔案在伺服器上最近的修改時間。請求過程如下:
**實現過程如下:
let jspath = path.resolve(__dirname,'./static/js/demo.js')
let cont = fs.readfilesync(jspath);
let status = fs.statsync(jspath)
let lastmodified = status.mtime.toutcstring()
if(lastmodified === req.headers['if-modified-since']) else
})複製**
我們多次重新整理頁面,可以看到請求結果如下:
雖然這個方案比前面三個方案有了進一步的優化,瀏覽器檢測檔案是否有修改,如果沒有變化就不再傳送檔案;但是還是有以下缺點:
為了解決檔案修改時間不精確帶來的問題,伺服器和瀏覽器再次協商,這次不返回時間,返回檔案的唯一標識etag。只有當檔案內容改變時,etag才改變。請求過程如下:
const md5 = require('md5');
let jspath = path.resolve(__dirname,'./static/js/demo.js');
let cont = fs.readfilesync(jspath);
let etag = md5(cont);
if(req.headers['if-none-match'] === etag) else
})複製**
請求結果如下:
在報文頭的**中我們可以看到有乙個欄位叫pragma,這是一段塵封的歷史....
在「遙遠的」http1.0時代,給客戶端設定快取方式可通過兩個欄位--pragma和expires。雖然這兩個欄位早可拋棄,但為了做http協議的向下相容,你還是可以看到很多**依舊會帶上這兩個字段。
當該字段值為no-cache
的時候,會告訴瀏覽器不要對該資源快取,即每次都得向伺服器發一次請求才行。
res.setheader('pragma', 'no-cache') //禁止快取
res.setheader('cache-control', 'public,max-age=120') //2分鐘
複製**
通過pragma來禁止快取,通過cache-control設定兩分鐘快取,但是重新訪問我們會發現瀏覽器會再次發起一次請求,說明了pragma的優先順序高於cache-control
我們看到cache-control中有乙個屬性是public,那麼這代表了什麼意思呢?其實cache-control不光有max-age,它常見的取值private、public、no-cache、max-age,no-store,預設值為private,各個取值的含義如下:
所以我們在重新整理頁面的時候,如果只按f5只是單純的傳送請求,按ctrl+f5會發現請求頭上多了兩個欄位pragma: no-cache和cache-control: no-cache。
上面我們說過強制快取的優先順序高於協商快取,pragma的優先順序高於cache-control,那麼其他快取的優先順序順序怎麼樣呢?網上查閱了資料得出以下順序(ps:有興趣的童鞋可以驗證一下正確性告訴我):
pragma > cache-control > expires > etag > last-modified
Http快取機制
快取快取,就是把需要的東西存起來,不需要每次都去請求。主要目的減小伺服器壓力,放到客戶端上來講,還利於節省流量,還能流暢的把ui顯示出來,提高了使用者體驗。對於http快取來講,主要的就是校驗快取的有效性,也就是新鮮度。如果客戶端不能及時響應服務端的資料變化,快取一直不能被更新,那不就是得不償失了?...
HTTP 快取機制
基於 header的示例 content length 3534http快取策略分為 1 快取策略 cache control 頭里的 public private no cache max age no store 其中no store為不儲存,no cache 0秒的max age 2 快取過期...
http快取機制
首先需要了解http協議的響應頭中的幾個欄位的含義 cache control expires 該欄位表示資源的過期時間。etag 該欄位表示資源的唯一標識。last modified 該欄位表示資源的最後修改時間。有以下2個問題需要注意 為什麼優先校驗etag,後校驗last modified?因...