摘要:所以我們要時刻留意,在使用時,一定要根據緩存命中率作出調整,在不發生緩存錯亂的情況之下,盡可能的提高資源的緩存命中率。
寫在前面
最近抽空參加了幾場大廠的面試,突然發現一個現象,就是不論面試偏服務端的職位還是偏客戶端的職位,不論面試的 5 年以上的高級職位,還是 3 年左右的中級職位,面試官開頭所問問題必然是關于 HTTP 的。
我記得之前找工作的時候,似乎都是先考察一些職位所需技能領域的基礎知識,之后再考察關于 HTTP 的東西,現在大家都將 HTTP 的問題放到面試的開頭來問,我覺的應該是越來越多的招聘者意識到,作為一個 Web 開發者,HTTP 真的是太重要了,必須要先考察。
回想起來,這幾年我自己對于 HTTP 的學習大多是碎片化的,很多東西無法系統地在腦海中組織起來。雖然感覺 HTTP 整體的學習難度是比較低的,但是各個知識點交雜在一起又變得很復雜很難,相信大家都會有同感。同時有些知識點,如果在實際工作中沒有采坑或者刻意深挖的話,很自然地就被忽略了。
由于在之前一次面試中,被狠狠地問了若干關于 Vary 的問題,所以想抽一些時間整理一下那些比較容易讓人忽略的知識點,算是查漏補缺吧。
內容協商首先需要了解的是內容協商這個術語。當我們通過某個 URI 來訪問其指向的資源時,HTTP 協議可以通過內容協商機制提供資源的不同的展示形式。
如果缺少服務端開發經驗話,對于這個概念可能會感到陌生,但其實我們在工作中幾乎都會遇到它,比如在調用接口時,經常會用到 Accept: application/json 這個頭部,有時可能會用到 Accept: application/xml,這就是內容協商,前者期望接口返回 json 格式的數據,而后者期望返回 xml 格式的數據。
一般客戶端涉及的常見頭部有以下幾個:
Accept: 聲明客戶端可以處理的資源格式
Accept-Charset: 聲明客戶端可以處理的字符集類型
Accept-Language: 聲明客戶端可以理解的自然語言
Accept-Encoding: 聲明客戶端支持的編碼格式
而服務端涉及的常見頭部包括:
Content-Type: 指示資源的 MIME 類型
Content-Language: 指示該資源所期望的自然語言
Content-Encoding: 指示資源使用該編碼格式進行內容轉換
仔細觀察的話,會發現它們其實存在著一定程度的對應關系。原因也很簡單,既然是協商,那必然就會和兩個人在進行說話一樣,如果兩者之間的對話內容沒有關聯,他們還怎么溝通呢?客戶端和服務端進行溝通同理。
如果想詳細了解該機制,可以參考MDN的文檔,很詳細,這里就不多說了。
這里順帶說明一下,對于內容協商機制中涉及的頭部,從 web 發展歷史上來看已經沒有什么實質的用途了,原因如下(有興趣的話可以閱讀這篇wiki):
Accept-Charset: 由于 utf-8 成為主流的字符集類型,所以使用其他字符集類型的服務可以將其轉換為 utf-8 類型
Accept-Language: 大體包含以下幾點
提供多種語言服務的網站往往是基于某種特定語言構建,再提供其他語言支持的,這樣每種語言類型的內容在質量上層次不齊,而訪問者可能會更傾向于內容質量更高的那一種語言,而內容協商機制無法替代用戶的主觀判斷
實踐中,對于切換網站語言的功能,切換方式往往更傾向于主動切換(比如提供一個切換的按鈕)而非自動切換
瀏覽器在用戶不提供語言相關配置的情況下,很難猜測用戶的自然語言傾向(一般可能會根據地理定位、ip等因素猜測),打個比方,比如我會經常出差去日本,但這不代表我會說日語,同時雖然我掛了加拿大的 vps,但是提供中文內容的網站,我還是傾向于看中文
Accept: 與 Accept-Language 類似,同樣因為內容的格式會因用戶的主觀意識而不同,還有諸多其他因素制約內容協商機制,所以最終失敗了。
唯一有些用途的是 Accept-Encoding,但鑒于如今大部分現代瀏覽器都已支持多種壓縮方式(常見的如 gzip、br),因此一定程度上已經不需要額外聲明這個頭部了,雖然大部分瀏覽器都會自動發送這個頭部,但其實這會造成額外 23 字節的浪費。
Vary 頭部在理解(或者鞏固)了內容協商的概念后,就可以介紹 Vary 這個頭部了。直接引用 MDN 對于它的描述:
The Vary HTTP response header determines how to match future request headers to decide whether a cached response can be used rather than requesting a fresh one from the origin server.Vary 是一個HTTP響應頭部信息,它決定了對于未來的一個請求頭,應該使用一個緩存作為響應還是向源服務器請求一個新的響應。
單純靠文檔對于 Vary 的描述來理解它其實是有些困難的,最起碼我會有這種感覺。
這個頭部的語法和其他的 HTTP 頭部類似,如下:
Vary:, , ...
不同的頭部之間使用逗號進行分割,同時可以指定 * 為它的值,這樣等價于將資源視為唯一,并不進行緩存,但這并不是最佳實踐,因此不建議這么做。
Vary 的工作原理一句話概括它的工作原理就是,就是它表示某個響應因某個響應頭部而不同。舉個例子,比如 Vary: Accept 的意思即為,響應因請求資源格式頭部而不同,那么通過相同 URI 訪問的資源就可以根據這個頭上知道其內容格式不同。
但我們已經知道,對于大部分內容協商機制中涉及的頭部,已經被看作是失敗的,那么 Vary 和這些頭部搭配使用還有什么意義呢?話雖如此,但 Vary 還可以與 HTTP 中其他的頭部來搭配使用,從而滿足很多應用場景下的特殊需求,比如動態服務、防止緩存錯亂等。
Vary 的應用場景以下簡單羅列一些常用的應用場景以及采坑指南。
Vary 與 動態服務關于動態服務,最常見的莫過于 Vary: User-Agent。眾所周知,UA 是一段特征字符串,通常包含區分客戶端類型、操作系統、版本號等信息,隨著移動 web 應用變得越流行,一個應用網站同時提供桌面和移動兩種版本的應用是很常見的事情。通過設置 Vary: User-Agent 頭部,對于搜索引擎,對于關鍵字的搜索結果可以提供更加準確的應用版本,對于客戶端,可以使其從緩存服務器獲取到相應應用類型的緩存版本,而不是錯誤地將桌面版緩存傳遞給移動版應用。
web 應用的性能在加載速度這一指標上,很大程度上取決于加載資源的大小,而圖片資源是所占比例最大的一塊。為了減少圖片的大小,除了對常見的圖片格式進行壓縮以外,chrome 推出的 WebP 格式也是不錯的選擇。但是這里的問題是,不是所有的瀏覽器都支持 WebP 圖片格式的,所以這里使用 Vary: Accept 來針對瀏覽器的支持情況返回相應的緩存副本,支持則返回 WebP 格式,不支持則返回縮略圖或者原圖。
還有其他關于動態服務的場景,比如要針對不同分辨率的屏幕加載不同質量的圖片(Client Hints 相關的頭部)、針對不同用戶身份提供不同的資源(Cookie頭部)等等。
Vary 與 緩存錯亂有時候我們會發現響應中存在 Vary: Accept-Encoding 頭部信息,我原先按照內容協商機制中所描述的內容來理解,但到后來才發現,其實很大程度上是為了防止緩存錯亂的問題。
設想一下,如果沒有這個頭部,當兩個分別支持 gzip 和 不支持 gzip 的客戶端對同一份資源進行獲取時,結果會變得十分微妙。如果不支持 gzip 的客戶端先訪問,緩存代理會緩存未壓縮的版本,那么當支持 gzip 的客戶端再訪問時,由于命中緩存,雖然它支持 gzip 但也只能加載未壓縮的資源。反過來同樣如此,支持 gzip 客戶端先訪問,則緩存代理會緩存壓縮版本,當不支持 gzip 的客戶端再訪問時,緩存同樣命中,但是由于它無法對壓縮資源解碼,所以會呈現亂碼。
通過 Vary: Accept-Encoding 我們可以防止這種情況的發生,因為 Vary 在這里其實是扮演著校驗器的角色,它會進一步對命中緩存的資源進行再校驗,如果發現頭部信息不同,則會將緩存資源視為無效,從而將請求繼續轉發至源服務器。這對于緩存代理服務器也有一定的益處,因為可以有有依據地針對不同的 Accept-Encoding 緩存不同的資源副本。
Vary 與 緩存命中率Vary 雖然可以防止緩存錯亂,但并不代表可以濫用,盲目的使用會適得其反,比如之前提及的 Vary: *,這樣等價于將每個請求視為唯一,并且不緩存其響應資源,除非有意為之,不然沒有人會犧牲緩存帶來的性能提升。
同時對于一些 Header 的值是開放性的,比如之前提及的 User-Agent,如果單純從字面量來匹配的話,眾多桌面瀏覽器的值會因各種因素而不同的,如果僅是簡單地將 UA 作為區分桌面端和移動端的依據,那么緩存命中率會達到一個很低的水平。如何解決這個問題呢?可以將這些 UA 頭部的值進行標準化,比如可以通過正則匹配所有桌面瀏覽器的 UA 并重新更改為 Desktop,之后再轉發至緩存代理和源服務器,這樣有利于提高緩存命中率,關于這部分的內容,可以參考這篇文章,其中有很細致的講解。
所以我們要時刻留意,在使用 Vary 時,一定要根據緩存命中率作出調整,在不發生緩存錯亂的情況之下,盡可能的提高資源的緩存命中率。
Vary 與 CORS對于跨域的有情況,Vary 也包含一些內容。HTTP 協議規定,當服務端響應包含 Access-Control-Allow-Origin 頭部,且它的值是一個具體的域名而不是通配符 *,那么這時必須要包含 Vary: Origin 這個頭部。
為什么要包含這個頭部,因為請求頭中的 Origin 頭部代表了該請求來源的具體域名信息,那么對于不同域名網站所發起的請求,會使用僅屬于它本身的緩存。一般而言,我們很少會遇到這種問題,因為一般都將 Access-Control-Allow-Origin 設置為了 *,至少我自己是這樣的。如果想進一步了解 Vary 和 CORS 的內容,可以參考這篇文章。
最后差不多就這么多內容了,如有錯誤,還望指正。
參考鏈接內容協商
Best Practices for Using the Vary Header
IE 與 Vary
CORS
Vary
Response with Vary Header
Understanding Vary Header
Getting the most out of Vary with Fastly
Why not conneg
條件型 CORS 響應下因缺失 Vary: Origin 導致的緩存錯亂問題
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/62052.html
摘要:醞釀許久之后,筆者準備接下來撰寫前端面試題系列文章,內容涵蓋瀏覽器框架分鐘搞定常用基礎知識前端掘金基礎智商劃重點在實際開發中,已經非常普及了。 這道題--致敬各位10年阿里的前端開發 - 掘金很巧合,我在認識了兩位同是10年工作經驗的阿里前端開發小伙伴,不但要向前輩學習,我有時候還會選擇另一種方法逗逗他們,拿了網上一道經典面試題,可能我連去阿里面試的機會都沒有,但是我感受到了一次面試1...
摘要:因為在頁面加載完成后,引擎維護著兩個隊列,一個是按頁面順序加載的執行隊列,還有一個空閑隊列,使用定時函數就是將回調函數加入到空閑隊列中,故和其他定時器是并發執行的。 1.window.onload和$(document).ready()的區別: ①執行時間:window.onload會在所有元素,包括圖片,引用文件加載完成之后執行,而$(document).ready()則會在HTML...
摘要:同源策略是什么跨域通信同源兩個文檔同源需滿足協議相同域名相同端口相同跨域通信進行操作通信時如果目標與當前窗口不滿足同源條件,瀏覽器為了安全會阻止跨域操作。 同源策略是什么? javascript跨域通信 同源:兩個文檔同源需滿足 協議相同 域名相同 端口相同 跨域通信:js進行DOM操作、通信時如果目標與當前窗口不滿足同源條件,瀏覽器為了安全會阻止跨域操作。跨域通信通常有以下方法 ...
閱讀 738·2021-10-09 09:44
閱讀 2005·2021-09-22 15:54
閱讀 5043·2021-09-22 10:55
閱讀 1434·2019-08-29 18:41
閱讀 771·2019-08-29 11:24
閱讀 2099·2019-08-28 18:20
閱讀 1024·2019-08-26 11:51
閱讀 3043·2019-08-26 11:00