標籤:

談談 HTTP 緩存

想學習更多前端或編程知識,歡迎關注專欄:敲代碼,學編程 - 知乎專欄

這幾天在面試的時候被問到了 HTTP 是如何進行緩存的,我在網上查了查,簡單整理了下筆記。

1. Pragma:

在 http 1.0 時代,給客戶端設定緩存方式可通過兩個欄位 Pragma 和 Expires,雖然這兩個欄位早可拋棄,但為了做 http 協議的向下兼容,你還是可以看到很多網站依然帶上這兩個欄位。

在響應報文中當該欄位值為 "no-cache" 的時候,會通知客戶端不要對該資源進行緩存,每次都得向伺服器發一次請求才行。

2. Expires:

有了 Pragma 來禁用緩存,那如果需要設置緩存的話就得有個東西來設置緩存的時間,對於 http 1.0 來說,Expires 就是做這件事的。

Expires 的值對應一個 GMT (格林尼治時間),比如 「Mon, 22 Mar 2017 11:12:01 GMT」 來告訴瀏覽器資源緩存過期時間,如果還沒有超過該時間點則不發請求,直接返回 200 OK。(from cache)

=所以如果面試官問你 http 響應 200 OK (from cache) 是否有發送 http 請求,那麼就請回答沒有,它們都是直接讀取緩存資源的。

當兩者一起使用時候,Pragma 優先順序更高,即當 Pragma 設置禁用緩存時,又給 Expires 定義一個還未到期的時間,會發現仍然會發送新的請求,所以 Pragma 優先順序比 Expires 高。

當然現在不提倡這樣的方式,因為它有兩個致命的缺點:

  1. Expires 定義的緩存時間是相對於伺服器上的時間而言的,而瀏覽器在判斷的時候是基於客戶端的系統時間的,如果用戶修改了自己電腦的系統時間,那麼這個緩存時間將沒有任何意義。
  2. 假如客戶端上某個資源緩存時間過期了,但此時其實伺服器並沒有更新過該資源,那麼這時候客戶端要求伺服器重新把東西再發送過來一遍,會浪費帶寬和時間,這顯然是不合理的,我們需要有一種正確的機制用來判斷東西到底可以直接使用緩存。

3. Cache-Control:

針對上述 Expires 時間是相對於伺服器而言,無法保證和客戶端時間統一的問題,http 1.1 新增加了 Cache-Control 來定義緩存過期時間,若報文中同時出現 Pragma、Expires 和 Cache-Control,那麼會以 Cache-Control 為準。

它可以由多個可選值組合而成,比如:

Cache-Control: max-age=3600, must-revalidate

它意味著該資源從原伺服器上取得的,且其緩存的有效時間為一小時,在後續一小時內,用戶重新訪問該資源則無需發送新的請求。

Cache-Control: no-cache, no-store

它告訴瀏覽器不使用緩存,要求每次請求要向伺服器發送請求,並且所有內容都不會被保存到緩存或者臨時文件中。

當然組合方式也有一定的限制,比如 no-cache 不能和 max-age 一起搭配使用。

4. Last-Modified:

為了解決前面遺留下的第二個問題,來保證當客戶端上某個資源保存的緩存時間過期了,但這時候其實伺服器並沒有更新過這個資源,伺服器能夠正確處理這樣的請求,而不是重新發送資源。

為了讓客戶端與伺服器之間能實現緩存文件是否更新的驗證、提升緩存的復用率,http 1.1 新增了幾個首部欄位來做這件事情。

伺服器將資源傳遞給客戶端時,會將資源最後更改的時間以 "Last-Modified: GMT" 的形式加入到響應報頭上一起返回給客戶端。

客戶端會為資源標記上該信息,下次再次請求時,會把該信息附帶在請求報文中發送,若傳遞的值與伺服器上該資源的最終修改時間一致,則說明該資源沒有被修改過。

請求報文有兩個欄位用來傳遞該標記時間,分別是:

  • If-Modified-Since: Last-Modified-value

該欄位告訴伺服器如果客戶端傳來的最後修改時間和伺服器上的一致,直接返回 304 狀態碼即可,當前各大瀏覽器均是使用該欄位來向伺服器傳遞保存的 Last-Modified 的值。

  • If-Unmodified-Since: Last-Modified-value

該欄位告訴伺服器如果客戶端傳來的最後修改時間和伺服器上的不一致,則直接返回 412 狀態碼(Precondition Failed)。

這兩者有什麼區別呢:

If-Modified-Since:

從字面上看,就是說:如果從某個時間點算起,如果文件被修改了。

  1. 如果真的被修改:那麼就開始傳輸,伺服器返回:200 OK
  2. 如果沒有被修改:那麼就無需傳輸,伺服器返回:304 Not Modified.

If-Unmodified-Since:

從字面上看, 意思是: 如果從某個時間點算起, 文件沒有被修改。

  1. 如果沒有被修改:則開始繼續傳送文件,伺服器返回: 200 OK
  2. 如果文件被修改:則不傳輸,伺服器返回:412 Precondition failed (預處理錯誤)

比如斷點續傳就會使用到這個欄位(一般會指定 Range 參數),要想斷點續傳,那麼文件就一定不能被修改,否則就不是同一個文件。

當然,Last-Modified 也有一定的缺陷,因為如果在伺服器上,一個資源被修改了,但其實際內容根本沒發生改變,會因為 Last-Modified 時間匹配不上而返回了整個實體給客戶端(即使客戶端緩存里有個一模一樣的資源)

5. Etag:

為了解決上述 Last-Modified 可能存在的不準確的問題,http 1.1 還推出了 Etag 實體欄位。

伺服器會通過某種演算法,給資源計算得出一個唯一標誌符(比如 md5 標誌),在把資源響應給客戶端的時候,會在響應報文中加上 "Etag: 唯一標識符" 返回給客戶端。

客戶端會保存該 Etag 欄位,並在下次請求的時候將其作為請求頭某欄位的值發送請求,伺服器只需要比較客戶端傳來的 Etag 跟自己伺服器上該資源的 Etag 是否一致,就可以很好地判斷資源相對客戶端而言是否被修改過了。

那麼客戶端如何把標記在資源上的 Etag 發送給伺服器呢?

  • If-None-Match: ETag-value

If-None-Match: "qwerrgerg-1332"

告訴服務端如果 ETag 沒匹配上需要重發資源數據,否則直接回送304 和響應報頭即可。

當前各瀏覽器均是使用的該請求首部來向伺服器傳遞保存的 ETag 值。

  • If-Match: ETag-value

該欄位告訴伺服器如果客戶端傳來的 Etag 值跟伺服器上的不一致,則直接返回 412 狀態碼(Precondition Failed)。

這兩者跟 Last-modified 十分類似。

如果 Last-Modified 和 ETag 同時被使用,則要求它們的驗證都必須通過才會返回304,若其中某個驗證沒通過,則伺服器會按常規返回資源實體及200狀態碼。

總結:

當我們在一個項目上做 http 緩存的應用時,我們還是會把上述提及的大多數首部欄位均使用上,例如使用 Expires 來兼容舊的瀏覽器,使用 Cache-Control 來更精準地利用緩存,然後開啟 ETag 跟 Last-Modified 功能進一步復用緩存減少流量。


推薦閱讀:

APP精細化HTTP分析(二):響應性能分析與優化
請正確使用http狀態碼,謝謝!
HTTP伺服器的本質:tinyhttpd源碼分析及拓展

TAG:HTTP |