1.1 - 緩存
- 緩存可以重用已獲取的資源能夠有效的提升網站與應用的性能。
- Web 緩存能夠減少延遲與網絡阻塞,進而減少顯示某個資源所用的時間。
- 藉助 HTTP 緩存,Web 站點變得更具有響應性。
- 緩存分爲兩點:強制緩存和協商緩存
1.2 - 強制緩存
- 概念
- 強制緩存就是向瀏覽器緩存查找該請求結果,並根據該結果的緩存規則來決定是否使用該緩存結果的過程。
- 簡單來講就是強制瀏覽器使用當前緩存
- 所請求的數據在緩存數據庫中尚未過期時,不與服務器進行交互,直接使用緩存數據庫中的數據。當緩存未過期時基本流程如下:
- 實現:通過服務器端設置響應頭字段來控制強制緩存的過期時間
- expires (http1.0) 其指定了一個日期/時間, 在這個日期/時間之後,HTTP響應被認爲是過時的。但是它本身是一個HTTP1.0標準下的字段,所以如果請求中還有一個置了 “max-age” 或者 “s-max-age” 指令的Cache-Control響應頭,那麼 Expires 頭就會被忽略。
- cache-control (http1.1) 通用消息頭用於在http 請求和響應中通過指定指令來實現緩存機制。
- cache-control 優先級比 expires 高
- expires
- 日期(new Date().toGMTString()) 緩存的最大有效時間
- cache-control
- max-age(單位s) 緩存的最大有效時間
- no-cache 使用協商緩存
- no-store 不使用任何緩存
- public (客戶端、代理服務器)緩存所有資源
- private 默認值,只有客戶端緩存所有資源
- 比如 jQuery 用強制緩存(長久不變,讀取不了新東西),因爲變化慢,還有一些圖片,一直變,有些廣告變動很快,就走協商緩存。
1.3-協商緩存
- 概念
- 協商緩存就是強制緩存失效後,瀏覽器攜帶緩存標識向服務器發起請求,由服務器根據緩存標識決定是否使用緩存的過程。
- 當強緩存過期未命中或者響應報文Cache-Control中有must-revalidate標識必須每次請求驗證資源的狀態時,便使用協商緩存的方式去處理緩存文件。
協商緩存主要原理是從緩存數據庫中取出緩存的標識,然後向瀏覽器發送請求驗證請求的數據是否已經更新,如果已更新則返回新的數據,若未更新則使用緩存數據庫中的緩存數據,具體流程如下,當協商緩存命中:
- 實現:
- Last-Modified / If-Modified-Since
- Etag / If-None-Match
- Etag / If-None-Match 優先級比 Last-Modified / If-Modified-Since 高。
- Last-modified
- 記錄服務器返回資源的最後修改時間
- 由客戶端發送給服務器
- If-Modified-Since
- 記錄服務器返回資源的最後修改時間
- 由服務器發送給客戶端
- Etag
- 當前文件的唯一標識(由服務器生成)
- 由客戶端發送給服務器
- If-None-Match
- 當前文件的唯一標識(由服務器生成)
- 由服務器發送給客戶端
- 工作流程:
- 第一次:由服務器返回 If-None-Match 和 If-Modified-Since 字段通過響應頭方式返回
- 第二次:下次瀏覽器請求時,攜帶了Etag(值爲上一次的If-None-Match的值)和 Last-modified(值爲上一次的If-None-Since的值)發送給服務器
- 服務器先檢查Etag是否等於最新的If-None-Match的值,如果相等直接走瀏覽器本地緩存,不相等就返回新的資源
- 如果沒有Etag,纔看Last-modified的值,檢查Last-modified是否等於最新的If-None-Since的值,如果相等直接走瀏覽器本地緩存,不相等就返回新的資源
1.4 - 緩存返回值
- 200(from memory cache)
- 命中強制緩存
- 緩存來自於內存
- 200(from disk cache)
- 命中強制緩存
- 緩存來自於磁盤
- 304 Not Modified
- 命中協商緩存
- 200
- 沒有命中緩存
1.5-瀏覽器緩存機制詳解流程圖
1.6-總結緩存策略
- 強制緩存(優先級更高)
- Expires cache-control
- 不會重新發送請求,直接走緩存
- 協商緩存
- last-modified和etag if-modified-since和if-None-Match
- 通常和cache-control配合使用,no-cached,會再次發送請求,由服務器判斷請求資源是否走緩存,
- 如果走 返回 304 Not Modefined
- 如果不走緩存 返回新的資源
1.7-cache緩存案例
const express = require('express');
const { resolve } = require('path');
const { readFile, stat, watchFile } = require('fs');
const etag = require('etag');
const app = express();
/*
緩存:
強制緩存
http 1.1 cache-control
http 1.0 expires
特點:在緩存的期限內,不會發送請求,直接讀取緩存
協商緩存
響應頭(服務器發給瀏覽器)
etag 文件唯一標識
last-modified 文件最近一次修改時間
請求頭
if-none-match 文件唯一標識
if-modified-since 文件最近一次修改時間
流程:
第一次瀏覽器訪問服務器資源: 服務器設置響應頭並返回資源給瀏覽器
etag xxx
last-modified xxx
瀏覽器接受到服務器返回的響應頭,就會存儲起來。
第二次瀏覽器訪問服務器資源,自動攜帶上之前存的響應頭,作爲請求頭髮送過去(會改名字)
etag --> if-none-math
last-modified --> if-modified-since
服務器
判斷服務器保存的etag和瀏覽器發送過來if-none-math的值是否相等
判斷服務器保存的last-modified和瀏覽器發送過來if-modified-since的值是否相等
如果都相等,走協商緩存 304
如果有一個不相等,說明資源發生過變化,返回新資源
特點:一定會訪問服務器(一定發送請求),由服務器決定是否走緩存
當強制緩存和協商緩存都存在:
先看強制緩存。命中了,就直接讀取緩存
如果強制緩存失效(過期了),看協商緩存
*/
app.get('/', (req, res) => {
// 訪問當前路由,返回index.html
// sendFile方法默認設置強制緩存、協商緩存
// res.sendFile(resolve(__dirname, 'public/index.html'));
const filePath = resolve(__dirname, 'public/index.html');
readFile(filePath, (err, data) => {
if (!err) {
// 返回響應
res.end(data);
} else {
console.log(err);
}
});
});
// 強制緩存
app.get('/js/index.js', (req, res) => {
const filePath = resolve(__dirname, 'public/js/index.js');
readFile(filePath, (err, data) => {
if (!err) {
// 設置強制緩存 設置響應頭
res.set('cache-control', 'max-age=20');
// res.set('expires', new Date(Date.now() + 10000).toGMTString());
// 返回響應
res.end(data);
} else {
console.log(err);
}
});
});
let etagName = '';
let lastModified = 0;
const filePath = resolve(__dirname, 'public/css/index.css');
// 監視文件的變化
watchFile(filePath, (curr, prev) => {
// 說明文件發生了變化,修改etagName/lastModified
etagName = etag(curr);
lastModified = new Date().toGMTString();
});
// 讀取文件,獲取初始化etagName, lastModified
stat(filePath, (err, stats) => {
if (!err) {
etagName = etag(stats);
lastModified = new Date().toGMTString();
}
});
// 協商緩存
app.get('/css/index.css', (req, res) => {
// 就要和etag對比
const ifNoneMatch = req.headers['if-none-match'];
// 就要和lastModified對比
const ifModifiedSince = req.headers['if-modified-since'];
if (ifNoneMatch === etagName && ifModifiedSince === lastModified) {
// 都相等,走緩存
res.status(304).end();
return
}
// 返回新資源
readFile(filePath, (err, data) => {
if (!err) {
// 設置協商緩存
// 接受瀏覽器發送過的etag和last-modified。 與當前的值對比。 如果一樣,走緩存,不一樣就返回最新的資源
res.set('etag', etagName);
res.set('last-modified', lastModified);
// 返回響應
res.end(data);
} else {
console.log(err);
}
});
});
app.listen(3000, (err) => {
if (!err) console.log('服務器啓動成功了~');
else console.log(err);
});
1.8舉個栗子看緩存