Cache-Control for Civilians

這是一篇譯文,原文在這裏,英文好的能夠去閱讀原文,已得到做者贊成翻譯本文。css

文章目錄

  1. Cache-Controlhtml

  2. public and private瀏覽器

  3. max-age緩存

    a. s-maxage安全

  4. no-storebash

  5. no-cache服務器

  6. must-revalidatecookie

    a. proxy-revalidate網絡

  7. immutable併發

  8. stale-while-revalidate

  9. stale-if-error

  10. 緩存刷新

    a. 沒有緩存刷新 – style.css

    b. 請求參數 – style.css?v=1.2.14

    c. 文件名惟一 – style.ae3f66.css

    d. Clear-Site-Data

  11. 例子和一些用法

    a. 在線的銀行頁面

    b. 實時列車時刻表頁面

    c. FAQ頁面

    d. 靜態的js/css

    e. 裝飾的圖片

  12. 須要記住的

  13. 相關資源

    a. 按我說的作,別按我作的去作

最好的網絡請求就是永遠都不會發生的請求: 想要得到更快的網站時,避免網絡請求會遠勝於有網絡。所以,擁有一個可靠的緩存策略可讓你的用戶感受很是不同。

話雖如此,但在個人工做中,愈來愈多的時候我看到是不考慮甚至徹底忽視緩存實踐。也許是由於第一次看見時沒有過分關注,或者多是由於缺少這樣的意識或知識?無論怎樣,讓咱們來複習一下。

Cache-Control

一個最廣泛、有效的管理資源緩存的方法是經過HTTP的頭部字段Cache-Control,這個頭部能夠應用到每個網站的資源,這意味着咱們頁面上的全部內容均可以有一個很是定製和精細的緩存策略。這也使得其很是複雜和強大。

一個Cache-Control頭部可能看起來像這樣:

Cache-Control: public, max-age=31536000
複製代碼

Cache-Control是字段名,publicmax-age=31536000是指令。Cache-Control能夠接受多個指令,在這篇博客中我會介紹這些指令以及它們的用例

public and private

public意味着任何能夠緩存的地方都會緩存一份響應的數據。包括CDN,代理服務器等。public一般都是多餘的,由於其餘的指令(好比max-age)已經隱含它了。

相反,private指令只容許在相應的接受方(客戶端或瀏覽器)保存一份。雖然private不是一個安全相關的指令,可是它能夠防止公共緩存(好比CDN)緩存一些用戶相關的信息

max-age

max-age 定義了一段時間(相對於請求時)來告訴瀏覽器相應是否須要刷新

Cache-Control: max-age=60
複製代碼

這條指令會告訴瀏覽器,它能夠在接下來的60秒內使用緩存中的這個文件,而沒必要從新驗證它。60秒後,瀏覽器將服務器發請求從新驗證文件。

若是服務器有一個新文件供瀏覽器下載,它會返回狀態碼200,下載新文件,並替換掉HTTP緩存中舊文件,並按照指令緩存文件。

若是服務器沒有須要下載的更新副本,服務器會以304響應,並更新緩存的時間。這意味着,若是Cache-Control: max-age=60仍然存在,緩存文件還將緩存60秒。總共緩存120秒。

注意: 若是單單使用max-age會有一個很是大的風險…max-age能夠告訴瀏覽器資源是否已通過時,可是它不能告訴瀏覽器它是否可使用過期的版本。瀏覽器可能在沒有驗證的狀況下使用過時的版本。這種行爲有些不肯定性,致使咱們很難判斷瀏覽器究竟作了什麼。幸虧,咱們有一系列更明確的指令,咱們能夠用這些指令來加強max-age。感謝Andy Davies的幫助

s-maxage

The s-maxage (note the absence of the - between max and age) will take precedence over the max-age directive but only in the context of shared caches. Using max-age and s-maxage in conjunction allows you to have different fresh durations for private and public caches (e.g. proxies, CDNs) respectively.

s-maxage (注意沒有"-"在maxage之間)將優先於max-age指令,但僅在共享緩存的狀況下有效。結合使用max-ages-maxage,你能夠分別對私有緩存和公共緩存(例如代理、CDN)擁有不一樣的緩存刷新時間。

no-store

Cache-Control: no-store
複製代碼

