真正“搞”懂HTTP協議10之快取控制

Zaking發表於2023-02-08

  HTTP快取相關的問題好像是前端面試中比較常見的問題了,上來就會問什麼cache-control欄位有哪些,有啥區別啥的。嗯……說實話,我覺得至少在本篇來說,HTTP快取還算不上覆雜,只是欄位稍微多了點,大家用心記一下就好啦。

  快取的概念,其實在你訪問網際網路中的任何資源其所產生的任何鏈路中的每一個節點幾乎都會進行快取,整個快取體系和細節十分複雜。比如瀏覽器快取,伺服器快取,代理伺服器快取,CDN快取,等等等等。

  但是快取又十分重要,不可缺少,為啥這麼說呢?由於HTTP請求的鏈路漫長,環境複雜,把一些必要的資訊在關鍵的節點進行快取,下次請求的時候可以儘可能的複用資料,就可以節省一部分資源的消耗,減少HTTP請求-應答的成本,節約頻寬,加快響應速度。

  那麼,基於請求-應答模式的特點,快取大致可以分為伺服器快取和客戶端快取,而伺服器快取經常與代理服務關聯在一起,所以,我們今天講的快取,其實主要聊的就是客戶端快取,也就是瀏覽器快取。下一篇,下下篇就會講代理和代理快取。嗯……代理其實也不復雜,但是往往兩個東西組合起來的就會複雜一些。

  那我們就來看看瀏覽器是咋快取的吧。

一、伺服器的快取控制

  假設,現在沒有快取,我們想象一下獲取資源的方式是什麼樣的?客戶端請求資源,伺服器返回資源,等下一次想要獲取同樣資源的時候,哪怕伺服器的資源並沒有更新,還是要重新走一遍網路請求,然後伺服器返回資源的完整鏈路。

  那,如果有了快取,在客戶端第一次獲取到請求的資源後,把資源快取到本地,下次再去請求的時候,發現本地有這個資源,直接拿來用就好了,完全不用去走網路請求。有快取的簡易流程大概是這樣的:

  1. 瀏覽器發現請求的該資源無快取,直接傳送請求,獲取伺服器資源。
  2. 伺服器收到請求後,響應該請求並返回資源,同時標記資源的有效期。
  3. 瀏覽器快取資源,等待下次使用。

  我們仔細的閱讀一下這個簡單的快取資源請求流程,發現其中有幾個重要的節點。首先,伺服器在返回該資源時,要標記該資源的有效期。然後,瀏覽器初次請求肯定是沒快取的,再次請求的時候,它要根據該資源的有效期來判斷下一步該怎麼辦。OK,我們再簡單一點,如果我們試圖去獲取快取資源,其實是要看伺服器的標記的。

  那麼換句話說,伺服器標記快取資源,瀏覽器會驗證該快取資源的標記

1、Cache-Control

  這個欄位想必大家非常熟悉了吧,就是伺服器用來標記資源快取有效期的頭欄位。我們可以這樣來標記資源的有效時間:

Cache-Control: max-age=30

  啥意思呢,就是該資源的有效期是30秒。這是一個相對時間,時間計算的起點是報文建立的時刻,也就是Date頭欄位的時間,是指資源離開伺服器的時刻,而不是客戶端收到報文的時候,換句話說,假設我們設定的時間是5秒,但是鏈路請求很長,花費了4秒的時間,那麼快取對於瀏覽器的有效時間只是1秒。那你可能會說,就這一秒有啥用啊~~假設你網站的訪問量特別大,每秒有上百萬的訪問,那你可以想象到這僅僅一秒的時間能節省伺服器多大的壓力了吧。當然,只是舉個極限的例子~

  除了max-age這個最常用的屬性以外,還有三個屬性可以更精確的指示瀏覽器如何使用快取:

  • no-store:不允許快取,用於某些變化非常頻繁的頁面,比如拼多多秒殺頁面。
  • no-cache:它的字面意思和no-store很容易搞混,實際上它的意思並不是不允許快取,而是可以快取,但是在使用之前必須要去伺服器驗證是否過期,是否有最新的版本。
  • must-revalidate:和no-cache又很相似,它的意思是快取不過期就可以繼續使用,但是過期瞭如果還想用就必須去伺服器驗證一下。

  cache-control的這幾個欄位的差別十分細微,大家要稍微認真記一下。

  我們學了四個Cache-Control頭欄位的屬性,我們來看張圖,鞏固一下這些細微的差別:

  我們來過下整張圖的流程,首先瀏覽器要透過no-store屬性判斷該資源是否允許快取,如果允許快取,那麼繼續判斷是否在使用快取前去驗證,需要驗證的話就直接判斷max-age,否則還要再去判斷must- revalidate屬性,快取失效後是否需要驗證。

