Referrer還是Referer? 一個迷人的錯誤

詩人鄭愁予曾經在一首詩中寫道:我達達的馬蹄是個美麗的錯誤,我不是歸人,是個過客。而對我來說,十九歲之前的我,一樣是個沉浸在詩歌中的文藝少年。十九歲之後的我,作爲一名程序員,更多的是邂逅各種錯誤。可偏偏人類世界對待錯誤從來都不寬容,所以,錯誤本身既不美麗,亦不浪漫。接近中年的我,無論如何,都寫不出年輕時令人驚豔的句子,這或許和我們面對錯誤時的不同心境,有着莫大的關聯,而今天這篇博客,同樣要從一個歷史上的錯誤說起。

因拼寫而懷疑人生

話說,博主這天做了一個非常“簡單”的功能,它允許用戶通過富文本編輯器來編寫HTML,而這些HTML會被插入到頁面的特定位置,譬如用戶可以爲頁腳的備案號添加一個超鏈接,當用戶點擊備案號的時候,就可以調轉到工信部備案號查詢的網站上。這個功能非常簡單吧,因爲這就是HTML中a標籤的作用。博主快速了引入UEditor,雖然這個項目百度都不再繼續維護了,雖然它直接把跨域問題甩鍋給使用者,可我還是完成了這個功能。相信你能感受到我的不情願吧,顯然這不是重點,因爲劇情的反轉纔是……

結果沒高興多久,測試同事就同我講,客戶提供的地址填進去以後,點擊鏈接瀏覽器直接返回4XX,可明明這個地址敲到瀏覽器裏就能打開啊……我腦海中快速地浮現出那道經典的面試題,瀏覽器裏敲完地址按下回車的瞬間到底發生了什麼?習慣性懷疑人生後,我發現居然是因爲Referer的問題,從我們站點調轉到客戶站點的時候攜帶了Referer,雖然有很多種方法可以讓瀏覽器禁止攜帶Referer,但我還是被這種歷史性的錯誤搞得懷疑人生。因爲人生最難的事情,就是“揣着明白裝糊塗”和“揣着糊塗裝明白”,所謂“假作真時真亦假”。

請注意區分RefererReferrer這兩個單詞,眼尖的人會發現後者多了一個r,這有點像什麼呢,大概類似於usr和user。我們總是不情願地相信這是歷史的錯誤,而固執地想要找到一種能自圓其說的理由。誠然,“前人栽樹,後人乘涼”,可我實在不肯承認,這是一羣卓越而智慧的先驅們,所創造出的某種高效簡寫。回顧一下,使用Referer的場合,基本都是在HTTP頭部,最常見的場景就是防盜鏈,Nginx能用Referer判斷訪問者來源,爬蟲就能用Referer和UserAgent僞造訪問者身份。那什麼時候用Referrer呢?我目前發現是在a標籤的rel屬性裏,例如下面的例子:

<a rel="noreferrer" href="https://www.w3school.com.cn/tags/att_a_rel.asp">w3school</a>

除此之外,rel屬性還支持像nofollow、friend、licence這樣的屬性,詳細地大家可以參考這裏。相信大家想到博主經歷了什麼了,沒錯,我就是按照平時的書寫習慣寫了Referer,然後被Web標準委員會給瘋狂地嘲諷了。那麼,爲什麼表達同一個含義的詞會有兩種寫法?爲什麼有時候要用Referer,而有時候要用Referrer? 這特麼到底是怎麼一回事兒……帶着這些疑問,讓我們一起回顧野蠻生長的Web標準,爲什麼要埋這樣一個坑在這裏。

後世不忘,前世之鍋?