若是咱們不想緩存文件怎麼辦?若是文件包含敏感信息怎麼辦?也許這是一個包含你銀行詳細信息的HTML頁面?又或者信息是實時的?也又多是一個包含實時股票價格的頁面?咱們確定不想緩存或從緩存中獲取這樣的信息: 咱們老是但願清除敏感的信息,而後獲取最新的實時信息。如今咱們須要用no-store

no-store是一條很是強大的指令,告訴瀏覽器或其餘設備不要緩存任何信息。任何帶有這條指令的資源,不管在什麼狀況下都會發起請求。

no-cache

Cache-Control: no-cache
複製代碼

這個是讓大多數人困惑的。no-cache並不意味着沒有緩存。它的意思是在你向服務器從新驗證緩存副本,而且服務器表示你可使用緩存副本以前,不要使用緩存。是的,這聽起來應該叫作必須從新驗證!只是聽起來也不是這樣。

no-cache其實是一種很是聰明的方式,能夠保證內容始終是最新的,可是若是可能的話,也可使用更快的緩存策略。no-cache老是會發起網絡請求,由於在返回瀏覽器的緩存以前,它必須與服務器從新驗證(除非服務器有更新的響應內容),可是若是服務器的響應良好,網絡傳輸只是文件的頭部:能夠從緩存中抓取主體,而不是從新加載。

因此,就像我說的,這是一種將獲取最新資源和從緩存中獲取文件的可能性結合起來的聰明方法,可是它會發出網絡請求,至少會有HTTP頭響應。

一個好的no-cache用例幾乎是任何動態HTML頁面。想一想一個網站的首頁:它不是實時的,也不包含任何敏感信息,可是理想狀況下,咱們但願頁面老是顯示最新的內容。咱們可使用cache-control: no-cache來指示瀏覽器首先檢查服務器,若是服務器沒有更新的內容( 304 ),咱們就使用緩存版本。若是服務器確實有一些更新的內容,它會照原樣作出響應( 200 )併發送更新的文件。

提示:在發送no-cache指令的同時發送max-age指令是沒有用的,由於從新驗證的時間限制是零秒。

must-revalidate

更使人困惑的是,雖然上面的那條指令聽起來應該被稱爲must-revalidate,但事實上must-revalidate是不一樣的(但類似)。

Cache-Control: must-revalidate, max-age=600
複製代碼

must-revalidate須要相關的max-age指令;上面代碼中,咱們把它設置爲十分鐘。

其中no-cache將當即與服務器進行從新驗證,而且只有在服務器認爲能夠的狀況下才使用緩存,must-revalidate就像有寬限期的no-cache同樣。在最初的十分鐘裏,瀏覽器不會發請求去服務器從新驗證,可是十分鐘事後,就會發請求去服務器驗證了。若是服務器說沒有什麼新的,它會以304做爲響應,而且新的緩存控制頭會應用到緩存中—十分鐘後纔會再次發送請求。若是十分鐘後,服務器上有一個更新的文件,咱們會獲得一個200的響應和它的內容,而且本地緩存會獲得更新。

must-revalidate的一個很好的例子就是像我這樣的博客:有不多改變的靜態頁面。固然,最新的內容是可取的,可是考慮到個人網站變化的頻率,咱們不須要像no-cache同樣頻繁的檢測更新。十分鐘驗證一次足夠了。

proxy-revalidate

與s-maxage相似,proxy-revalidatemust-revalidate的一個只對公共緩存起做用的特定版本。

immutable

immutable一個很是新且簡潔的指令,它告訴瀏覽器更多關於咱們發送的文件的類型——它的內容是可變的仍是不可變的?可是,在咱們看immutable怎麼用以前,讓咱們先看看它正在解決的問題:

用戶刷新會致使瀏覽器從新驗證文件,而無論文件是不是最新的,由於用戶刷新一般意味着如下兩種狀況之一:

  1. 頁面不能工做了,或者
  2. 內容過期了

那麼讓咱們檢查一下服務器上是否還有更新的東西。

若是服務器上有更新的文件,咱們確定想下載它。所以,咱們將獲得200個響應、一個新文件,而且——但願——問題獲得解決。然而,若是服務器上沒有新文件,咱們將返回一個304頭部,沒有新文件,而是一個完整的往返延遲。若是咱們從新驗證許多文件,致使不少的304,這可能會增長數百毫秒的沒必要要的開銷。

