強緩存
HTTP/1.0 使用的是 Expires
字段至朗,HTTP/1.1 使用的是 Cache-Control
字段词渤。
-
Expires
即過期時間嗓违,時間是相對于服務器的時間而言的干像,存在于服務端返回的響應頭中蛙埂,在這個過期時間之前可以直接從緩存里面獲取數(shù)據(jù)倦畅,無需再次請求。但這種方式存在一個問題:無需再次請求服務器的時間和瀏覽器的時間可能并不一致绣的。 - 在 HTTP 1.1 中叠赐,使用的是
Cache-Control
字段,這個字段采用的時間是過期時長屡江,對應的字段是max-age=*
芭概。
以下是使用 Node 模擬的簡單示例:
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const time = new Date()
time.setTime(time.getTime() + 100 * 1000) // 時間戳計算后 10s
const expires = time.toUTCString() // GMT(格林尼治標準時間)時間格式
res.setHeader('Expires', expires)
// 優(yōu)先級高于 Expires
res.setHeader('Cache-Control', 'max-age=30')
const html = fs.readFileSync('./src/index.html', 'utf8')
res.end(html)
})
server.listen(3000)
當 Expires
和 Cache-Control
同時存在時,優(yōu)先考慮 Cache-Control
字段惩嘉。當緩存資源失效了罢洲,也就是沒有命中強緩存,接下來就進入?yún)f(xié)商緩存文黎。
協(xié)商緩存
如果緩存過期了惹苗,我們就可以使用協(xié)商緩存來解決問題。協(xié)商緩存需要請求耸峭,如果緩存有效會返回 304桩蓉。協(xié)商緩存需要客戶端和服務端共同實現(xiàn),和強緩存?樣劳闹,也有兩種實現(xiàn)方式 Last-Modified
和 ETag
Last-Modified
Last-Modified
表示本地文件最后修改時間院究,If-Modified-Since
會將 Last-Modified
的值發(fā)送給服務器洽瞬,詢問服務器在該時間后資源是否有更新,有更新的話就會將新的資源發(fā)送回來业汰。
以下是使用 Node 模擬的簡單示例:
const http = require('http')
const fs = require('fs')
const server = http.createServer((req, res) => {
const reqModified = req.headers['if-modified-since']
const info = fs.statSync('./src/index.html')
const lastModified = info.mtime.toUTCString() // GTM
if (reqModified && reqModified === lastModified) {
console.log('瀏覽器走緩存')
res.statusCode = 304
res.end()
return
}
res.setHeader('Last-Modified', lastModified)
const html = fs.readFileSync('./src/index.html', 'utf8')
res.end(html)
})
server.listen(3000)
但是如果在本地打開緩存文件伙窃,就會造成 Last-Modified
被修改,所以在 HTTP/1.1 出現(xiàn)了 ETag
蔬胯。
ETag
ETag
類似于文件指紋对供,If-None-Match
會將當前 ETag
發(fā)送給服務器,詢問該資源 ETag
是否變動氛濒,有變動的話就將新的資源發(fā)送回來产场。并且 ETag
優(yōu)先級? Last-Modified
高。
const http = require('http')
const fs = require('fs')
const crypto = require('crypto')
const server = http.createServer((req, res) => {
const buffer = fs.readFileSync('./src/index.html') // 二進制文件流
const hashTool = crypto.createHash('md5') // 使用 md5 加密算法
hashTool.update(buffer, 'utf8') // 注入二進制
const md5 = hashTool.digest('hex') // 生成 md5 加密的唯一標識 hash
const reqETag = req.headers['if-none-match']
if (reqETag && reqETag === md5) {
console.log('ETag 緩存')
res.statusCode = 304
res.end()
return
}
const reqModified = req.headers['if-modified-since']
const info = fs.statSync('./src/index.html')
const lastModified = info.mtime.toUTCString() // GTM
if (reqModified && reqModified === lastModified) {
console.log('modified 緩存')
res.statusCode = 304
res.end()
return
}
res.setHeader('Last-Modified', lastModified)
res.setHeader('ETag', md5)
const html = fs.readFileSync('./src/index.html', 'utf8')
res.end(html)
})
server.listen(3000)
注意:以上使用 Node 進行模擬的示例舞竿,真實情況具體如何要看服務器那邊如何配置京景。
Last-Modified
與 ETag
的對比
- 性能上,
Last-Modified
優(yōu)于ETag
骗奖,Last-Modified
記錄的是時間點确徙,而Etag
需要根據(jù)文件的 MD5 算法生成對應的 hash 值。 - 精度上执桌,
ETag
優(yōu)于Last-Modified
鄙皇。ETag
按照內(nèi)容給資源帶上標識,能準確感知資源變化仰挣,Last-Modified
在某些場景并不能準確感知變化伴逸。
如果兩者都存在,優(yōu)先判斷 If-None-Match
進行 ETag
協(xié)商緩存膘壶。
緩存位置
當命中強緩存和協(xié)商緩存返回 304 時错蝴,瀏覽器會從緩存中獲取資源。
瀏覽器中的緩存位置一共有四種颓芭,按優(yōu)先級從高到低排列分別是:
- Service Worker — 其借鑒了 Web Worker 思路顷锰,主要功能有:離線緩存、消息推送和網(wǎng)絡代理亡问,其中離線緩存就是
Service Worker Cache
官紫。 - Memory Cache — 內(nèi)存緩存,從效率上講它是最快的州藕,從存活時間來講又是最短的万矾,當渲染進程結(jié)束后,內(nèi)存緩存也就不存在了慎框。
- Disk Cache — 存儲在磁盤中的緩存良狈,從存取效率上講是比內(nèi)存緩存慢的,優(yōu)勢在于存儲容量和存儲時長笨枯。
- Push Cache — 推送緩存薪丁,它瀏覽器緩存的最后一道防線遇西,它是
HTTP/2
的內(nèi)容,詳細內(nèi)容可以查看 HTTP/2 push is tougher than I thought
瀏覽器在選擇 Disk Cache 與 Memory Cache 的存儲上:內(nèi)容使用率高的严嗜,文件優(yōu)先進入磁盤粱檀。比較大的 JS、CSS 文件會直接放入磁盤漫玄,反之放入內(nèi)存茄蚯。
強緩存與協(xié)商緩存區(qū)別
- 強緩存 — 瀏覽器不會與服務端協(xié)商,而是直接獲取瀏覽器緩存睦优。
- 協(xié)商緩存 — 瀏覽器會先向服務器確認資源的有效性后渗常,才決定是從緩存中獲取資源還是重新獲取資源。
- 強緩存在瀏覽器進行判斷汗盘,而協(xié)商緩存在服務端進行判斷
總結(jié)
首先檢查 Cache-Control
皱碘,驗證強緩存是否可用
- 如果可用的話,直接使用
- 否則進入?yún)f(xié)商緩存隐孽,發(fā)送 HTTP 請求癌椿,服務器通過請求頭中的
If-Modified-Since
或者If-None-Match
字段檢查資源是否更新- 資源更新,返回資源和 200 狀態(tài)碼菱阵。
- 否則踢俄,返回 304,告訴瀏覽器直接從緩存中獲取資源晴及。
本文首發(fā) blog褪贵,如果喜歡或者有所啟發(fā),歡迎 Star
抗俄,對作者也是一種鼓勵。