什麼是 HTTP 快取#
我們為什麼需要快取(cache)?
我們知道,我們每次訪問一個網頁都需要加載資源,如果這些資源都從遠端的伺服器上加載,整個請求和響應時間會很長,有沒有快一點的辦法呢?有,可以從附近的 CDN 請求資源,減少了長距離的網路傳輸,響應速度自然就提升了。可是,CDN 還是離客戶端有點遠,有沒有辦法在瀏覽器快取數據,儘量少的向外請求資源呢?其實這些思想都是快取的思想,在每個節點都做快取就可以極大地減少遠端資源請求,加快響應速度。
快取中有幾個概念
- Stale Content:陳舊的內容,即被快取且過期的內容
- Fresh Content:新鮮的內容,即被快取且未過期的內容
- Cache Validation:是聯繫伺服器以檢查快取內容的有效性,並在其即將過期時得到更新的過程。
- Cache Invalidation:是刪除快取中可用的任何陳舊內容的過程。
快取按照位置可以分為瀏覽器快取,代理快取,反向代理快取
瀏覽器快取#
瀏覽器快取僅限於一個用戶,並且與其他快取不相同,並且它可以存儲私有的響應。
代理快取#
與服務於單個用戶的瀏覽器快取不同,代理快取可以服務於訪問相同內容的數百個不同用戶。往往是由 ISP 實施的。
反向代理快取#
反向代理快取一般部署在伺服器端,用於減輕伺服器的負載。
快取頭#
每當伺服器發出一些響應時,它都會伴隨一些 HTTP 標頭,以指導快取與否以及如何快取此響應
Expires#
在 HTTP/1.1 和引入快取控制之前,有一個 Expires 標頭,它只是一個時間戳,告訴快取應該將某些內容視為新鮮的時間。比如
Expires: Mon, 13 Mar 2017 12:22:00 GMT
當然,快取上的時鐘必須與伺服器上的時鐘同步,否則可能無法實現所需的結果。
儘管 Expires 還在使用,但我們現在應該優先考慮Cache-Control
Pragma#
這也是來自 HTTP/1.1 的舊特性,我們可能會看到在這裡和那裡使用Pragma: no-cache
,希望阻止響應被快取。
Cache-Control#
快取控制(Cache-Control)規定了內容應被快取多長時間和以何種方式快取。
cache-control 可以包含多個值,
private
將快取設置為私有意味著該內容不會在任何代理中被快取,它只會被客戶端(即瀏覽器)快取。
public
如果設置為公開,除了被客戶端快取外,還可以被代理機構快取;為許多其他用戶服務。
no-store
指定該內容不被任何一個快取所快取。
no-cache
no-cache 表示可以保持快取,但快取的內容在被提供之前要從伺服器上重新驗證(例如,使用 ETag)。也就是說,仍然有一個對伺服器的請求,但是是為了驗證,而不是為了下載快取的內容。
max-age: seconds
max-age 規定了內容被快取的秒數。例如,如果 cache-control 看起來像下面這樣。
cache-control: max-age=900, private
s-maxage: seconds
s-maxage 這裡 s - 前綴代表共享。這條指令專門針對共享快取。像 max-age 一樣,它也可以得到一些東西被快取的秒數。如果存在,它將覆蓋共享快取的 max-age 和 expires 頭文件。
must-revalidate
must-revalidate 有時可能發生的情況是,如果你有網路問題,而內容無法從伺服器檢索,瀏覽器可能會在沒有驗證的情況下提供陳舊的內容。 must-revalidate 可以避免這種情況。如果這個指令存在,意味著在任何情況下都不能提供陳舊的內容,在提供數據之前必須從伺服器上重新驗證。
proxy-revalidate
proxy-revalidate 與 must-revalidate 類似,但它對共享或代理快取的規定是相同的。換句話說,proxy-revalidate 對於 must-revalidate 來說就像 s-maxage 對於 max-age 一樣。
Mixing Values
我們可以以不同的方式組合這些指令以實現不同的快取行為,但是no-cache/no-store
和public/private
是互斥的。如果同時指定no-store
和no-cache
,則no-store
將優先於no-cache
。
Validators#
到目前為止,我們只討論了內容是如何被快取的,以及多長時間的快取內容被認為是新鮮的,但我們沒有討論客戶端如何從伺服器上進行驗證。下面我們討論用於此目的的頭信息。
Etag
Etag 或 "Entity Tag" 是在 HTTP/1.1 規範中引入的。Etag 只是一個獨特的標識符,伺服器將其與一些資源聯繫在一起。這個 ETag 後來被客戶端用來做有條件的 HTTP 請求,說明 "如果 ETag 與我的 ETag 不一樣,就給我這個資源",只有在 ETag 不匹配的情況下才會下載內容。
生成 ETag 的方法在 HTTP 文檔中沒有指定,通常使用一些抗碰撞的哈希函數來給資源的每個版本分配 ETag。可以有兩種類型的 etag,即強和弱的 etag。
ETag: "j82j8232ha7sdh0q2882" - Strong Etag
ETag: W/"j82j8232ha7sdh0q2882" - Weak Etag (prefixed with `W/`)
強驗證 ETag 意味著兩個資源是完全相同的,它們之間根本沒有區別。而一個弱的 ETag 意味著兩個資源雖然嚴格意義上不一樣,但可以被認為是相同的。例如,弱的 ETag 可能對動態內容很有用。
現在我們知道什麼是 Etag 了,但瀏覽器是如何提出這個請求的呢? 透過向伺服器提出請求,同時在If-None-Match
頭中發送可用的 Etag。
考慮一下這樣的情況:你打開了一個網頁,其中加載了一個標誌圖片,快取期為 60 秒,ETag 為abc123xyz
。大約 30 分鐘後,你重新加載該網頁,瀏覽器會注意到 60 秒內新鮮的標誌現在已經過時了;它將觸發一個請求給伺服器,在 if-none-match 頭中發送過時的標誌圖像的 ETag
If-None-Match: "abc123xyz"
然後,伺服器將比較這個 ETag 和資源的當前版本的 ETag。如果兩個 ETag 都匹配,伺服器將發回 304 未修改的響應,這將告訴客戶,它所擁有的副本仍然是好的,它將被視為新的 60 秒。如果兩個標誌都不匹配,即標誌很可能已經改變,客戶端將被發送新的標誌,它將用來替換它所擁有的陳舊的標誌。
Last-Modified
伺服器可能包括Last-Modified
標頭,表明一些內容最後被修改的日期和時間。
Last-Modified: Wed, 15 Mar 2017 12:30:26 GMT
當內容過期時,客戶端將向伺服器發出一個條件性請求,其中包括它在名為If-Modified-Since
的頭中的最後修改日期,以獲得更新的 Last-Modified 日期;如果它與客戶端的日期相匹配,內容的Last-Modified
日期將被更新,以便在另外的 n 秒內被視為新鮮。如果收到的Last-Modified
日期與客戶擁有的日期不一致,則從伺服器重新加載內容,並替換為客戶擁有的內容。
If-Modified-Since: Wed, 15 Mar 2017 12:30:26 GMT
你現在可能會問,如果快取的內容既有Last-Modified
,又有ETag
,怎麼辦?那麼,在這種情況下,兩者都要使用,也就是說,當且僅當 ETag 與新檢索的內容相匹配,並且 Last-Modified 日期也相匹配時,將不會有資源重新下載。如果 ETag 不匹配或者 Last-Modified 日期大於伺服器上的日期,內容就必須重新下載。