immutable是一種告訴瀏覽器文件永遠不會改變的方式,它是不可變的,所以永遠不須要從新驗證它。咱們能夠徹底減小往返延遲的開銷。咱們所說的可變或不可變文件是什麼意思呢?

- style.css: 當咱們改變這個文件的內容時,咱們根本不改變它的名字。文件老是存在的,它的內容老是變化的。這個文件是可變的。 
- style.ae3f66.css: 這個文件是獨一無二的——它是以基於內容的指紋命名的,因此當內容改變的時候,咱們獲得了一個全新的文件。這個文件是不可變的。
複製代碼

咱們將在緩存刷新部分詳細討論這一點。

若是咱們可以以某種方式向瀏覽器傳達咱們的文件是不可變的——它的內容永遠不會改變——那麼咱們也可讓瀏覽器知道,它不須要費心去檢查更新的版本:永遠不會有更新的版本,由於當文件的內容改變時,它就再也不存在了。

這正是不可變指令的做用:

Cache-Control: max-age=31536000, immutable
複製代碼

在支持immutable的瀏覽器中,用戶刷新永遠不會在31536000秒內致使從新驗證。這意味着不會花費沒必要要的往返時間在304響應上,這可能會在關鍵路徑上爲咱們節省大量延遲( CSS塊渲染)。在高延遲鏈接上,這種節省多是顯而易見的。

注意: 你不該該把immutable應用到任何不是不可變的文件。您還應該有一個很是強大的緩存刷新策略,這樣您就不會無心中主動緩存一個應用了不可變的文件。

stale-while-revalidate

我真的,真的但願stale-while-revalidate能獲得有更好的支持。

到目前爲止,咱們已經談了不少關於從新驗證的事情: 瀏覽器發請求到服務器檢查是否有文件更新。在高延遲鏈接上,僅從新驗證的持續時間就已很明顯,而且該時間就是死時間——在咱們從服務器獲得響應以前,咱們既不能釋放緩存(304),也不能下載新文件(200)。

stale-while-revalidate提供了一個寬限期(由咱們定義),在寬限期內,當咱們檢查更新版本時,容許瀏覽器使用過時資源。

Cache-Control: max-age=31536000, stale-while-revalidate=86400
複製代碼

這條指令告訴瀏覽器,「這個文件可使用一年,可是在那一年結束後,你有一個額外的星期能夠繼續使用這個過期的資源,同時在後臺從新驗證它」。

對非關鍵資源來講,stale-while-revalidate是一條很好的指令,固然,咱們想要最新的版本,可是咱們知道,若是咱們在檢查更新時使用過時的資源,不會形成任何損害。

stale-if-error

以相似於tale-while-revalidate的方式,stale-if-error容許瀏覽器有一個寬限期,在該寬限期內,若是從新驗證的資源返回5xx類錯誤,你能夠返回一個過時的資源。

Cache-Control: max-age=2419200, stale-if-error=86400
複製代碼

這條指令,咱們指示緩存文件在28天(2419200秒)內是新的,而且若是在此以後遇到錯誤,咱們將容許額外的一天(86400秒),在此期間咱們返回過時資源。

Cache Busting

談論緩存而不談論緩存刷新是不負責任的。我老是建議在考慮緩存策略以前先解決緩存刷新策略。反之則很是頭痛。

緩存刷新是爲了解決這樣一個問題: 我剛剛告訴瀏覽器在下一年裏使用這個文件,可是我剛剛改變了它,我不想讓用戶等一全年後才獲得新的文件!我該怎麼能干預呢?!

沒有緩存刷新 – style.css

這是最不可取的作法: 這是絕對不會刷行緩存的。這是一個可變文件,咱們很難緩存它。

您應該很是當心地緩存這樣的文件,由於一旦它們出如今用戶的設備上,咱們幾乎就失去了對它們的全部控制。

儘管這個例子是一個樣式表,可是HTML頁面也徹底屬於這個陣營。咱們不能改變網頁的文件名——想象一下這會形成多大的影響!—這就是爲何咱們傾向於根本不緩存它們。

請求參數 – style.css?v=1.2.14

這裏,咱們仍然有一個可變的文件,可是咱們在它的文件路徑中添加了一個查詢字符串。雖然比什麼都不加要好,但它仍然不完美。若是在中間的什麼步驟去掉了查詢字符串,咱們就能夠回到以前的狀況,即徹底沒有緩存刷新。許多代理服務器和CDN不會緩存帶有字符串的任何內容(例如,從Cloudflare本身的文檔就說過對"style.css?something"的請求會被替換爲"style.css"),多是配置,也多是處於防護性地目的(查詢字符串可能包含特定於一個特定響應的信息)。

