面對瀏覽器,我們經常需要考慮一些效能的問題,除了讓使用者提高頻寬,我們還需要通過快取一些檔案來提高使用者體驗。瀏覽器的快取即是HTTP快取,我們開啟瀏覽器時總需要與遠端伺服器做互動,如果伺服器允許的話,瀏覽器首次訪問網站時會把成功的請求結果攔截下來並儲存在記憶體或硬碟上,以便下次訪問時快速獲取。零零散散看過許多HTTP快取的文章,所以這次想梳理一下脈絡和自己動手模擬一下伺服器看看我們可以如何指控瀏覽器快取我們的檔案。
DEMO
學習過程中做了一個小demo模擬快取過程,可以在github.com/abigaleypc/…中檢視原始碼。
強快取 與 協商快取
HTTP快取可以分為強快取 與 協商快取兩大類。強快取是當我們發起一個HTTP請求時,前端不會與伺服器做互動,而是根據上一次設定的快取期限,未過期時則不再向後端做請求,即使檔案已經被更新了我們也無法獲取。而協商快取則需要與伺服器做一個互動,當伺服器告訴我們上次的快取並未做修改時,後端則不返回我們需要的檔案,否則返回最新檔案。
強快取
強快取包含Expires 與 cache-control
Expires
表示存在時間,允許客戶端在這個時間之前不去檢查(發請求),例如:Expires: Wed, 21 Oct 2015 07:28:00 GMT
可採用兩種方法對Expires進行設定:
頁面標籤中設定
html < META HTTP-EQUIV="Expires" VALUE="May 31,2001 13:30:15" >
後端設定
Response.Expires=時間(單位:分)來啟用快取。 // 另一種方式 Response.AddHeader("expires","utc時刻")
以上時間表示訊息傳送的時間,時間的描述格式由rfc822定義。例如,Web伺服器告訴瀏覽器在2018-04-28 03:30:01這個時間點之前,可以使用快取檔案。傳送請求的時間是2018-04-28 03:25:01,即快取5分鐘。
理解cache-control
強快取 - 體驗max-age
“max-age”指令指定從請求的時間開始,允許獲取的響應被重用的最長時間(單位:秒)。如果還有一個 設定了 "max-age" 或者 "s-max-age" 指令的Cache-Control響應頭,那麼 Expires 頭就會被忽略。
下面做一個測試max-age
的實驗,開啟GitHub的專案並npm start
,測試之前請開啟控制檯,將瀏覽器控制檯中的Disable cache
關閉。如下圖
- Step1: 在入口檔案
app.js
中設定最大快取時間,如下:
app.use(
"/public",
express.static("public", {
maxAge: "1d"
}));
複製程式碼
- Step2: 將
/public
下的靜態檔案最大快取時間設為一天,現在在/public
下的main.js
寫了一段更新頁面文字的程式碼
window.onload = function() {
let name = document.getElementById("name");
name.innerText = "abigale";
};
複製程式碼
- Step3: 第一次開啟瀏覽器並開啟控制檯,點選
max-age
選單,採用max-age
做快取:瀏覽器查詢快取檔案的順序為 memory - disk - 網路
- Step4: 重新整理頁面:快取優先從memory查詢,如查詢得到則採用memory中檔案
- Step5: 重新開啟頁面:memory若無快取,則從disk查詢
- Step6: 修改
/public/main.js
window.onload = function() {
let name = document.getElementById("name");
name.innerText = "AbigaleYu";
};
複製程式碼
無論是重新整理頁面訪問還是重新開啟頁面訪問,結果與Step4
Step5
一樣,name並沒有更新為AbigaleYu。這是因為強快取在快取時間內並不會去獲取新檔案,而是採用快取檔案
該快取策略弊端: 當設定時間內更新檔案了,瀏覽器並不知道。
其他value
cache-control 下還有其他幾個常用的value
- no-cache: 表示每次請求都需要與伺服器確認一次,這種情況一般帶有ETag,當伺服器端驗證無修改時,則從快取中取檔案,伺服器無需返回檔案。
- no-store: 表示無論如何都不允許客戶端做快取,每次湊需要做一次完整的請求和完整的響應。
- public:如果響應被標記為“public”,則即使它有關聯的 HTTP 身份驗證,甚至響應狀態程式碼通常無法快取,也可以快取響應。大多數情況下,“public”不是必需的,因為明確的快取資訊(例如“max-age”)已表示響應是可以快取的。我們有時可以在memory,disk,路由等找到快取就是因為這是public的設定
- private:不允許任何中間快取對其進行快取,只能終端使用者做快取
舉一些例子
快取設定表現max-age=86400瀏覽器以及任何中間快取均可將響應(如果是“public”響應)快取長達 1 天(60 秒 x 60 分鐘 x 24 小時)。max-age=86400瀏覽器以及任何中間快取均可將響應(如果是“public”響應)快取長達 1 天(60 秒 x 60 分鐘 x 24 小時)。private, max-age=600客戶端的瀏覽器只能將響應快取最長 10 分鐘(60 秒 x 10 分鐘)。no-store不允許快取響應,每次請求都必須完整獲取。
引申問題:from memory cache
與 from disk cache
的區別
瀏覽器訪問頁面時,查詢靜態檔案首先會從快取中讀取,快取分為兩種,記憶體快取與硬碟快取。查詢檔案的順序為:memory -> disk -> 伺服器。記憶體快取是在kill程式時刪除,即關閉瀏覽器時記憶體快取消失,而硬碟快取則是即使關閉瀏覽器也仍然存在。當我們首次訪問頁面,需要從伺服器獲取資源,將可快取的檔案快取在記憶體與硬碟,當重新整理頁面時(這種情況沒有關閉瀏覽器)則從記憶體快取中讀取,我們可以在上面的截圖看到from memory cache的所需要的時間為0,這是最快的讀取檔案方式,當我們重新開一個頁面時,也就是已經kill這個程式,記憶體快取已經消失了,這時候就從硬體快取獲取,而當我們手動在瀏覽器清除快取時,下次訪問就只能再去伺服器拉取檔案了。但有一點可以從上面圖中看到,並不是從硬碟獲取快取的時間一定比從網路獲取的時間短,示例中的時間是更長的,這取決於網路狀態和檔案大小等因素,從快取獲取有利有弊,當網路較差或者檔案較大時,從硬碟快取獲取可以給使用者較好的體驗。
協商快取
Last-Modified 與 If-Modified-since
- Last-Modified 標示這個響應資源的最後修改時間。web 伺服器在響應請求時,告訴瀏覽器資源的最後修改時間
- If-Modify-since 再次向伺服器請求時帶上,如果資源已修改,返回 HTTP 200,未被修改,返回 HTTP 304
DEMO : 依然是GitHub的原始碼。可切換到選單 Last Modified / If-Modified-Since 檢視
- Step1: 在
app.js
入口檔案中,我們通過將 Last-Modified 的頭設定為作業系統上該檔案的上次修改日期來控制快取js
app.use( "/lastModified",
express.static("public/lastModified", {
lastModified: true,
setHeaders: setCustomCacheControl
})
);
複製程式碼
- Step2: 在路徑為
./public/lastModified/main.js
的檔案中,讀取檔案response header中的 Last-modified 展示出來
window.onload = function() {
let time = document.getElementById("time");
time.innerText = document.lastModified;
};
複製程式碼
- Step3: 點選選單
Last Modified / If-Modified-Since
,第一次請求狀態碼是意料中的200,但可以看到響應頭response header多了Last-modified 記錄我們最後修改檔案的時間
- Step4: 再次重新整理頁面時,狀態碼更新為304(Not Modified),但不同與max-age,此處的304雖然是檔案並未被修改,但依然需要相似的請求時間,這是因為協商快取需要向伺服器諮詢檔案是否被更新,而且可以看到該請求中request header 多了 If-Modified-Since 欄位,這就是告訴瀏覽器上次檔案的修改時間。可以看到上次修改時間 If-Modified-Since 與 響應頭 response header 中 Last-modified 是一致的,因此返回檔案未被修改。
- Step5: 修改main.js中的內容,並重新整理頁面,這次可以看到main.js的請求不是返回304,而是200,展開main.js請求,也可以看到request header 中上次修改時間 If-Modified-Since 與 響應頭 response header 中 Last-modified 是不一致的,因為我們需要獲取到最新檔案而不是使用快取檔案。
ETag 和 If-None-Match
- ETag 告訴瀏覽器當前資源在伺服器的唯一標識
- If-None-Match 再次向伺服器請求時帶上,如果資源已修改,返回 HTTP 200,未被修改,返回 HTTP 304
總結
瀏覽器獲取檔案的過程如下:
Abigale's blog : Abigale's Blog