最近抽空參加了幾場大廠的面試,忽然發現一個現象,就是不論面試偏服務端的職位仍是偏客戶端的職位,不論面試的 5 年以上的高級職位,仍是 3 年左右的中級職位,面試官開頭所問問題必然是關於 HTTP 的。web
我記得以前找工做的時候,彷佛都是先考察一些職位所需技能領域的基礎知識,以後再考察關於 HTTP 的東西,如今你們都將 HTTP 的問題放到面試的開頭來問,我覺的應該是愈來愈多的招聘者意識到,做爲一個 Web 開發者,HTTP 真的是過重要了,必需要先考察。面試
回想起來,這幾年我本身對於 HTTP 的學習大可能是碎片化的,不少東西沒法系統地在腦海中組織起來。雖然感受 HTTP 總體的學習難度是比較低的,可是各個知識點交雜在一塊兒又變得很複雜很難,相信你們都會有同感。同時有些知識點,若是在實際工做中沒有采坑或者刻意深挖的話,很天然地就被忽略了。chrome
因爲在以前一次面試中,被狠狠地問了若干關於 Vary 的問題,因此想抽一些時間整理一下那些比較容易讓人忽略的知識點,算是查漏補缺吧。json
首先須要瞭解的是內容協商這個術語。當咱們經過某個 URI 來訪問其指向的資源時,HTTP 協議能夠經過內容協商機制提供資源的不一樣的展現形式。跨域
若是缺乏服務端開發經驗話,對於這個概念可能會感到陌生,但其實咱們在工做中幾乎都會遇到它,好比在調用接口時,常常會用到 Accept: application/json
這個頭部,有時可能會用到 Accept: application/xml
,這就是內容協商,前者指望接口返回 json 格式的數據,然後者指望返回 xml 格式的數據。瀏覽器
通常客戶端涉及的常見頭部有如下幾個:緩存
而服務端涉及的常見頭部包括:服務器
仔細觀察的話,會發現它們其實存在着必定程度的對應關係。緣由也很簡單,既然是協商,那必然就會和兩我的在進行說話同樣,若是二者之間的對話內容沒有關聯,他們還怎麼溝通呢?客戶端和服務端進行溝通同理。app
若是想詳細瞭解該機制,能夠參考MDN的文檔,很詳細,這裏就很少說了。ide
這裏順帶說明一下,對於內容協商機制中涉及的頭部,從 web 發展歷史上來看已經沒有什麼實質的用途了,緣由以下(有興趣的話能夠閱讀這篇wiki):
Accept-Language: 大致包含如下幾點
惟一有些用途的是 Accept-Encoding,但鑑於現在大部分現代瀏覽器都已支持多種壓縮方式(常見的如 gzip、br),所以必定程度上已經不須要額外聲明這個頭部了,雖然大部分瀏覽器都會自動發送這個頭部,但其實這會形成額外 23 字節的浪費。
在理解(或者鞏固)了內容協商的概念後,就能夠介紹 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: Accept
的意思即爲,響應因請求資源格式頭部而不一樣,那麼經過相同 URI 訪問的資源就能夠根據這個頭上知道其內容格式不一樣。
但咱們已經知道,對於大部份內容協商機制中涉及的頭部,已經被看做是失敗的,那麼 Vary 和這些頭部搭配使用還有什麼意義呢?話雖如此,但 Vary 還能夠與 HTTP 中其餘的頭部來搭配使用,從而知足不少應用場景下的特殊需求,好比動態服務、防止緩存錯亂等。
如下簡單羅列一些經常使用的應用場景以及採坑指南。
關於動態服務,最多見的莫過於 Vary: User-Agent
。衆所周知,UA 是一段特徵字符串,一般包含區分客戶端類型、操做系統、版本號等信息,隨着移動 web 應用變得越流行,一個應用網站同時提供桌面和移動兩種版本的應用是很常見的事情。經過設置 Vary: User-Agent
頭部,對於搜索引擎,對於關鍵字的搜索結果能夠提供更加準確的應用版本,對於客戶端,可使其從緩存服務器獲取到相應應用類型的緩存版本,而不是錯誤地將桌面版緩存傳遞給移動版應用。
web 應用的性能在加載速度這一指標上,很大程度上取決於加載資源的大小,而圖片資源是所佔比例最大的一塊。爲了減小圖片的大小,除了對常見的圖片格式進行壓縮之外,chrome 推出的 WebP 格式也是不錯的選擇。可是這裏的問題是,不是全部的瀏覽器都支持 WebP 圖片格式的,因此這裏使用 Vary: Accept
來針對瀏覽器的支持狀況返回相應的緩存副本,支持則返回 WebP 格式,不支持則返回縮略圖或者原圖。
還有其餘關於動態服務的場景,好比要針對不一樣分辨率的屏幕加載不一樣質量的圖片(Client Hints 相關的頭部)、針對不一樣用戶身份提供不一樣的資源(Cookie頭部)等等。
有時候咱們會發現響應中存在 Vary: Accept-Encoding
頭部信息,我原先按照內容協商機制中所描述的內容來理解,但到後來才發現,其實很大程度上是爲了防止緩存錯亂的問題。
設想一下,若是沒有這個頭部,當兩個分別支持 gzip 和 不支持 gzip 的客戶端對同一份資源進行獲取時,結果會變得十分微妙。若是不支持 gzip 的客戶端先訪問,緩存代理會緩存未壓縮的版本,那麼當支持 gzip 的客戶端再訪問時,因爲命中緩存,雖然它支持 gzip 但也只能加載未壓縮的資源。反過來一樣如此,支持 gzip 客戶端先訪問,則緩存代理會緩存壓縮版本,當不支持 gzip 的客戶端再訪問時,緩存一樣命中,可是因爲它沒法對壓縮資源解碼,因此會呈現亂碼。
經過 Vary: Accept-Encoding
咱們能夠防止這種狀況的發生,由於 Vary 在這裏實際上是扮演着校驗器的角色,它會進一步對命中緩存的資源進行再校驗,若是發現頭部信息不一樣,則會將緩存資源視爲無效,從而將請求繼續轉發至源服務器。這對於緩存代理服務器也有必定的益處,由於能夠有有依據地針對不一樣的 Accept-Encoding 緩存不一樣的資源副本。
Vary 雖然能夠防止緩存錯亂,但並不表明能夠濫用,盲目的使用會拔苗助長,好比以前說起的 Vary: *
,這樣等價於將每一個請求視爲惟一,而且不緩存其響應資源,除非有意爲之,否則沒有人會犧牲緩存帶來的性能提高。
同時對於一些 Header 的值是開放性的,好比以前說起的 User-Agent,若是單純從字面量來匹配的話,衆多桌面瀏覽器的值會因各類因素而不一樣的,若是僅是簡單地將 UA 做爲區分桌面端和移動端的依據,那麼緩存命中率會達到一個很低的水平。如何解決這個問題呢?能夠將這些 UA 頭部的值進行標準化,好比能夠經過正則匹配全部桌面瀏覽器的 UA 並從新更改成 Desktop,以後再轉發至緩存代理和源服務器,這樣有利於提升緩存命中率,關於這部分的內容,能夠參考這篇文章,其中有很細緻的講解。
因此咱們要時刻留意,在使用 Vary 時,必定要根據緩存命中率做出調整,在不發生緩存錯亂的狀況之下,儘量的提升資源的緩存命中率。
對於跨域的有狀況,Vary 也包含一些內容。HTTP 協議規定,當服務端響應包含 Access-Control-Allow-Origin 頭部,且它的值是一個具體的域名而不是通配符 *
,那麼這時必需要包含 Vary: Origin
這個頭部。
爲何要包含這個頭部,由於請求頭中的 Origin 頭部表明了該請求來源的具體域名信息,那麼對於不一樣域名網站所發起的請求,會使用僅屬於它自己的緩存。通常而言,咱們不多會遇到這種問題,由於通常都將 Access-Control-Allow-Origin 設置爲了 *
,至少我本身是這樣的。若是想進一步瞭解 Vary 和 CORS 的內容,能夠參考這篇文章。
差很少就這麼多內容了,若有錯誤,還望指正。