文件名惟一 – style.ae3f66.css

指紋識別是緩存刷新文件的首選方法。經過每次內容改變時都改變文件名,咱們在技術上不會緩存任何東西:咱們最終會獲得一個全新的文件!這很是健壯,而且容許使用不可變的。若是您能夠在靜態資產上實現這一點,請這樣作!一旦您成功實現了這種很是可靠的緩存刷新策略,您就可使用最激進的緩存形式:

Cache-Control: max-age=31536000, immutable
複製代碼

實現細節

這個方法的關鍵是改變文件名,但它不必定是指紋。如下全部示例都具備相同的效果:

  1. /assets/style.ae3f66.css: 經過hash文件來刷新.
  2. /assets/style.1.2.14.css: 經過版本號來刷新.
  3. /assets/1.2.14/style.css: 經過目錄結構來刷新.

然而,最後一個例子意味着咱們對每一個版本進行版本控制,而不是對每一個單獨的文件進行版本控制。這反過來意味着,若是咱們只須要緩存咱們的樣式表,咱們還必須緩存該版本的全部靜態文件。這是潛在浪費,因此最好選擇(1)或(2)。

Clear-Site-Data

緩存失效很難——衆所周知如此——因此目前有一個規範正在幫助開發人員很是明確地一次性清除他們站點的整個緩存:Clear-Site-Data

我不想在這篇文章中講太多細節,由於Clear-Site-Data不是緩存控制指令,而是一個全新的HTTP頭。

Clear-Site-Data: "cache"
複製代碼

將該頭應用到您的任何一個源資產將清除整個源的緩存,而不只僅是它所附加的文件。這意味着,若是你須要從全部訪問者的緩存中清除你的整個站點,你能夠只將上面的標題應用到你的網頁上。

在撰寫本文時,瀏覽器支持僅Chrome, Android Webview, Firefox, 和 Opera。

提示: Clear-Site-Data將接受許多指令:"cookies"、"storage"、"executionContexts"和"*"(表示全部上述內容)。

例子和一些用法

好吧,讓咱們來看一些可能使用Cache-Control頭部的場景。

在線的銀行頁面

像網上銀行應用程序頁面,列出你最近的交易,當前的餘額,也許還有些敏感的銀行帳戶信息,須要是最新的(想象一下,你收到了一個列出你一週前餘額的頁面!)並確保不被他人看見(您確定不但願您的銀行詳細信息存儲在共享緩存(或者任何緩存,真的) )。

爲此,咱們可使用:

Request URL: /account/
Cache-Control: no-store
複製代碼

根據規範,這足以防止瀏覽器在私有和共享緩存中長久的保存響應:

no-store告訴緩存不許存儲請求或響應的任何部分。該指令適用於私有和共享緩存。"不許"在這裏意味着緩存不得有意將信息存儲在非易失性存儲器中,而且必須盡最大努力在轉發信息後儘快將其從易失性存儲器中移除。

可是若是你想有更強的防護性,也許你能夠選擇:

Request URL: /account/
Cache-Control: private, no-cache, no-store
複製代碼

這將明確指示不要在公共緩存(例如CDN )中存儲任何內容,要始終提供儘量最新的副本,而且不要將任何內容保存到存儲中。

實時列車時刻表頁面

若是咱們正在構建一個顯示近實時信息的頁面,若是這些信息存在的話,咱們但願確保用戶老是能看到咱們能給他們的最好的、最新的信息。咱們可使用:

Request URL: /live-updates/
Cache-Control: no-cache
複製代碼

這個簡單的指令意味着瀏覽器在與服務器確認緩存是否被容許使用前不會直接從緩存中顯示響應。這意味着用戶永遠不會看到過期的列車信息,可是若是服務器要求緩存鏡像最新的信息,他們就能夠從從緩存中獲取。

對於幾乎全部網頁來講,這一般是一個明智的默認設置:給咱們最新的可能內容,可是若是可能的話,讓咱們使用緩存來加速。

FAQ頁面

像FAQ這樣的頁面極可能不多更新,並且其中的內容也不太可能對時間敏感。它固然沒有實時運動成績或飛行狀態那麼重要。咱們可能能夠緩存這樣的頁面一段時間,並強制瀏覽器按期檢查新內容,而不是每次訪問。讓咱們看看:

