一、瀏覽器第一次發起一個http/https請求,讀取服務器的資源html
二、服務端設置響應頭(cache-control、Expires、last-modified、Etag)給瀏覽器web
2.1. cache-control、Expires 屬於強緩存,last-modified、Etag屬於對比緩存(協商緩存)數據庫
三、瀏覽器不關閉tab、f5刷新頁面(再次發起一個請求給服務器)瀏覽器
3.一、若是cache-control的max-age 和 Expires 未超過緩存時間,全部資源除了index.html 都來自於內存緩存(from memory cache)加載。且狀態碼爲200緩存
3.二、若是cache-control的max-age緩存時間爲5s, Expires的過時時間是超過5s,則cache-control會覆蓋Expiresbash
3.三、若是強緩存失效,則下一步會走對比緩存。瀏覽器會從第二步的拿到的響應頭,在刷新發起請求會設置 a、if-modified-since值爲響應的last-modified的值; b、if-none-match 值爲響應的Etag的值;服務器
3.四、若是if-modified-since 和if-none-match都存在,則if-none-match的優先比if-modified-since高。直接對比第二步給瀏覽器的Etag的值,若是相等就直接返回一個狀態爲304不返回內容,若是不相等就返回一個狀態碼爲200,而且會返回內容和cache-control 、Expires、last-modified、Etag等響應頭;網絡
3.五、若是if-modified-since 存在, if-none-match不存在,步驟跟上述的3.4相似,只不過服務端對比的是if-modified-since 和第一次返回給瀏覽器last-modified的值性能
四、若是瀏覽器關閉tab。從新打開新tab,發起請求資源。步驟跟上述3相似,只不過在上述3.1中,左右資源除了index.html緩存(from disk cache)都從磁盤加載。優化
當客戶端請求後,會先訪問緩存數據庫看緩存是否存在。若是存在則直接返回,不存在則請求真的服務器。
強制緩存直接減小請求數,是提高最大的緩存策略。 它的優化覆蓋了文章開頭提到過的請求數據的所有三個步驟。若是考慮使用緩存來優化網頁性能的話,強制緩存應該是首先被考慮的。
能夠形成強制緩存的字段是 Cache-control 和 Expires
Expires
這是 HTTP 1.0 的字段,表示緩存到期時間,是一個絕對的時間 (當前時間+緩存時間)。在響應消息頭中,設置這個字段以後,就能夠告訴瀏覽器,在未過時以前不須要再次請求。
Expires: Thu, 22 Mar 2029 16:06:42 GMT
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
res.setHeader('Expires', new Date(Date.now() + 20000).toGMTString());
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('not found')
return
}
if(stat.isFile()) {
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
複製代碼
以上代碼給Expires設置過時時間爲20s。
首次請求 首次請求 所有走網絡請求
20s內F5刷新當前,從內存裏面加載。由於咱們沒有關閉TAB,因此瀏覽器把緩存的應用加到了內存緩存。(耗時0ms,也就是1ms之內)
20s內關閉tab,打開請求的url,從磁盤加載
關閉了TAB,內存緩存也隨之清空。可是磁盤緩存是持久的,因而全部資源來自磁盤緩存。(大約耗時3ms,由於文件有點小)並且對比2和3,很明顯看到內存緩存仍是比disk cache快得多的。20s之後請求,緩存已經失效,重複第1步
過時的缺點:
在這裏,其餘電腦訪問服務器,若修改電腦的本地時間,會致使瀏覽器判斷緩存失效 這裏修從新修改緩存時間: res.setHeader('Expires',new Date(Date.now()+ 2000000).toGMTString())
Cache-control
已知Expires的缺點以後,在HTTP/1.1中,增長了一個字段Cache-control,該字段表示資源緩存的最大有效時間,在該時間內,客戶端不須要向服務器發送請求
Expires 和 Cache-control 區別
Expires設置的是 絕對時間
Cache-control設置的是 相對時間
緩存控制的優先級大於到期
複製代碼
Cache-control: max-age=20
const http = require('http')
const url = require('url')
const path = require('path')
const fs = require('fs')
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
res.setHeader('Cache-Control', 'max-age=20')
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('not found')
return
}
if(stat.isFile()) {
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
複製代碼
以上代碼給cache-control設置max-age爲20s
解析:首次請求->關閉tab再次請求參考Expires的圖
no-store 和 no-cache的區別
no-store: 若是服務器再響應中設置了no-store。那麼瀏覽器不會存儲此次相應的數據,當下次請求時,瀏覽器會在請求一次,就是說不會對比Etag res.setHeader('Cache-control', 'no-store')
no-cache 若是服務器在響應中設置了no-cache,那麼說明瀏覽器在使用緩存前會對比Etag,返回304就會避免修改
public 和 private
當強制緩存失效(超過規定時間)時,就須要使用對比緩存,由服務器決定緩存內容是否失效。對比緩存是能夠和強制緩存一塊兒使用。
last-modified
一、服務器在響應頭中設置last-modified字段返回給客戶端,告訴客戶端資源最後一次修改的時間。
Last-Modified: Sat, 30 Mar 2019 05:46:11 GMT
二、瀏覽器在這個值和內容記錄在瀏覽器的緩存數據庫中。
三、下次請求相同資源,瀏覽器將在請求頭中設置if-modified-since的值(這個值就是第一步響應頭中的Last-Modified的值)傳給服務器
四、服務器收到請求頭的if-modified-since的值與last-modified的值比較,若是相等,表示未進行修改,則返回狀態碼爲304;若是不相等,則修改了,返回狀態碼爲200,並返回數據
http.createServer((req, res) => {
let { pathname } = url.parse(req.url, true);
console.log(pathname);
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('Not Fount');
return
}
if(stat.isFile()) {
res.setHeader('Last-Modified', stat.ctime.toGMTString())
console.log(stat.ctime.toGMTString())
if(req.headers['if-modified-since'] === stat.ctime.toGMTString()) {
console.log('if-modifined-since', req.headers['if-modified-since'])
res.statusCode = 304;
res.end()
return
}
fs.createReadStream(abs).pipe(res)
}
})
}).listen(3000)
複製代碼
last-modified的缺點:
Etag
爲了解決上述問題,出現了一組新的字段 Etag 和 If-None-Match
Etag是根絕文件內容,算出一個惟一的值。服務器存儲着文件的 Etag 字段。以後的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新時間改變成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 變成了 If-None-Match。服務器一樣進行比較,命中返回 304, 不命中返回新資源和 200。 Etag 的優先級高於 Last-Modified
http.createServer(function(req, res) {
let { pathname } = url.parse(req.url, true);
console.log(pathname)
let abs = path.join(__dirname, pathname);
fs.stat(path.join(__dirname, pathname), (err, stat) => {
if(err) {
res.statusCode = 404;
res.end('Not Found')
return
}
if(stat.isFile()) {
//Etag 實體內容,他是根絕文件內容,算出一個惟一的值。
let md5 = crypto.createHash('md5')
let rs = fs.createReadStream(abs)
let arr = []; // 你要先寫入響應頭再寫入響應體
rs.on('data', function(chunk) {
md5.update(chunk);
arr.push(chunk)
})
rs.on('end', function() {
let etag = md5.digest('base64');
if(req.headers['if-none-match'] === etag) {
console.log(req.headers['if-none-match'])
res.statusCode = 304;
res.end()
return
}
res.setHeader('Etag', etag)
// If-None-Match 和 Etag 是一對, If-None-Match是瀏覽器的, Etag是服務端的
res.end(Buffer.concat(arr))
})
}
})
}).listen(3000)
複製代碼
Etag的缺點: