緩存技術一直一來在WEB
技術體系中扮演很是重要角色,是快速且有效地提高性能的手段。 css
以前在學習緩存的過程當中,一直沒有實踐過,有些概念常常會忘記。 今天主要經過Node實踐的方式學習瀏覽器緩存,順便分析一下Koa
處理緩存的源碼。前端
首先咱們看一下瀏覽器請求緩存過程。 java
發出請求後,會先在本地查找緩存。node
查到有緩存,要判斷緩存是否新鮮(是否過時)。瀏覽器
沒有過時,直接返回給客戶端。緩存
若是緩存過時了,就要再次去服務器請求最新的資源,返回給客戶端,並從新進行緩存。安全
可能不少同窗看其餘博客,提到都是「強緩存/協商緩存」等說法,這個我會放到後面講。 上圖中新鮮一詞比較少見,來自《HTTP權威指南》。服務器
由於HTTP
會將資源緩存一段時間,在這個時間內,這個緩存就是「新鮮的」。 因此檢查緩存是否過時就被稱爲,新鮮度檢測。框架
那麼接下來就經過Node
來實戰一下,看看:koa
上述提到緩存一段時間,那麼HTTP提供了通用首部字段(就是請求報文和響應報文都能用上的字段),來控制緩存時間。
Pragma 是HTTP/1.0標準中定義的一個header屬性,請求中包含Pragma的效果跟在頭信息中定義Cache-Control: no-cache相同,可是HTTP的響應頭沒有明肯定義這個屬性,因此它不能拿來徹底替代HTTP/1.1中定義的Cache-control頭。一般定義Pragma以向後兼容基於HTTP/1.0的客戶端。
Expires
會返回一個絕對時間,若是請求時間在Expires
指定的時間以前,就能命中緩存。可是由於客戶端能夠修改本地時間,會和和服務器時間不一致,容易出現差錯,不推薦使用。
Cache-Control
是如今常見的緩存方式,上述字段不少,初學者能夠只看max-age
,避免混亂,也是最有意義的屬性。
Cache-Control
描述的是一個相對時間,在進行緩存命中的時候,都是利用客戶端時間進行判斷,因此相比較
Expires
,
Cache-Control
的緩存管理更有效,安全一些。
經過Koa
框架,簡單搭建一個Node
服務。並經過koa-static
管理靜態資源。
代碼結構參考以下,maxage
緩存設置10
秒。
node index.js // server is starting at port 8001
複製代碼
在代碼截圖上,能夠看到給
koa-static
傳了
maxage: 10 * 1000
。
koa-static
源碼中引入了koa-send
庫。截取部分koa-send
源碼,只要傳入maxage
,就會設置Cache-Control
的max-age
。爲符合前端開發者習慣傳入爲毫秒,其實是用秒爲單位的。
NetWork
能夠觀察到已經成功設置
Cache-Control: max-age=10
訪問測試以下圖:
在10s內再次請求,能夠看到js/css均來自緩存memory cache
。
10s後緩存過時,不走緩存,便再次從服務器獲取。
通過上面的實驗能夠看出,在Js/Css
都走本地緩存的時候,HTML
是依舊從服務端獲取的。
Cache-Control: max-age=0
。
通過測試,發現若是單獨請求
Js
資源,也會出現此類現象。所以得出結論,這個是瀏覽器默認加的,應該是爲了
保證直接請求的資源最新。
針對request
請求,若是有Cache-Control
限制,那麼緩存系統就會先校驗Cache-Control
。不符合規則就直接請求服務端,具體規則以下:
Network
中的
disable-cache
也是如此,發出請求時,表示不須要走緩存,必定要服務端最新的。
上述不管是http1.0
仍是1.1
的方案,都是在本地緩存中存放一段時間。過時後就須要去服務端從新請求一遍。這個也被稱之爲強緩存。
可是,緩存中過時並不意味服務端資源改變。
所以請求發現本地緩存過時,能夠去服務端諮詢一下,這個資源還新鮮嗎?還能夠繼續使用嗎?常見的方法就是攜帶字段If-Modified-Since
和If-None-Match
。若是驗證資源是新鮮的,沒有改變。那隻須要返回一個標識,也就是咱們常說的304
,不須要返回數據,加速請求時間。
這個過程就是新鮮度檢測,那實現這個緩存的方式就是咱們常說的協商緩存。
下面看下Node實戰協商緩存。
攜帶If-Modified-Since
的前提是,緩存中存儲了Last-Modified
字段。 每一個請求返回時,response
中能夠攜帶字段Last-Modified
,是服務端資源修改的最後日期。
If-Modified-Since
就是緩存中的
Last-Modified
,和服務端資源最後修改時間進行比較,就知道資源是否新鮮了。
每一個請求返回時,response
中能夠攜帶字段Last-Modified
,是由於咱們使用的koa-static
會默認給咱們的返回頭加上Last-Modified
。
If-Modified-Since
。
可是驗證發現,10s後緩存過時,再次發出請求並無返回304,仍是200。
緣由是須要配置中間件
koa-conditional-get
。
koa-conditional-get
作了什麼,讓協商緩存生效。 能夠看出源碼很是簡單,判斷是否新鮮便可。
ctx.fresh
如何計算,會在後面講。但很明顯是校驗了If-Modified-Since
和Last-Modified
。
測試結果,10s內Js/Css
走強緩存。HTML
因爲請求默認加max-age
爲0,走協商緩存返回304,不須要返回數據,Size
由484B降至163B。
Js/Css
緩存到期,所有走協商緩存,因爲
Last-Modified
一直沒有改變,均返回304,不須要返回數據,
Size
降至163B。 返回304後,會重置
max-age
,10s內請求無需請求服務器,依然是強緩存。
Js
內容測試結果,
Css
沒有修改依舊返回304。
Js
修改致使
Last-Modified
大於請求中的
If-Modified-Since
,資源不夠新鮮,返回200並返回最新數據。
Last-Modified
工做流程以下:
通常來講,在沒有調整服務器時間和篡改客戶端緩存的狀況下,這兩個header
配合起來管理協商緩存是很是可靠的,可是有時候也會服務器上資源其實有變化,可是最後修改時間卻沒有變化的狀況,而這種問題又很不容易被定位出來,而當這種狀況出現的時候,就會影響協商緩存的可靠性。因此就有了另一對header
來管理協商緩存,這對header就是【ETag
、If-None-Match
】
這個header
是服務器根據當前請求的資源生成的一個惟一標識,這個惟一標識是一個字符串,只要資源有變化這個串就不一樣,因此能很好的補充Last-Modified
的問題。 避免干擾,能夠註釋Last-modified
邏輯。
ETag
的驗證也很是簡單,只須要再加入一箇中間件
koa-etag
,重啓服務測試。
發出請求,response
已有Etag
:
If-None-Match
爲緩存中的
Etag
值:
修改Js
資源測試,結果以下:
Js
資源測試後,致使
Etag
改變。服務端再驗證資源不新鮮,
Js
資源從新獲取,返回200。
Css
沒有修改,
Etag
沒變返回304。
Etag
總體流程和Last-Modified
保持一致。
koa
的etag
生成主要2個方法,具體的能夠直接去看源碼。
(1)根據文件的修改時間和文件大小生成
function stattag (stat) {
var mtime = stat.mtime.getTime().toString(16)
var size = stat.size.toString(16)
return '"' + size + '-' + mtime + '"'
}
複製代碼
(2)使用crypto
庫加密生成
function entitytag (entity) {
if (entity.length === 0) {
// fast-path empty
return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'
}
// compute hash of entity
var hash = crypto
.createHash('sha1')
.update(entity, 'utf8')
.digest('base64')
.substring(0, 27)
// compute length of entity
var len = typeof entity === 'string'
? Buffer.byteLength(entity, 'utf8')
: entity.length
return '"' + len.toString(16) + '-' + hash + '"'
}
複製代碼
在前面看到koa-conditional-get
可讓協商緩存生效,緣由是對資源新鮮度作了304返回的處理。
ctx.fresh
是如何處理的?
能夠看到Koa在request中的fresh
方法以下:
fresh
方法,判斷該請求的資源是否新鮮。
只保留核心代碼,能夠自行去看fresh
的源碼。
var CACHE_CONTROL_NO_CACHE_REGEXP = /(?:^|,)\s*?no-cache\s*?(?:,|$)/
function fresh (reqHeaders, resHeaders) {
// 1. 若是這2個字段,一個都沒有,不須要校驗
var modifiedSince = reqHeaders['if-modified-since']
var noneMatch = reqHeaders['if-none-match']
if (!modifiedSince && !noneMatch) {
console.log('not fresh')
return false
}
// 2. 給端對端測試用的,由於瀏覽器的Cache-Control: no-cache請求
// 是不會帶if條件的 不會走到這個邏輯
var cacheControl = reqHeaders['cache-control']
if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {
return false
}
// 3. 比較 etag和if-none-match
if (noneMatch && noneMatch !== '*') {
var etag = resHeaders['etag']
if (!etag) {
return false
}
// 部分代碼
if (match === etag) {
return true;
}
}
// 4. 比較if-modified-since和last-modified
if (modifiedSince) {
var lastModified = resHeaders['last-modified']
var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))
if (modifiedStale) {
return false
}
}
return true
}
複製代碼
fresh
的代碼判斷邏輯總結以下,知足3種條件之一,fresh
爲true
。
瀏覽器緩存總體流程以下:
max-age
等)。from cache
)。etag/last-modified
),去服務端再驗證該資源是否更新,本地緩存是否能夠繼續使用。max-age
等更新緩存時間。不須要返回數據,加速請求時間。咱們常說的強緩存,其實就是直接在本地緩存獲取,也就是Cache-Control: max-age
等配置,不須要和服務端溝通。
而協商緩存是在強緩存的基礎上,配置etag或last-modified
等參數。本地緩存失效後,去服務端進行新鮮度檢測。能夠避免每次本地緩存過時後都返回最新的數據,形成請求緩慢。
本文的源碼分析圍繞koa
,不表明其餘服務框架。對這塊知識不瞭解建議實踐一下。寫錯的地方,接受批評指正~