Request URL: /faqs/
Cache-Control: max-age=604800, must-revalidate
複製代碼

這告訴瀏覽器將網頁緩存一週( 604800秒),一旦這一週結束,咱們就須要向服務器查詢是否須要更新。

注意:在同一個網站中對不一樣的頁面採用不一樣的緩存策略可能會致使這樣一個問題: 您的 no-cache 主頁請求它引用的最新樣式,f4fa2b.css,可是您的要緩存三天的FAQ頁面仍然指向樣式a3f66.css。這可能會有輕微的影響,但這是一個您應該注意的場景。

靜態的js/css

假設咱們的app.[fingerprint].js文件更新很是頻繁——可能在咱們發佈的每個版本中都是如此——可是咱們也在每次文件發生變化時都會根據內容對其重命名(這很是好),而後咱們能夠這樣作:

Request URL: /static/app.1be87a.js
Cache-Control: max-age=31536000, immutable
複製代碼

咱們是否很是頻繁地更新JS並不重要:由於咱們有可靠地緩存刷新,因此咱們能夠想緩存多久就緩存多久。在這種狀況下,咱們選擇緩存一年。我選擇了一年,由於首先,一年是很長的時間,其次,瀏覽器實際上不太可能將文件保存那麼長時間(瀏覽器有有限的存儲空間能夠用於HTTP緩存,因此它們本身會按期清空部分文件;用戶可能也會清除他們緩存)。任何超過一年的設置不會更有效。

此外,由於這個文件的內容從不改變,咱們可讓瀏覽器知道這個文件是不可變的。即便用戶刷新了頁面,咱們也不須要在一年內對它進行從新驗證。咱們不只得到了使用緩存的速度優點,還避免了從新驗證的延遲損失。

裝飾的圖片

想象一張純粹裝飾性的照片伴隨着一篇文章。它不是信息圖或圖表,它不包含任何對理解頁面其他部分相當重要的內容,用戶甚至不會真正注意到它是否徹底丟失了。

圖像一般是須要下載的很是重的資源,因此咱們想緩存它;它對頁面並不重要,因此咱們不須要獲取最新版本;咱們甚至有可能在圖像有點過期後就再也不提供圖像了。讓咱們這樣作:

Request URL: /content/masthead.jpg
Cache-Control: max-age=2419200, must-revalidate, stale-while-revalidate=86400
複製代碼

在這裏,咱們告訴瀏覽器將圖像存儲28天( 2419200秒),咱們要在28天的時間限制後向服務器查詢更新,若是圖像過時不到一週( 86400秒),咱們在後臺獲取最新版本時使用該圖像。

須要記住的

  • 緩存刷新相當重要。在開始緩存策略以前,先制定緩存刷新策略。
  • 通常來講,緩存網頁內容是個壞主意。超文本連接不能被破壞,並且因爲你的網頁一般是進入你網頁其他子資源的入口,你最終也會緩存對你靜態資產的引用。這會讓你(和你的用戶)感到沮喪。
  • 若是您要緩存任何一個網頁,若是一個網站上不一樣類型的網頁上有不一樣的緩存策略,而且其中一類網頁老是新的,而另外一類網頁有時是從緩存中獲取的,那麼這可能會致使不一致。
  • 若是你能依賴緩存刷新去緩存你的靜態資源,那麼你最好用一個不可變的指令一次性緩存多年。
  • 非關鍵內容能夠有一個過期的寬限期,指令相似於過時同時從新驗證。
  • immutablestale-while-revalidate不只給咱們帶來了緩存的傳統優點,還容許咱們在從新驗證時下降延遲成本。

儘可能避免網絡請求,可讓咱們的用戶得到更快的體驗(同時下降基礎設施的吞吐量)。經過對咱們的資源有一個好的見解,並對咱們可用的東西有一個概述,咱們能夠開始爲咱們本身的應用程序設計很是精細、定製和有效的緩存策略。

用緩存管理一切

相關資源

按我說的作,別按我作的去作,(做者調皮了)

在別人在Hacker News上噴我虛僞以前,我想說的是我本身的緩存策略很是很差,我甚至都不打算去作。(全文完)

第一次翻譯這麼長的,若有錯誤,請指出,必定修改。

相關文章
相關標籤/搜索