HTTP 協議分別在 1.0 / 1.1 兩個時代推出了 Expires / Cache-control 兩種 cache 策略,這裏我們無需瞭解全部的細節,無需記住整個 RFC 內容
但是當我們需要使用 HTTP cache 策略時,我們需要注意以下細節:
Expires 是 HTTP 1.0 那個時代的東西了,目前來看,可以不使用了,因爲 HTTP 1.0 的 user agent 佔有率在 0.1% 以下(我們主要面向的 web 瀏覽器均默認使用 HTTP 1.1 )
Cache-control 是 HTTP 1.1 的新特性,也是我們主要做文章使用 cache 策略的工具
Cache 策略:
#1 保鮮期 only
這個是最最基礎的一種策略,只需要在響應頭中設定:
Cache-control: max-age=[secs]
[secs] 是 cache 在客戶端存活的秒數,例如 Cache-control: max-age=1800 表明 cache 的時間是半小時
只使用這樣一個聲明就可以使瀏覽器能夠將這個 HTTP 響應的內容寫入臨時目錄做 cache
這裏是簡要過程:
I
(1) 瀏覽器第一次請求資源 http://test.qq.com/test.cgi
(2) 查詢臨時文件目錄發現無 cache 存儲,遂發出請求到 web server
(3) web server 響應資源,並設定 Cache-control:max-age=300
(4) 瀏覽器收到響應將資源呈獻給用戶的同時,在臨時文件目錄以 ”http://test.qq.com/test.cgi” 爲 key 緩存這個響應
---5 分鐘內 ---
II
(1) 瀏覽器再一次請求資源 http://test.qq.com/test.cgi
(2) 查詢臨時文件目錄發現存在 cache 存儲,檢查保鮮期 max-age ,還未過期,則直接讀取之,響應給用戶
---5 分鐘後 ---
III
(1) 瀏覽器再一次請求資源 http://test.qq.com/test.cgi
(2) 查詢臨時文件目錄發現存在 cache 存儲,檢查保鮮期 max-age ,已經過期,則發請求到 web server
#2 保鮮期 + 最後修改時間驗證
這裏的要素是,在給出保鮮期的同時,給出一個資源的驗證方式:
Last-Modified: [UTC time]
[UTC time] 標示這個響應資源的最後修改時間,例如 Last-Modified: Mon, 06 Jul 2009 09:21:48 GMT
這個響應頭只有配合 Cache-control 的時候纔有實際價值,只是聲明校驗資源的方式,並不能影響資源的保鮮期時長
利用資源的可校驗性,我們可以實現在 cache 的資源超過保鮮期瀏覽器再次請求時的 304 響應,令瀏覽器再次使用之前的 cache
這裏是簡要過程:
I
(1) 同 #1 中 I (1)
(2) 同 #1 中 I (2)
(3) web server 響應資源,並設定
Cache-control:max-age=300
Last-Modified: Mon, 06 Jul 2009 09:21:48 GMT
(4) 同 #1 中 I (4)
---5 分鐘內 ---
( 同 #1 中 II)
---5 分鐘後 ---
III
(1) 瀏覽器再一次請求資源 http://test.qq.com/test.cgi
(2) 查詢臨時文件目錄發現存在 cache 存儲,檢查保鮮期 max-age ,已經過期
發現資源具有 Last-Modified 聲明,則爲請求帶上頭 If-Modified-Since: Mon, 06 Jul 2009 09:21:48 GMT
發送請求到 web server
(3) web server 收到請求後發現有頭 If-Modified-Since 則與被請求資源的最後修改時間進行比對
若最後修改時間較新,說明資源又被改動過,則響應整片資源內容, HTTP 200 ( 需要整塊內容寫爲包體 )
若最後修改時間較舊,說明資源無新修改,則響應 HTTP 304 ( 無需包體 ) ,告知瀏覽器繼續使用所保存的 cache
( 這裏當然也可以根據自己的需要決定是 200 還是 304 ,我們的 CGI 畢竟是一種原始的實現 )
#3 保鮮期 + 自定義標識驗證
這裏的要素是,在給出保鮮期的同時,給出另一種資源的驗證方式:
ETag: [custom flag]
[custom flag] 標示這個響應資源的由開發者自己確定的簽名驗證標識 ,例如 ETag: "abcdefg"
這個響應頭只有配合 Cache-control 的時候纔有實際價值,是聲明校驗資源的方式
ETag 的使用爲我們實現 304 響應提供了更多的靈活性,我們可以拋開必須將驗證轉化成時間格式的限制
這裏是簡要過程:
I
(1) 同 #1 中 I (1)
(2) 同 #1 中 I (2)
(3) web server 響應資源,並設定
Cache-control:max-age=300
ETag: "abcdefg"
(4) 同 #1 中 I (4)
---5 分鐘內 ---
( 同 #1 中 II)
---5 分鐘後 ---
III
(1) 瀏覽器再一次請求資源 http://test.qq.com/test.cgi
(2) 查詢臨時文件目錄發現存在 cache 存儲,檢查保鮮期 max-age ,已經過期
發現資源具有 ETag 聲明,則爲請求帶上頭 If-None-Match: "abcdefg"
發送請求到 web server
(3) web server 收到請求後發現有頭 If-None-Match 則與被請求資源的相應校驗串進行比對
可以是一個版本號,可以是短時間戳,可以是資源校驗和 ( 強烈不推薦使用 ) ,或者乾脆是一個常量 ( 可以乾脆拿來做容錯 )
If-None-Match 發來的串與我們的自有值比對,根據我們自己的任何策略算法,可以自由決定如何返回瀏覽器, 304 或 200
這裏有一個使用 ETag 來做容錯的例子 ( 應用列表目前在使用 ) :
(1) 我們的每次正常返回都是
200
Cache-control: max-age=1800
ETag: "anything"
這裏 anything 是個常量,我們只用來告訴瀏覽器, cache 過期要髮帶 If-None-Match 的請求過來
(2) 這樣來自客戶端的一大部分請求基本上都會帶上 If-None-Match 頭,我們的 CGI 據此可以知道這個請求的客戶端是否有 cache
此時如果 CGI 聯繫 server 失敗,那麼可以直接返回 304 ,驅使客戶端使用上一次 cache 的正確結果,且更新保鮮期 max-age 爲 300 秒
這樣我們實現了一個基於 HTTP cache 的容錯,如果我們的資源還能實現一套時間戳存儲的話
那麼我們可以在正常情況下也實現校驗後的 304 ,從而節省流量
這裏還有一個比較慘的教訓,國內 www 上都沒有文獻記載,全球業界也只有一點文獻可以找到:
IE6 在資源有 gzip 壓縮同時有 ETag 頭時, cache 後再次發請求不會帶 If-None-Match 頭!!!
這個教訓導致我們試圖通過 304 減少 u.qzone.qq.com 流量的一次嘗試失敗 ( 當然我們能換一種方式實現 )
非常詭異,不中招是不知道的 …