30 分鐘 HTTP 查漏補缺之 Vary

寫在前面

最近抽空參加了幾場大廠的面試,忽然發現一個現象,就是不論面試偏服務端的職位仍是偏客戶端的職位,不論面試的 5 年以上的高級職位,仍是 3 年左右的中級職位,面試官開頭所問問題必然是關於 HTTP 的。web

我記得以前找工做的時候,彷佛都是先考察一些職位所需技能領域的基礎知識,以後再考察關於 HTTP 的東西,如今你們都將 HTTP 的問題放到面試的開頭來問,我覺的應該是愈來愈多的招聘者意識到,做爲一個 Web 開發者,HTTP 真的是過重要了,必需要先考察。面試

回想起來,這幾年我本身對於 HTTP 的學習大可能是碎片化的,不少東西沒法系統地在腦海中組織起來。雖然感受 HTTP 總體的學習難度是比較低的,可是各個知識點交雜在一塊兒又變得很複雜很難,相信你們都會有同感。同時有些知識點,若是在實際工做中沒有采坑或者刻意深挖的話,很天然地就被忽略了。chrome

因爲在以前一次面試中,被狠狠地問了若干關於 Vary 的問題,因此想抽一些時間整理一下那些比較容易讓人忽略的知識點,算是查漏補缺吧。json

內容協商

首先須要瞭解的是內容協商這個術語。當咱們經過某個 URI 來訪問其指向的資源時,HTTP 協議能夠經過內容協商機制提供資源的不一樣的展現形式。跨域

若是缺乏服務端開發經驗話,對於這個概念可能會感到陌生,但其實咱們在工做中幾乎都會遇到它,好比在調用接口時,常常會用到 Accept: application/json 這個頭部,有時可能會用到 Accept: application/xml,這就是內容協商,前者指望接口返回 json 格式的數據,然後者指望返回 xml 格式的數據。瀏覽器

通常客戶端涉及的常見頭部有如下幾個:緩存

  • Accept: 聲明客戶端能夠處理的資源格式
  • Accept-Charset: 聲明客戶端能夠處理的字符集類型
  • Accept-Language: 聲明客戶端能夠理解的天然語言
  • Accept-Encoding: 聲明客戶端支持的編碼格式

而服務端涉及的常見頭部包括:服務器

  • Content-Type: 指示資源的 MIME 類型
  • Content-Language: 指示該資源所指望的天然語言
  • Content-Encoding: 指示資源使用該編碼格式進行內容轉換

仔細觀察的話,會發現它們其實存在着必定程度的對應關係。緣由也很簡單,既然是協商,那必然就會和兩我的在進行說話同樣,若是二者之間的對話內容沒有關聯,他們還怎麼溝通呢?客戶端和服務端進行溝通同理。app

若是想詳細瞭解該機制,能夠參考MDN的文檔,很詳細,這裏就很少說了。ide

這裏順帶說明一下,對於內容協商機制中涉及的頭部,從 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: <header-name>, <header-name>, ...

不一樣的頭部之間使用逗號進行分割,同時能夠指定 * 爲它的值,這樣等價於將資源視爲惟一,並不進行緩存,但這並非最佳實踐,所以不建議這麼作。

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 的內容,能夠參考這篇文章

最後

差很少就這麼多內容了,若有錯誤,還望指正。

參考連接

相關文章
相關標籤/搜索