當前位置:
首頁 > 科技 > 掌握 HTTP 緩存——從請求到響應過程的一切(下)

掌握 HTTP 緩存——從請求到響應過程的一切(下)

前言

【第973期】掌握 HTTP 緩存——從請求到響應過程的一切(上)討論了關於利用 HTTP 頭來解決緩存問題。今日早讀文章由來自首個前端Online Judge ScriptOJ@鬍子大哈翻譯授權分享。

正文從這開始~

Cookies

你已經知道了緩存頭是如何起作用的,現在我們來看下在緩存裡面 cookie 起了什麼作用。首先, Cookie 的設定也在 HTTP 響應頭中,名字是 Set-Cookie。設置一個 cookie 的目的是標識這個用戶,就是說你需要為每個用戶設置一個 cookie。

想像一下緩存的場景,你是否會緩存一個包含了Set-Cookie的 HTTP 響應,在緩存時間內,每個人都會得到相同的 cookie 和同樣的用戶 session?你肯定不想這樣。

另外,用戶 session 狀態的改變可能會影響到響應內容的變化。一個簡單的場景:電商購物車。你給用戶要麼提供一個空購物車,要麼是用戶自己選了很多物品的購物車。同樣的道理,你不希望這個也被緩存,畢竟每個用戶都應該有自己的購物車。

一個解決方法是在運行時通過 JavaScript 設置 Cookie,比如 Google Analytics。GA 通過 JS 設置 cookie,但這個 cookie 既不影響渲染,也不設置 Set-Cookie 頭。GA 會在目標網站上添加類似於 "you are tracked via Google Analytics" 的圖標,但是只要這些改變都是在運行時添加進去的,就都沒有問題。

正確處理 cookie 和緩存

首先你需要知道你網站的 cookie 的工作原理。cookie 是不是只在特定時間使用(如在用戶登錄過程中使用)?原則上,cookie 是不是會被注入到所有響應?

正如上一節所說的,不論何時伺服器返回了一個帶有Set-Cookie 的響應,你都希望能夠保證它不會被緩存。那麼問題就轉化成為,當你返回一個帶有「用戶特性」內容的響應時(如購物車),CDN /代理伺服器,會作何操作?

如果沒設置 Set-Cookie,是不是允許緩存呢?

如果設置了 Set-Cookie,是不是自動丟棄所有Cache-Control 頭呢?

其實,如果從應用層面來講,你儘管可以去實現你所喜歡的 web 應用就可以了,至於 cookie 和 CDN 都是自動設置的。還是用 Apache 的 .htaccess 來作為例子來解釋:

#1)如果 cookie 沒設置,允許緩存

HeadersetCache-Control"public max-age=3600""expr=-zresp( Set-Cookie )

#2)如果 cookie 被設置,不允許緩存

Header always remove Cache-Control "expr=-nresp( Set-Cookie )

# 2a)第二條的另一種形式,如果設置了 cookie,緩存時間設置成

HeadersetCache-Control"no-cache max-age=0 must-revalidate""expr=-nresp( Set-Cookie )

規則1:如果沒設置 Set-Cookie,則給Cache-Control 設置一個默認值;

規則2:如果設置了 Set-Cookie,則忽略Cache-Control;

規則2a:是規則2的另一種表示形式,設置最大緩存時間是 0。

無 cookie 的訪問路徑

一些 CMS / 框架還在使用一種暴力的方式種 cookie。而實際上,決定是否種 cookie 取決於不同的因素,比如會話時間因素。如果你有一個很高安全性的 web 應用,設置會話時間是 5 分鐘,那麼為每個響應設置一個新 cookie 都不過分。而假設你的應用連「用戶特性」都沒有,也就是說所有的東西對所有用戶都是公用的,那麼設置任何形式的 cookie 都是沒有道理的。

所以下面這個例子是否適合你自己,很大程度上依賴於你的應用到底是什麼類型的。我們來一起看一下,我先給一下這個例子的上下文關係:假設你有個新網站,你的所有文章都在 http://www.foobar.tld/news/item/ 這個路徑下面。現在你希望能夠保證,所有訪問 /news/item/ 的路徑都不包含 Set-Cookie,因為你確定不需要 cookie。

