HTTP緩存相信都不陌生,由於它是在前端性能優化中必不可少的一個環節。在首次進入或者請求數據正常傳輸數據,而當再次進入或者請求數據時,能夠走本地或者服務器上的緩存,來節省流量、優化性能、提升用戶體驗、下降網絡負荷等等。javascript
web緩存主要用來緩存html文件、js文件、css文件、數據,基本上都是提高客戶端/瀏覽器請求到服務器之間的速度,固然也能夠結合數據壓縮如gzip、7z等等加快響應數據傳輸。css
在整個應用中能夠錯多層緩存結構這裏很少作介紹,由於前面已經大體介紹過了,這裏主要介紹和前端比較相關的HTTP緩存。html
大體分爲下面幾步來加深對HTTP中的緩存理解和應用場景。前端
大體把協議分爲強緩存(過時策略)和協商緩存(協商策略)兩類緩存,可能不太準確只是本身的如今的看法,瀏覽器/客戶端經過這兩種策略決定使用緩存中的副本仍是從服務器中獲取最新的資源。java
key | 描述 | 緩存策略 | 首部類型 |
---|---|---|---|
Pragma | 指定緩存機制(http 1.0 字段) | 強緩存(過時策略) | 響應首部字段 |
Cache-COntrol | Cache-Control 通用消息頭字段,被用於在http請求和響應中,經過指定指令來實現緩存機制。 |
強緩存(過時策略) | 響應/請求首部字段 |
Expires | Expires 響應頭包含日期/時間, 即在此時候以後,響應過時。 |
強緩存(過時策略) | 響應首部字段 |
Last-Modified | Last-Modified 是一個響應首部,其中包含源頭服務器認定的資源作出修改的日期及時間。 |
協商緩存(協商策略) | 響應首部字段 |
If-Modified-Since | If-Modified-Since 是一個條件式請求首部,服務器只在所請求的資源在給定的日期時間以後對內容進行過修改的狀況下才會將資源返回,狀態碼爲 200 。 |
協商緩存(協商策略) | 請求首部字段 |
ETag | ETag HTTP響應頭是資源的特定版本的標識符。 |
協商緩存(協商策略) | 響應首部字段 |
If-None-Match | If-None-Match 是一個條件式請求首部。對於 GET 和 HEAD 請求方法來講,當且僅當服務器上沒有任何資源的 ETag 屬性值與這個首部中列出的相匹配的時候,服務器端會才返回所請求的資源,響應碼爲 200 。 |
協商緩存(協商策略) | 請求首部字段 |
If-Match(輔助) | If-Match 的使用表示這是一個條件請求。在請求方法爲 GET 和 HEAD 的狀況下,服務器僅在請求的資源知足此首部列出的 ETag 值時纔會返回資源。 |
協商緩存(協商策略) | 請求首部字段 |
If-Unmodified-Since(輔助) | If-Unmodified-Since 只有當資源在指定的時間以後沒有進行過修改的狀況下,服務器纔會返回請求的資源,或是接受 POST 或其餘 non-safe 方法的請求。 |
協商緩存(協商策略) | 請求首部字段 |
Vary(輔助) | Vary 是一個HTTP 響應頭部信息,它決定了對於將來的一個請求頭,應該用一個緩存的回覆(response)仍是向源服務器請求一個新的回覆。 |
協商緩存(協商策略) | 響應首部字段 |
緩存又分爲強緩存和協商緩存。其中強緩存包括Expires
和Cache-Control
,主要是在過時策略生效時應用的緩存。弱緩存包括Last-Modified
和ETag
,是在協商策略後應用的緩存。強弱緩存之間的主要區別在於獲取資源時是否會發送請求。node
強緩存和協商緩存git
200 OK
304 Not Modified
屬於**強緩存(過時策略)**的有以下:web
Cache-Control
用於指定資源的緩存機制,能夠同時在請求和響應頭中設定。可是Cache-Control
中的屬性也分爲請求和響應緩存指令,大體分爲以下:算法
緩存請求指令 客戶端能夠在HTTP請求中使用的標準 Cache-Control
指令。chrome
Cache-Control: max-age= / max-stale[=] / min-fresh= / no-cache / no-store / no-transform / only-if-cached
緩存響應指令 服務器能夠在響應中使用的標準 Cache-Control
指令。
Cache-control: must-revalidate / no-cache / no-store / no-transform / public / private / proxy-revalidate / max-age= / s-maxage=
Cache-Control
: cache-directive[,cache-directive]
。cache-directive
爲緩存指令,大小寫不敏感,共有12個與HTTP緩存標準相關,以下所示。其中請求指令7種,響應指令9種。Cache-Control
能夠設置多個緩存指令,以逗號,
分隔。
no-cache
以後並不表明瀏覽器不緩存,而是在獲取緩存前要向服務器確認資源是否被更改。至關於max-age: 0, must-revalidate
max-age
或者Expires
頭,可是僅適用於共享緩存(好比各個代理),私有緩存會忽略它。must-revalidate
做用相同,但它僅適用於共享緩存(例如代理),並被私有緩存忽略。Content-Encoding
、Content-Range
、Content-Type
等字段的修改,所以代理服務器的gzip
壓縮將不被容許。還有一點須要注意的是,no-cache
並非指不緩存文件,no-store
纔是指不緩存文件。no-cache
僅僅是代表跳過強緩存,強制進入協商策略。
禁止緩存 Cache-Control: no-cache, no-store, must-revalidate
緩存靜態資源 Cache-Control:public, max-age=86400
Expires
指定緩存的過時時間,爲絕對時間,即某一時刻。
注意:參考本地時間進行比對,在指定時刻後過時。RFC 2616建議最大值不要超過1年。
Cache-Control
中的max-age
指令用於指定緩存過時的相對時間。資源達到指定時間後過時。該功能與Expires
相似。但其優先級高於Expires,若是同時設置max-age
和Expires
,max-age
生效,忽略Expires
。
Cache-Control > Expires
強緩的設置流程圖大體以下:
在沒有強緩存時,就會走協商緩存,協商緩存大體流程:
屬於**協商緩存(協商策略)**的有以下:
Last-Modified/If-Modified-Since
大體流程以下:
Last-Modified
,返回給客戶端If-Modified-Since
字段If-Modified-Since
中的時間對比,若是沒有變化返回304
狀態碼(瀏覽器得知304狀態碼,資源從緩存中獲取),若是改變返回200
而且更新資源、更新Last-Modified
上面的流程是在設置不使用強緩存時的場景,這個只是如今的理解可能有不少的不太完善的地方。
Last-Modified
用於標記請求資源的最後一次修改時間。
語法
Last-Modified: <day-name>, <day> <month> <year> <hour>:<minute>:<second> GMT
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
複製代碼
注意
Last-Modified
只能精確到秒,所以不適合在一秒內屢次改變的資源。If-Modified-Since
是一個條件式請求首部,與Last-Modified
何用。有兩種結果以下:
If-Modified-Since/Last-Modified
相同返回304
狀態碼,客戶端使用緩存If-Modified-Since/Last-Modified
不相同返回200
狀態碼,返回新的資源
If-Modified-Since
只能夠用在GET
或HEAD
請求中。
If-Unmodified-Since
表示資源未修改則正常執行更新,不然返回412(Precondition Failed)
狀態碼的響應。主要有以下兩種場景。
If-Range
字段同時使用時,能夠用來保證新的片斷請求來自一個未修改的文檔。根據實體內容生成一段惟一hash字符串,標識資源的狀態,由服務端產生。瀏覽器會將這串字符串傳回服務器,驗證資源是否已經修改,若是沒有修改,過程以下:
ETag
HTTP響應頭是資源的特定版本的標識符。 語法
ETag: W/"<etag_value>"
ETag: "<etag_value>"
複製代碼
W/ 可選 'W/'(大小寫敏感) 表示使用弱驗證器。 弱驗證器很容易生成,但不利於比較。 強驗證器是比較的理想選擇,但很難有效地生成。
"<etag_value>" 實體標籤惟一地表示所請求的資源。 它們是位於雙引號之間的ASCII字符串(如「675af34563dc-tr34」)。
注意:ETag和If-None-Match的值均爲雙引號包裹的。
ETag
的優先級高於Last-Modified
。當ETag
和Last-Modified
,ETag
優先級更高,但不會忽略Last-Modified
,須要服務端實現。
ETag
和 If-None-Match
常被用來處理協商緩存。而 ETag
和 If-Match
能夠 避免「空中碰撞」。
ETag
HTTP 響應頭是資源的特定版本的標識符。這可讓緩存更高效,並節省帶寬,由於若是內容沒有改變,Web 服務器不須要發送完整的響應。而若是內容發生了變化,使用 ETag
有助於防止資源的同時更新相互覆蓋(「空中碰撞」)。
當編輯MDN時,當前的WIki內容被散列,並在相應中放入Etag
:
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
複製代碼
將更改保存到WIKI頁面(發佈數據)時,POST請求將包含有ETag
值的If-Match
頭來檢車是否爲最新版本。
If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
複製代碼
若是哈希值不匹配,則意味着文檔已經被編輯,拋出 412 ( Precondition Failed)
前提條件失敗錯誤。
If-None-Match
是客戶端發送給服務器時的請求頭,其值是服務器返回給客戶端的 ETag
,當 If-None-Match
和服務器資源最新的 Etag
不一樣時,返回最新的資源及其 Etag
。
緩存策略分爲強緩存和協商緩存,首先通過強緩存的過時策略,纔會走後面的協商緩存的協商策略,大體把緩存分爲三個階段本地緩存階段(強緩存)、協商緩存階段(本地+服務器)、緩存失敗階段。
大體在每一個階段中作的什麼判斷:
http
請求到服務器。(主要應用是強緩存、serverWorker);304
,讓瀏覽器使用本地找到的那個資源;200
, 固然這個是指找到資源的狀況下,若是服務器上沒有這個資源,則返回404
。大體流程以下圖所示:
這張圖中沒有包含serverWorker
的緩存判斷流程b,可是在後面會有一篇文章專門介紹serverWorker,由於他是屬於PWA中的內容。
存儲策略發生在收到請求響應後,用於決定是否緩存相應資源;過時策略發生在請求前,用於判斷緩存是否過時;協商策略發生在請求中,用於判斷緩存資源是否更新。
在用戶刷新頁面(F5)時,會對緩存產生影響,這裏就會記錄用戶操做對緩存產生的影響。用戶操做事項以下表所示:
用戶操做 | 強緩存 | 協商緩存 |
---|---|---|
(新標籤)地址欄回車 | 有效 | 有效 |
(地址不變)地址欄回車 | 兼容性問題Chrome(失效)/Firefox(有效) | 有效 |
連接跳轉 | 有效 | 有效 |
前進/後退 | 有效 | 有效 |
從收藏欄打開連接 | 有效 | 有效 |
(window.open)新開窗口 | 有效 | 有效 |
刷新(Command/Ctrl + R / F5) | 失效 | 有效 |
強制刷新(Command + Shift + R / Ctrl + F5) | 失效 | 失效 |
基本上包含了一些常見的用戶操做對強緩存和協商緩存的影響,大體的判斷流程以下:
注意
- (地址不變)地址欄回車:它比較特殊,爲何它在Chrome是失效,在Firefox中是有效。由於Chrome把地址不變回車等同於刷新當前頁面,而在Firefox都是做爲新地址回車處理的。
webkit(Chrome內核)
資源分爲主資源和派生資源。主資源是地址欄輸入的URL請求返回的資源,派生資源是主資源中所引用的JS、CSS、圖片等資源。- 在
Chrome
下刷新時,只有主資源的緩存應用方式如上圖所示,派生資源的緩存應用方式與新標籤打開相似,會判斷緩存是否過時。強緩存生效時的區別在於新標籤打開爲from disk cache
,而當前頁刷新派生資源是from memory cache
。- 而在
Firefox
下,當前頁面刷新,全部資源都會如上圖所示。
從緩存的位置上來講分爲四種,而且各自有優先級,當依次由上到下查找緩存且都沒有命中的時候,纔會去請求網絡,大體以下:
Service Worker 是一種獨立於主線程以外的 Javascript 線程。它能夠幫咱們實現離線緩存、消息推送和網絡代理等功能。
使用
Service Worker
的話,傳輸協議必須爲HTTPS
。由於Service Worker
中涉及到請求攔截,因此必須使用HTTPS
協議來保障安全。
Service Worker 實現緩存大體分爲如下幾個步驟:
在這裏就不細說了,後面有一個單獨的章節來說述Service Worker, Service Worker 的緩存與瀏覽器其餘內建的緩存機制不一樣,它可讓咱們自由控制緩存哪些文件、如何匹配緩存、如何讀取緩存,而且緩存是持續性的。
Memory Cache 是內存中的緩存。主要包含的是當前頁面中請求到的數據如圖片(base64)、腳本(JavaScript)、樣式(css)等靜態數據。讀取內存中的數據確定比磁盤中的快,可是內存中的緩存持續性很短,它會隨着當前Tab頁面關閉,內存中的緩存也就被釋放。
好比在百度首頁刷新頁面,效果以下圖所示:
preload<link>
元素的
rel
屬性的屬性值
preload
,
<link rel="preload">
來顯示的指定的預加載資源,也會被放入
memory cache
中。
prefetch <link rel="prefetch">
已經被許多瀏覽器支持了至關長的時間,但它是意圖預獲取一些資源,以備下一個導航/頁面使用(好比,當你去到下一個頁面時)。 瀏覽器會給使用prefetch
的資源一個相對較低的優先級與使用preload
的資源相比。
subresource <link rel="subresource">
被Chrome支持了有一段時間,而且已經有些搔到預加載當前導航/頁面(所含有的資源)的癢處了。這些資源會以一個至關低的優先級被加載。 Memory Cache
不會輕易的命中一個請求,除了要有匹配的URL,還要有相同的資源類型、CORS模式以及一些其餘特性。 Memory Cache
是不關心HTTP語義
的,好比Cache-Control: max-age=0
的資源,仍然能夠在同一個導航中被重用。可是在特定的狀況下,Memory Cache
會遵照Cache-Control: no-store
指令,不緩存相應的資源。
Memory Cache
匹配規則在標準中沒有詳盡的描述,因此不一樣的瀏覽器內核在實現上會有所不一樣。
HTTP Cache
也被叫作Disk Cache
。從字面的意思上理解Disk Cache
就是儲存在硬盤上的緩存,所以它是持久存儲的,是實際存在於文件系統中的。 並且它容許相同的資源在跨會話,甚至跨站點的狀況下使用,例如兩個站點都使用了同一張圖片。
HTTP Cache
會根據HTTP Herder
中的字段判斷哪些資源須要緩存,哪些資源能夠不請求直接使用,哪些資源已通過期須要從新請求。 當命中緩存以後,瀏覽器會從硬盤中讀取資源,雖然比起從內存中讀取慢了一些,但比起網絡請求仍是快了很多的。絕大部分的緩存都來自 disk cache
。
凡是持久性存儲都會面臨容量增加的問題,
disk cache
也不例外。在瀏覽器自動清理時,會有神祕的算法去把「最老的」或者「最可能過期的」資源刪除,所以是一個一個刪除的。不過每一個瀏覽器識別「最老的」和「最可能過期的」資源的算法不盡相同,可能也是它們差別性的體現。
Push Cache
(推送緩存)是 HTTP/2
中的內容,當以上三種緩存都沒有命中時,它纔會被使用。 它只在會話(Session)中存在,一旦會話結束就被釋放,而且緩存時間也很短暫,在Chrome瀏覽器中只有5分鐘左右,同時它也並不是嚴格執行HTTP頭中的緩存指令。
Push Cache
在國內可以查到的資料不多,也是由於 HTTP/2
在國內不夠普及。這裏推薦閱讀Jake Archibald
的 HTTP/2 push is tougher than I thought 這篇文章,文章中的幾個結論:
若是以上四種緩存都沒有命中的話,那麼只能發起請求來獲取資源了。
那麼爲了性能上的考慮,大部分的接口都應該選擇好緩存策略,一般瀏覽器緩存策略分爲兩種:強緩存和協商緩存,而且緩存策略都是經過設置 HTTP Header
來實現的。
這兩種緩存類型存在於 Chrome 中。 disk cache
存在硬盤,能夠存不少,容量上限比內容緩存高不少,而 memory cache
從內存直接讀取,速度上佔優點,這兩個各有各的好處!
由於關於在何時用到什麼緩存的文檔至關的少因此真的很差判斷,是當前使用的是哪一個緩存,好比下面這個例子:
其實緩存之間也沒有太好的對比性,大體能夠從緩存策略和緩存位置兩個角度對比緩存的優缺點。
Service Worker
相對於Disk Cache/Memory Cache
配置會麻煩一點,可是Service Worker
應用場景更廣,性能也會好一點。Service Worker
必需要在Https
協議中才會生效。它們的值優缺點如上所示,如在chrome、firefox、ie中Memory cache和Disk cache也是不太相同的。
Chrome瀏覽器的速度比其餘兩個瀏覽器的速度更快一點,主要是由於V8引擎的執行速度更快,另外一方面應該就是它的緩存策略的使用。 從這四個方面強緩存、協商緩存、Disk Cache、Memory Cache來對比,爲何說Chrome執行效果比其它的兩個瀏覽器的執行速度和加載速度更快。
就以百度首頁爲例看一下Chrome和Firefox的差異。
在Chrome
和Firefox
中打開https://www.baidu.com/
首頁,結果以下圖所示 Firefox效果以下:
咱們以百度的bd_logo1.png
的請求爲例,logo
的請求是一個Get
請求,同時它被設置了四個緩存配置,可是它在兩個瀏覽器中表現並不相同,以下圖所示
Firefox效果以下:
Chrome效果以下:
首先在再次請求時瀏覽器端都沒有攜帶協商緩存須要的頭部字段,因此它們確定走的是強緩存,在強緩存中Cache-Control
的優先級是最高的,因此都是走的Cache-Control的策略。
能夠看到它們的區別以下幾點:
200
,Firefox返回的狀態碼是304
。0(耗時 0ms,也就是 1ms 之內)
,那麼它使用的本地的資源。而Firefox中它是從服務器獲取的資源。在這裏咱們來一個一個測試expires/cache-control/etag/last-modified/pragma
它們是否和咱們上面所總結的一致。
測試環境chrome 78.0.3904.70
、node 12.9.1
、koa 2.x
.
總體的目錄結構以下圖所示:
代碼可能寫的比較粗糙,可是後面會優化一下,公共代碼以下:
index.html代碼以下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/index/index.css">
<script src="/index/index.js"></script>
</head>
<body>
測試cache
<img src="/index/rotateX.png" alt="">
</body>
</html>
複製代碼
app.js代碼以下
const Koa = require('koa');
const Router = require('koa-router');
const Static = require('koa-static');
const fs = require('fs-extra');
const Path = require('path');
const mime = require('mime');
const app = new Koa();
const router = new Router();
router.get('/', async (ctx, next) => {
ctx.type = mime.getType('.html');
// console.log(__dirname)
const content = await fs.readFile(Path.resolve(__dirname + '/index/index.html'), 'UTF-8');
// console.log(content);
ctx.body = content;
await next();
})
// 待優化
router.get('/index/rotateX.png', async (ctx, next) => {
const { response, path } = ctx;
ctx.type = mime.getType(path);
const imageBuffer = await fs.readFile(Path.resolve(__dirname, `.${path}`));
ctx.body = imageBuffer;
await next();
})
// 待優化
router.get('/index/index.css', async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path);
const content = await fs.readFile(Path.resolve(__dirname, `.${path}`), 'UTF-8');
ctx.body = content;
await next();
});
// 待優化
router.get('/index/index.js', async (ctx, next) => {
const { path } = ctx;
ctx.type = mime.getType(path);
const content = await fs.readFile(Path.resolve(__dirname, `.${path}`), 'UTF-8');
ctx.body = content;
await next();
});
// app.use(Static('./index'))
app.use(router.routes()).use(router.allowedMethods());
app.listen(3000, function (err) {
// console.log()
if (err) {
console.log(err)
} else {
console.log('啓動成功')
}
})
複製代碼
下面的代碼都是在這個代碼上修改,index.js
、index.css
、rotateX.png
本身寫就能夠,或者去網上下載一個稍微超過2kb
大小的文件。
使用Cache-Control
緩存測試效果,修改代碼以下:
修改app.js
// ...省略代碼
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代碼
// 添加代碼
ctx.set('Cache-Control', 'max-age=' + 10);
})
// ...省略代碼
router.get('/index/index.css', async (ctx, next) => {
// ...省略代碼
// 添加代碼
ctx.set('Cache-Control', 'max-age=' + 10);
})
// ...省略代碼
router.get('/index/index.js', async (ctx, next) => {
// ...省略代碼
// 添加代碼
ctx.set('Cache-Control', 'max-age=' + 10);
})
複製代碼
咱們在經過nodemon app.js
運行代碼,運行效果大體以下:
localhost:3000
時,由於沒有任何緩存因此資源是從服務器中請求
來的,以下圖所示
Cache-Control: max-age=10
,因此會走本地緩存
,以下圖所示
第二次請求,三個請求都來自 memory cache
。由於咱們沒有關閉 TAB,因此瀏覽器把緩存的應用加到了memory cache
。(耗時 0ms,也就是 1ms 之內)https://www.baidu.com
,再返回頁面時,它也會走本地緩存
,以下圖所示
由於跳轉頁面等因而關閉了 TAB,memory cache
也隨之清空。可是 disk cache
是持久的,因而全部資源來自 disk cache
。(大約耗時 3ms,由於文件有點小)並且對比 2 和 3,很明顯看到 memory cache
仍是比 disk cache
快得多的。
咱們來對比一下no-cache
和no-store
的區別,修改代碼以下:
修改index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/index/index.css">
<link rel="stylesheet" href="/index/index.css">
<script src="/index/index.js"></script>
<script src="/index/index.js"></script>
</head>
<body>
測試cache
<img src="/index/rotateX.png" alt="">
<img src="/index/rotateX.png" alt="">
<!-- 異步請求圖片 -->
<script> setTimeout(function () { let img = document.createElement('img') img.src = '/index/rotateX.png' document.body.appendChild(img) }, 1000) </script>
</body>
</html>
複製代碼
咱們暫時不修改緩存的配置,經過nodemon app.js
運行代碼,運行效果大體以下:
HTML
中的資源存入到緩存 (memory cache
),這樣碰到相同 src
的圖片就會自動讀取緩存(但不會在 Network
中顯示出來)Network
中顯示。下面咱們修改app.js中的代碼以下:
// ...省略代碼
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-cache');
})
// ...省略代碼
router.get('/index/index.css', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-cache');
})
// ...省略代碼
router.get('/index/index.js', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-cache');
})
複製代碼
咱們運行代碼看的效果以下圖所示:
HTML
中的資源存入到緩存 (memory cache
),這樣碰到相同 src
的圖片就會自動讀取緩存(但不會在 Network
中顯示出來)若是把no-cache
修改成no-store
修改app.js
// ...省略代碼
router.get('/index/rotateX.png', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-store');
})
// ...省略代碼
router.get('/index/index.css', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-store');
})
// ...省略代碼
router.get('/index/index.js', async (ctx, next) => {
// ...省略代碼
// 替換原來代碼
ctx.set('Cache-Control', 'no-store');
})
複製代碼
咱們運行代碼看的效果以下圖所示:
當咱們設置了Cache-Control: no-store
時,能夠看到css
、js
文件都被請求了兩次,png
請求了三次。
memory cache
是無視 HTTP
頭信息的,可是 no-store
是特別的。在這個設置下,memory cache
也不得不每次都請求資源。no-store
狀況下,依然是每次都發送請求,不進行任何緩存。這裏來設置協商緩存Last-Modified/If-Modified-Since
,代碼修改以下:
修改app.js
const responseFile = async (path, context, encoding) => {
const fileContent = await fs.readFile(path, encoding);
context.type = mime.getType(path);
context.body = fileContent;
};
router.get('/index/rotateX.png', async (ctx, next) => {
ctx.set('Pragma', 'no-cache');
const { response, request, path } = ctx;
const imagePath = Path.resolve(__dirname, `.${path}`);
const ifModifiedSince = request.headers['if-modified-since'];
console.log(ifModifiedSince)
const imageStatus = await fs.stat(imagePath);
const lastModified = imageStatus.mtime.toGMTString();
if (ifModifiedSince === lastModified) {
response.status = 304;
} else {
response.lastModified = lastModified;
await responseFile(imagePath, ctx);
}
await next();
})
複製代碼
大體流程以下:
在Chrome
中選中Disable Cache
禁用緩存,能夠經過下面圖片看到服務器端發送給客戶端Last-Modified: Thu, 24 Oct 2019 05:12:37 GMT
。
關閉 disable cache
後再次訪問圖片時,發現帶上了 if-modified-since
請求頭,值就是上次請求響應的 last-modified
值,由於圖片最後修改時間不變,因此 304 Not Modified
。效果以下圖所示
啓用
Disable Cache
時,咱們能夠看到客戶端/瀏覽器端自動帶上了Pragma':'no-cache'
、'Cache-Control': 'no-cache'
這兩個字段,不適用緩存。
修改app.js,經過npm i crypto -D
安裝crypto
,用於生成md5
。
// 處理 css 文件
router.get('/index/index.css', async (ctx, next) => {
const { request, response, path } = ctx;
ctx.type = mime.getType(path);
response.set('pragma', 'no-cache');
const ifNoneMatch = request.headers['if-none-match'];
const imagePath = Path.resolve(__dirname, `.${path}`);
const hash = crypto.createHash('md5');
const imageBuffer = await fs.readFile(imagePath);
hash.update(imageBuffer);
const etag = `"${hash.digest('hex')}"`;
if (ifNoneMatch === etag) {
response.status = 304;
} else {
response.set('etag', etag);
ctx.body = imageBuffer;
}
await next();
});
複製代碼
運行效果以下圖所示:
他的過程和Last-Modified/If-Modified-Since
,可是由於Last-Modified/If-Modified-Since
它不能監聽1s
之內的資源變化,因此通常用他來作Etag/If-None-Match
的補充方案。
緩存大體分爲:強緩存
、協商緩存
。
強緩存
: pragma
、cache-control
、expires
協商緩存
: last-modified/If-modified-since
、etag/if-none-match
強緩存優先級
: cache-control > pragma > expires
協商緩存優先級
: etag/if-none-match > last-modified/If-modified-since
緩存位置分爲: Service Worker
、Memory Cache
、Disk Cache
、Push Cache
,也是從左到右若是命中就使用。
上面的實例只是比較簡單的應用,其實還有不少有意思的實例能加深對緩存的理解,以下:
pragma
、cache-control
、expires
優先級last-modified/If-modified-since
、etag/if-none-match
優先級cache-control: no-cache
與cache-control: max-age=0, must-revalidate
效果是否相同chrome
、firefox
、ie
之間的緩存差異本篇文章有意避開Service Worker
的詳細介紹,由於會有單獨的一篇文章來介紹Service Worker
在真實應用的使用。
在線代碼,能夠刷新頁面(刷新內部頁面)在控制檯中查看當前效果。
感受寫的不錯請點一下贊,據說長的帥的人都會點贊,而且點贊後走上人生巔峯。