二、客戶端的快取控制

  我們剛剛學習了Cache-Control頭欄位,並且學習了伺服器是怎麼控制該欄位的相關屬性的。不僅僅是伺服器可以控制快取,客戶端也可以控制快取,客戶端是怎麼控制的呢?

  當你點選瀏覽器的重新整理按鈕的時候,實際上,瀏覽器就在HTTP請求中夾帶了"Cache-Control:max-age=0",之前說過,max-age是生存時間,紀錄的是從伺服器生成的那一刻的有效期,而瀏覽器本地的資源,肯定不可能是0,所以當瀏覽器加上了max-age=0的時候,每次都會向伺服器請求最新的資源。而當你使用Control+F5強制重新整理的時候其實是瀏覽器發了一個“Cache-Control: no-cache”。基本上和max-age=0差不多一樣的效果,但是含義肯定是不一樣的,就看伺服器要怎麼理解和處理不同的欄位。

  那麼什麼時候快取才能派上用場呢?當我們點選瀏覽器的前進後退按鈕的時候,就會直接從快取中獲取資料,另外,重定向的時候,也可能會使用到快取。那這兩類操作有啥區別呢。其實本質上來說,就是前進、後退、跳轉這樣的操作,瀏覽器不會私自加上Cache-Control欄位,並且會清空If-Modified-Since 和 If-None-Match欄位(後面會說條件請求),所以就會檢查快取,直接利用之前的資源,不再進行網路通訊。

  我們可以在稍後的例子中試一下~

三、條件請求

  僅僅只是Cache-Control欄位,只能做到重新整理資料,不能很好的利用快取資料,由於快取資料可能會失效,所以每次使用快取前還必須去伺服器驗證一下。有點麻煩~

  那咋整呢?我們可以先發一個HEAD請求,或許伺服器資源的一些基本資訊,然後和快取的資料做比較,如果沒有改動就使用快取,否則呢,就去伺服器獲取最新的資源。

  但是這樣的兩個網路請求成本太高了,所以HTTP就定義了一些If開頭的“條件請求”欄位,專門用來驗證資源是否過期,把兩個請求合併到一個請求中,驗證的職責也交給伺服器,客戶端只要坐享其成就可以了。

  條件請求一共有五個欄位,我們最常用的只有“if-Modified-Since”和“If-None-Match”這兩個。需要第一次請求的時候,伺服器預先提供“Last-modified”和“ETag”,當第二次請求的時候就可以帶上快取裡的原值,驗證資源是否是最新的。

  如果資源沒有變化,那麼伺服器返回個304,更新下資源的有效時間,使用快取就可以了。

  Last-modified很好理解,就是最後一次修改檔案的時間。那ETag是啥呢?ETag 是“實體標籤”(Entity Tag)的縮寫,是資源的一個唯一標識,主要是用來解決修改時間無法準確區分檔案變化的問題。

  比如,檔案的修改時間是秒級甚至更短的,所以一秒內的新版本是無法區分的,再比如,一個檔案定期更新,但有時內容沒有變化,用修改時間就會以為發生了變化,傳送給客戶端以為是新的資源,浪費頻寬。

  使用ETag就可以精確的識別資源的變動情況,讓瀏覽器更有效地利用快取。

  ETag還有強弱之分。

  強 ETag 要求資源在位元組級別必須完全相符,弱 ETag 在值前有個“W/”標記,只要求資源在語義上沒有變化,但內部可能會有部分發生了改變(例如 HTML 裡的標籤順序調整,或者多了幾個空格)。至於是強還是弱,其實是由伺服器自主決定的。弱也可以強,強也可以弱。

  條件請求裡其他的三個頭欄位是“If-Unmodified-Since”“If-Match”和“If-Range”,其實只要你掌握了“if-Modified-Since”和“If-None-Match”,可以輕易地“舉一反三”。

四、示例

  我們先搞個max-age的小例子,看看快取能否生效:

res.setHeader("Cache-Control", "max-age=20");

  就很簡單,我們可以看到在響應頭中返回了我們設定好的快取欄位:

真正“搞”懂HTTP協議10之快取控制

  大家還可以自己嘗試設定no-store,no-cache等欄位。當你設定了no-store屬性後,你會發現,哪怕使用瀏覽器的前進,後退按鈕,每次也是重新從伺服器獲取資源,但是no-cache和max-age則會使用快取。

  簡單的例子就這樣啦~

五、小結

  本篇到這裡就結束啦~我們稍稍回顧一下,都學了哪些內容,其實就一個欄位Cache-Control,並且稍微涉及到了一點關於瀏覽器可能會夾帶的私貨是怎樣的。並且我們還學習了條件請求以及強弱Etag,大家要著重瞭解下。然後呢,本篇沒有問題,但是有個遺留的小例子我沒寫,就是條件請求的例子。大家可以參照我上面的小例子,試著在這裡玩一下條件請求。

  好啦~下一篇,我們來學下代理~

 

相關文章