# 通用 PHP 重定向做法,將"?path=$1"寫到重定向規則里

RewriteCond%{REQUEST_FILENAME}!-d

RewriteCond%{REQUEST_FILENAME}!-f

RewriteRule^(.*)$ index.php?path=$1[NC,L,QSA]

RewriteRule^$ index.php[NC,L,QSA]

# 利用 query 中的 path=來判斷

Header always unset Set-Cookie

通過這樣的設置,你就可以保證所有訪問/news/item/ 的路徑都不包含 Set-Cookie。而到底是否應該設置 cookie,需要你根據你自己的應用特點來判斷。

設計出來的緩存能力

有很多設計方案可以使你的 web 應用具有高緩存性。鑒於本文僅僅是一篇文章而不是一本書,我不可能每個點都深入的來講,但是我可以著重提一下通用的方法。

我還用電商作為例子。假設電商網站首頁的 top 位置上展示了正在出售的物品,生成這些物品需要進行若干次的資料庫操作,代價比較大,因此希望把它們緩存起來。但是,問題在於購物車,它是為那些登陸用戶準備的,所以希望得到的結果是: top 物品是一樣的,而針對登陸用戶展示購物車。

那麼優化策略首先要為每個用戶提供一個和登陸狀態無關的「通用」頁。然後通過 JavaScript 為已經生成的網頁提供購物車。站在用戶的視角,最終展示形式是一樣的。那麼現在你有了兩個請求(整個網頁請求 + 購物車請求),而不是一個請求(整個網頁請求,包含購物車)。ok,現在你可以把代價很大的部分,即 top 物品分離出來,把它們緩存起來了。

這種方法或者其延伸方法,不適合已經開發好的項目。因為它可能會改變很多介面和視圖層(MVC 架構)的內容。最好你在一開始就設計好。

緩存失效:busting 和 purging

使用 max-age 和 s-maxage 你已經可以很好地控制一個指定的響應被緩存多長時間。但是這不足以適用於所有的情況。這些設置都是在返迴響應時預設的,而現實情況往往是並不知道一個響應應該設置多久期滿。回想一下剛才電商首頁的例子:假設它包含了展示在 top 位置的 10 個實體。你設置了 max-age=900給這個首頁以保證每15分鐘刷新一次。現在,其中 1 個實體由於發布了太久了要被撤銷,那麼你就需要把之前的緩存響應刪掉,這時候其實還沒到 15 分鐘,那麼該怎麼辦?

不要擔心,這是一個常見的問題,有很多方法解決。首先我們先來解釋一下術語:

緩存 busting,是用來解決瀏覽器長期緩存問題,它通過版本標識來告訴瀏覽器該文件有一個新的版本。這時瀏覽器將不會從本地緩存取內容,而從源伺服器請求新版本的文件。緩存 busting的詳細介紹在這裡:What is Cache Busting?。

緩存 purging,表示直接從緩存中刪除內容(即響應),以使得緩存可以立馬得到更新。

用於版本管理的緩存 busting

這種方法經常使用在 CSS 文件、JS 文件上。通常一個確切的版本號、一串哈希或者時間戳都可以用作標識,如下面的例子:

數字版本號:style-v1.css,style.css?v=1

哈希串版本:style.css?d3b07384d113edec49eaa6238ad5ff00

這時候在發布程序的時候,你只要注意文件的版本就可以了。舉個例子,一個 HTML 網頁通過 這種形式包含了一個 CSS 文件。CSS 文件將會被緩存起來,這時如果你想讓你的新 CSS 文件起作用,那麼用最新的版本號命名它就可以。如果不做任何變化的話,即便你更新了文件,這個 HTML 還會使用緩存中的舊 CSS 文件。

緩存 purging

不同 CDN 供應商清除緩存的方式不一樣。很多供應商都是基於開源軟體 Varnish 來構建自己的 CDN 服務,所以一個通用的做法是在 HTPP 請求中使用 PURGE 結構,如:

PURGE/news/item/i-am-obsolete HTTP/1.1

