關於瀏覽器緩存
瀏覽器的資源請求,如果使用了緩存基本上是兩種情況
- status code: 200 ok ( from cache )
- status code: 304 Not Modified
上面兩種方式有什么區別呢?簡單地說,第一種方式是不向瀏覽器發送請求,直接使用本地緩存文件。第二種方式,瀏覽器雖然發現了本地有該資源的緩存,但是不確定是否是最新的,於是想服務器詢問,若服務器認為瀏覽器的緩存版本還可用,那么便會返回304。
瀏覽器關於緩存使用的決策
那么,瀏覽器如何決定是使用哪種方式呢?這就和服務器在請求返回中的Header字段有關了。下面對相關的字段進行簡單介紹。
Cache-Control
Cache-Control 是最重要的規則。這個字段用於指定所有緩存機制在整個請求/響應鏈中必須服從的指令。該字段通常覆蓋默認緩存算法。另外,緩存指令是單向的,即請求中存在一個指令並不意味着響應中將存在同一個指令。
簡單地說,該字段用於控制瀏覽器在什么情況下直接使用本地緩存而不向服務器發送請求。一般具有以下值:
public
: 所有內容都將被緩存private
: 內容只緩存到似有緩存中no-cache
: 所有內容都不會被緩存no-store
: 所有內容都不會被緩存到緩存或者internet臨時文件中must-revalidation/proxy-revalidation
: 如果緩存的內容失效,請求必須發送到服務器/代理以進行重新驗證max-age=xxx( xxx is numeric )
: 緩存的內容將在 xxx 秒后失效, 這個選項只在HTTP 1.1可用, 並如果和Last-Modified一起使用時, 優先級較高
其中最常用的屬性便是 max-age
, 這個字段很簡單,就是瀏覽器在資源成功請求后的制定時間內,都將直接調用本地緩存和不會向服務器去請求數據。
Expires
Expires 頭部字段提供一個日期和時間,在該日期前的所有對該資源的請求都會直接使用瀏覽器緩存而不用向服務器請求(注意:cache-control max-age 和 s-maxage 將覆蓋 Expires 頭部。)
Expires 字段接收以下格式的值:“Expires: Sun, 08 Nov 2009 03:37:26 GMT”。
但是使用Expires
存在服務器端時間和瀏覽器時間不一致的問題。
Last-Modified/E-tag
Last-Modified
和E-tag
的作用都是向服務器確認當前緩存文件是否為最新。拋開功能不看,這兩個字段的表現如下:
- 若服務器在響應一個資源時添加了
Last-Modified
字段,那么當下一次瀏覽器再一次向服務器請求該資源時(前提是瀏覽器中上一次的資源被緩存過了),會在請求header中包含If-Modified-Since
字段,且值與服務器第一次響應給瀏覽器的Last-Modified
字段一致 - 若服務器在響應一個資源時添加了
ETag
字段,那么當下一次瀏覽器再一次向服務器請求該資源時(前提是瀏覽器中上一次的資源被緩存過了),會在請求header中包含If-None-Match
字段,且值與服務器第一次響應給瀏覽器的ETag
字段一致
那么上述是遵循了Http協議的瀏覽器會自動實現的,而要實現304的功能,就需要服務器(比如Apache對於靜態資源會自動實現這兩個字段的響應)或者我們手動在服務器端編寫響應的邏輯來實現。
- 若服務器在收到的資源請求中發現含有
Last-Modified
字段,則說明瀏覽器中包含了該資源的某一版本的緩存,此時服務器端將根據該字段的值進行一定的邏輯判斷,以決定讓瀏覽器直接使用已有的緩存(返回304
)還是將最新的文件發送過去(200
,發送新文件並更新Last-Modified
字段) - 若服務器在收到的資源請求中發現含有
If-None-Matc
字段,則說明瀏覽器中包含了該資源的某一版本的緩存,此時服務器端將根據該字段的值進行一定的邏輯判斷,以決定讓瀏覽器直接使用已有的緩存(返回304
)還是將最新的文件發送過去(200
,發送新文件,並更新ETag
)
若同時使用了Last-Modified
和ETag
,正確的做法應該是當兩者都符合條件時,才返回304
什么時候使用ETag?
Etag
主要為了解決 Last-Modified
無法解決的一些問題。
- 一些文件也許會周期性的更改,但是他的內容並不改變(僅僅改變的修改時間),這個時候我們並不希望客戶端認為這個文件被修改了,而重新GET。這種情況下可以將某個能用來表明文件內容是否被更改的值(比如
md5
)來作為ETag
- 某些文件修改非常頻繁,比如在秒以下的時間內進行修改,(比方說1s內修改了N次),
If-Modified-Since
能檢查到的粒度是s級的,這種修改無法判斷(或者說UNIX記錄MTIME只能精確到秒) - 某些服務器不能精確的得到文件的最后修改時間
不同的頁面打開方式產生的請求區別
一般我們打開(或者更新)一個頁面(或者資源)有幾種方式:
- 在地址欄中輸入地址,然后回車
- 激活當前頁面地址,然后回車
- F5刷新頁面
- 單機Back/Forward按鈕
上面幾種方式對資源的請求,會產生不同的結果,並且各瀏覽器的表現並不一致。具體的區別可以參考鳥哥的《瀏覽器緩存機制》
其中大家需要注意的一點是,刷新頁面(F5或者刷新按鈕),不管是否設置了max-age
,都會重新像服務器發送請求。但是這不影響304
邏輯。
實例代碼測試
用nodejs
寫了一個簡單的靜態文件服務器,用來測試上面的Cache-Control
和Last-Modified
,具體代碼可以看gist
例子比較簡單,大體邏輯:
- 每個資源的
200
返回設置max-age=10
,即緩存10秒。同時設置Last-Modified
- 每次收到請求后,若發現包含
if-modified-since
字段,則304
測試過程和結果:
- 第一次請求,不管是
index.html
還是index.css
均為200
,且response中包含了max-age
和Last-Modified
- 在地址欄中回車(10s以內),
index.html
為304
,index.css
為200 ok ( from cache )
- 若對頁面進行刷新( F5或者刷新頁面 ),兩者均為
304
(在10s內或者不在10s內)
需要注意的問題:
index.html
不管是否設置了max-age
,都是304
,同理,將index.css
直接放到地址欄中請求也是304
index.css
一次304
之后的10s內又能繼續進行不需要請求服務器的直接本地緩存。這里我個人認為,瀏覽器的“直接使用本地緩存“的效果和從服務器成功200
到數據產生的效果是一致的,除了文件本身,header字段等信息也是同樣會被cache的,因此304
之后,max-age
又可以被繼續使用一遍。
做了nodejs
的實驗之后,又開啟了apache
服務器,同樣的將index.html
和index.css
文件放置到服務器根目錄中,請求后,發現apache
默認已經做了Last-Modified
和ETag
的處理,並且在修改index.css
文件后,可以觀察到這兩個值的變化。