故事要追溯到上個世紀90年代,當時HTTP協議中需要有一個用來表示頁面或資源來源的請求頭部,Philip Hallam-Baker將這個請求頭部定義爲Referer,並將其寫入了RFC1945,這就是著名的HTTP/1.0協議。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-C7ZzUicn-1577601723437)(https://i.loli.net/2019/12/07/GE2WydKMf6HSk5n.png)]

然而這裏發生一件有趣的事情,這個單詞實際上是被作者給拼錯了,即正確的拼寫應該是Referrer。因爲發現這個錯誤時爲時已晚,大量的服務端和客戶端都採用了這個錯誤的拼寫,誰讓它被寫到了HTTP協議裏呢?這其中就有像Nginx裏的ngx_http_referer_module、Django裏的HttpRequest.META.HTTP_REFERER等等。考慮到這個錯誤波及的範圍過大,HTTP標準制定者奉決心將錯就錯,於是在接下來的RFC2616,即HTTP/1.1中,HTTP標準制定者追加了針對這個錯誤的說明:

HTTP/1.1協議中定義的Referer

說到這裏,大家至少明白了一件事情,這個錯誤的Referer其實是指Referrer。對於標準寫錯了這件事情,大家其實都能理解,因爲只要是人就免不了會出錯。可爲什麼不能一錯到底呢?既然要使用Referer這個錯誤的拼寫,那就一直這樣錯下去好了,爲什麼特麼又冒出來個Referrer,雖然它的拼寫的確是對的,可不統一的寫法還是會讓人抓狂啊!君不見mainmian傻傻分不清,君不見C++裏falseflase的神奇宏定義。假如沒有今天這個事情,我完全不知道還有Referrer的存在啊,可都拼錯多少年了,我都把假當作真了,你突然這樣搞,我還是會感到手足無措的啊!就像Configuration這個單詞,雖然博主英語並不算太好,可至少敢拍着胸脯說這個單詞沒寫錯,結果有次我寫對了反而讓測試給我提了Bug,因爲特麼項目裏定義的實際上是Configuation。你說,你這樣讓人崩潰不?

那麼,爲什麼會有Referrer這個正確的拼寫呢?這就要說到Referrer-Policy這個HTTP頭部。不錯,這次你沒有看錯,標準制定老爺們這次終於寫對了。顧名思義,這是一種用來告訴瀏覽器應該如何發送Referer的策略。常見的取值有:no-referrer、no-referrer-when-downgrade、origin、origin-when-cross-origin、same-origin、strict-origin、strict-origin-when-cross-origin、unsafe-url,關於它們的含義及用途,大家可以參考這裏。雖然我們經常吐槽JavaScript是一門垃圾語言,但是這一次,大家居然都非常齊心地統一了寫法,譬如DOM Level 2裏定義的 document.referrerFetch API中的Request接口的referrer屬性等,這一次都寫對了。而Referrer-Policy除了和JavaScript可以集成以外,同樣可以和HTML、CSS集成。博主一開始遇到的問題,實際上就是和HTML集成的一個場景。

//meta標籤裏的'referrer'
<meta name="referrer" content="origin">
//出現在a, area, img, iframe, script, <link>等元素裏的'referrer'
<a href="http://example.com" referrerpolicy="origin">
//出現在a, area, link等標籤的rel屬性裏的'referrer'
<a href="http://example.com" rel="noreferrer">

而和CSS集成實際上就是style標籤中的referrerpolicy屬性,它默認是no-referrer-when-downgrade,我們可以在返回一個CSS文件的時候設置響應流的Referrer-Policy,或者是設置style標籤中的referrerpolicy屬性,這個就不展開講啦!

本文小結

通過這次被標準制定者按在地上摩擦的經歷,居然無意中收穫了這樣一段"迷人"的歷史。假如JavaScript這裏爲了兼容歷史錯誤而使用Referer的話,可能博主就不會一邊吐槽這個錯誤,一邊又乖乖地滾去讀RFC2616。從這裏可以得出一個結論:HTTP 請求中的 Referer 是一個典型的拼寫錯誤,歷史悠久,可以預見還會一直錯下去,以後 Referer 變成一個專有名詞也說不定。所以一般涉及到讀取 HTTP 請求頭的場景,我們需要用 Referer 這種錯誤拼寫(後端);除此之外一般都要用 Referrer 這種正確的拼寫(前端)。有人說,使用JavaScript開發同構應用的體驗非常好,恐怕從今天這篇博客以後要打個折扣,因爲你剛剛在後端寫完referer,轉眼就要在前端寫referrer,希望像博主這樣的僞全棧工程師不會因此而精神分裂。實用主義者能用就行的策略,讓這個錯誤在很多年以後還被人提起,假如這些標準制定者尚在人世的話,不知道會不會在瀏覽網頁的時候,想起第一次起草RFC1945的那個下午。果然,歷史還真是迷人啊!

發佈了247 篇原創文章 · 獲贊 913 · 訪問量 206萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章