說說瀏覽器端快取的那點事兒-撲朔迷離的 etag 與 last-modified

發表於2016-08-29

前言

做為一個正宗的科班出身, 《計算機網路》 這門課程肯定是學過的,但到底有沒有學細學透學明白,就不得而知了。

依舊是做為一個正宗的科班出身偽前端,在新的網際網路寒冬之下又被某網際網路公司的面試官噴了。

雖然曾經還是比較仔細的看過 HTTP 相關的東西,但長時間沒有複習加上當時沒有做筆記、寫部落格沉澱還是有很多的生疏。

回去之後查了一下相關的資料,算是做一次知識的沉澱(造輪子)。

正文

1、 304

開啟 Chrome,按下 F12、訪問一個曾經訪問過的網頁,如果沒有勾選 Disable cache,很容易能看到這樣的狀態:

qq 20160821222213

304,代表 NOT MODIFIED,他發生在這樣的一種狀態下:伺服器正確接收到了一個帶條件(Conditional Validation)的 GET,如果這個條件是真的就會返回 304、否則就會返回 200(A conditional GET or HEAD request has been received and would have resulted in a 200 OK response if it were not for the fact that the condition evaluated to false)。

換個角度來說,如果瀏覽器接收到的 response 的狀態碼是 304,就代表這個資源在客戶端中的快取依然是有效的,即在上次拿到資源到當前這段時間之內伺服器端並沒有對這個資源做修改。

這樣做有什麼好處呢? 顯然這樣可以在使用很小的一個 HTTP 請求的代價上就實現下面兩個功能:

  • 確保拿到的資源是最新的
  • 客戶端資源沒有問題的情況下不需要再次那資源,解決每次完整請求資源時的效能問題。 撒

那所謂的帶提交的 GET 是怎麼回事呢? 請看下面的內容:

2、 撲所迷離的 etag 與 last-modified

a、曖昧期的表白

在大多數的情況下大家聽到的可能就是 etaglast-modified,沒錯,他就是伺服器在接收到帶條件的GET 請求之後塞到 responseheader 裡面。那條件是什麼?是不是又與 etaglast-modified 相對應呢?

答案是肯定的,他們的對應如下所示:

  • etag —— if-none-match
  • last-modified —— if-modified-since

qq 20160821235958

b、前戲

有了上面的對應關係之後相信不再撲朔了。他們的關係如下:

如果本地有相關資源的快取,並且在快取的時候響應頭裡面有 etag 或者 last-modified 的情況,這個時候去請求伺服器的時候就會是帶有條件的 GET 請求(Conditional Validation)。

在請求頭裡面可能會有兩個欄位: if-none-matchif-modified-since,其中 if-none-match 的值是伺服器上次返回該資源時響應頭裡面 etag 的值,if-modified-since 的值是伺服器上次返回該資源時響應頭裡面 last-modified 裡的值。

緊接著伺服器端就會接收到這個帶有條件的 request,然後會根據這兩個值去判斷快取的資源是否是最新的。

如果沒問題,即資源是最新的情況下就會返回 304body 為空;不是的話就會返回 200,即目前瀏覽器端的資源不是最新的,body 裡面就是資源體,然後客戶端就會用最新返回的資源覆蓋掉之前的資源

也就是說。傳送這種帶條件的請求的必要條件是 資源在瀏覽器端有快取,並且在快取的時候伺服器端的reponse 裡面有 etag 或者 last-modified。如果這個條件不滿足,傳送的請求就是沒有條件的(unconditionally)。

c、前戲後的協商 —— 一些優化

雖然說通過這種方式能夠減輕伺服器的壓力,解決一些請求資源時的效能問題。但是細細看來,還是存在一些浪費:每個都要去帶上條件請求伺服器來看資源是不是最新的,大多情況下是最新的情況下豈不是每次都在做無意義的驗證? 別急,做個約定就好,在 response 裡面加上 Cache-ControlExpires 即可。

通常他們是長這樣的:

  • cache-control:max-age=96247433
  • expires:Thu, 03 Jan 2019 04:24:16 GMT

qq 20160822000133

Cache-control 用於控制HTTP快取(在HTTP/1.0中可能部分沒實現,僅僅實現了Pragma: no-cache)Expires 表示存在時間,允許客戶端在這個時間之前不去檢查(發請求),等同 max-age 的效果。但是如果同時存在,則被 Cache-Controlmax-age 覆蓋。

題外話:Expires要求客戶端和服務端的時鐘嚴格同步。HTTP1.1引入Cache-Control來克服Expires頭的限制。如果max-age和Expires同時出現,則max-age有更高的優先順序。

d、前戲後的爭執

這麼多的欄位,現在數數好像已經有 6 個了,那麼他們是誰先誰後?

如果比較粗的說先後順序應該是這樣:

  1. Cache-Control —— 請求伺服器之前
  2. Expires —— 請求伺服器之前
  3. If-None-Match (Etag) —— 請求伺服器
  4. If-Modified-Since (Last-Modified) —— 請求伺服器

需要注意的是 如果同時有 etaglast-modified 存在,在傳送請求的時候會一次性的傳送給伺服器,沒有優先順序,伺服器會比較這兩個資訊(在具體實現上,大多數做法針對這種情況只會比對 etag)。伺服器在輸出上,如果輸出了 etag 就沒有必要再輸出 last-modified(實際上大多數情況會都輸出)。

具體可以參見知乎上的這個問題——關於瀏覽器的快取,有了Etaglast-Modified 還有必要存在嗎???。如果要深究,就要仔細看看 RFC 了,如果恰好你看了,歡迎在評論中提出。

e、完事後的討論 – 各種途徑的訪問

瀏覽器輸入 url 之後敲下回車,重新整理 F5 與強制重新整理(Ctrl + F5),又有什麼區別?

實際上瀏覽器輸入 url 之後敲下回車就是先看本地 cache-control、expires 的情況,重新整理(F5)就是忽略先看本地 cache-control、expires 的情況,帶上條件 If-None-Match、If-Modified-Since,強制重新整理(Ctrl + F5)就是不帶條件的訪問。

值得注意的是,如果是 瀏覽器輸入 url 之後敲下回車 你在 network 裡面看到的狀態往往是 200,但是大小是 0。這是因為這個 200 是上次訪問資源返回的狀態碼。

如果你是一位開發者,還是建議在 Chrome 裡面開啟 Disable Cache.

推薦閱讀資料

相關文章