Host:www.foobar.tld

使用這個請求通常需要許可權認證,或者是源確認(即 IP 白名單),不過不同供應商的要求也不一樣。

清除一個或幾個緩存項比較容易,但是在某些場景下,卻不是這麼簡單。舉個例子,一個博客的場景,博客裡面都有關於作者的部分,現在你要改變關於作者的一些內容,那麼你需要手動清理所有包含了作者信息的頁面。你確實可以一個一個手動清理,但是假設你有成千上萬個網頁被影響了,那問題就變得麻煩了。

下面介紹一個解決方案。

代理標籤

「代理標籤」 這個名字來源於 CDN 供應商 Fastly,不同供應商給它起的名字不一樣,比如還有叫它「緩存標籤」的,Varnish 叫它 Hashtwo/Xkey,這裡我就不詳細介紹其他供應商的情況了。

不論它叫什麼,它們的目的都是一樣的:給響應打標籤。這樣你就可以輕鬆地從緩存中刪除相關的標籤就可以,甚至都不用知道緩存的到底是什麼東西。

還是拿來舉例子,源端返回一個含有代理標籤的響應:

HTTP/1.1200OK

Content-Type:text/html

Content-Length:123

Surrogate-Key:top-10company-acme category-foodstuff

這個例子中的標籤為:top-10, company-acme,和category-foodstuff。這裡給一個電商的實際場景來理解其含義:這個響應包含了電商首頁的前 10 個物品,這些物品由 ACME 公司提供,並且其目錄類別都設定為食品類。

設置了標籤以後,當物品發生了變化以後,你只需要刪除包含有 company-acme 和 top-10 的標籤就可以了。是不是很簡單?

同樣,具體如何清除緩存的操作方法,不同 CDN 供應商是不一樣的。

寫在最後

上面討論的更多的是理論上的做法,還有很多文章專門介紹不同的 CDN 的使用。如果你想深入了解的話,下面的資料每篇可能都是你需要的。

谷歌開發者:HTTP 緩存

Push CDN 和 Pull CDN

CDN 類型(管理員視角)

緩存頭概覽

緩存詳解(Mozilla)

ETag頭詳解(Mozilla)

Cace-Control 頭詳解(Mozilla)

If-None-Match 頭詳解(Mozilla)

Fastly:代理標籤

KeyCDN:緩存標籤

作者:@James Sinclair

原文:https://blog.fortrabbit.com/mastering-http-caching

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 前端早讀課 的精彩文章:

webpack正式發布v3.0.0
大前端時代,從前端小工到架構師的進階錦囊!
【第974期】WAAPI
上海一條視頻招前端開發
掌握 HTTP 緩存——從請求到響應過程的一切(上)

TAG:前端早讀課 |

您可能感興趣

HTTP協議的請求與響應
淺析一次HTTP請求
美ITC駁回iPhone進口禁令請求,蘋果在與高通專利戰中先下一城
老楊竟然答應了BLACKPINK粉絲的這十個請求?
你知道你的HTTP請求有多辛苦嗎?
WE再度失利,粉絲紛紛請求換AD,WE老闆卻是這樣回應!
Spring MVC請求及返回JSON數據
ITC駁回了高通公司申請禁止進口iPhone的請求
通過拆分請求來實現的SSRF攻擊
iPhone 系統升級時一直顯示「已請求更新」,如何解決?
netty整合springMVC,實現高效的HTTP服務請求
Servlet 客戶端 HTTP 請求
WE、Mystic因為上廁所而請求暫停,或因此要被罰款一萬元
谷歌 G Suite 用戶無意中刪除了數據,請求谷歌恢復無果:將其告上法庭
基於非瀏覽器的跨域HTTP請求攻擊
高性能的 PHP 封裝的 HTTP Restful 多線程並發請求庫-MultiHttp
如何實現一個HTTP請求庫——axios源碼閱讀與分析
Nginx配置SSL實現https請求並重定向http請求的實現
高通再訴蘋果,請求禁售iPhone XS/XR
使用 gorilla/mux 進行 HTTP 請求路由和驗證