HTTP 的前世今生,那些不為人知的祕密

yes的練級攻略發表於2020-09-29

每個時代,都不會虧待會學習的人。

大家好,我是 yes。

HTTP 協議在當今的網際網路可謂是隨處可見,一直默默的在背後支援著網路世界的執行,對於我們程式設計師來說 HTTP 更是熟悉不過。

平日裡我們都說架構是演進的,需求推動著技術的迭代、更新和進步,對於 HTTP 協議來說也是如此。

不知你是否有想過 HTTP 協議是如何誕生的,一開始是怎樣的,又是怎麼一步一步發展到今天的 HTTP/3 ?

其中經歷了哪些不為人知的祕密?

今天我就想和大家一起來看一看 HTTP 的演進之路,來看看它是如何從一個小寶寶成長為現在統治網際網路的存在。

不過在此之前,我們先簡單的看看網際網路的始祖-阿帕網的一段小歷史,還是很有趣的。

網際網路的始祖-阿帕網

在 1950 年代,通訊研究者們認識到不同計算機使用者和網路之間的需要通訊,這促使了分散式網路、排隊論和封包互動的研究。

在1958 年2月7日,美國國防部長尼爾 · 麥克爾羅伊釋出了國防部 5105.15 號指令,建立了高階研究計劃局(ARPA) 。

ARPA 的核心機構之一 IPTO(資訊處理處)贊助的一項研究導致了阿帕網的開發。

我們來看看這段歷史。

在 1962 年,ARPA 的主任聘請約瑟夫·利克萊德擔任 IPTO 的第一任主任,他是最早預見到現代互動計算及其在各種應用的人之一。

IPTO 資助了先進的計算機和網路技術的研究,並委託十三個研究小組對人機互動和分散式系統相關技術進行研究。每個小組獲得的預算是正常研究補助金的三十至四十倍。

這就是財大氣粗啊,研究人員肯定是幹勁十足!

在 1963 年利克萊德資助了一個名為 MAC 的研究專案,該專案旨在探索在分時計算機上建立社群的可能性

這個專案對 IPTO 和更廣泛的研究界產生了持久的影響,成為廣泛聯網的原型。

並且利克萊德的全球網路願景極大地影響了他在 IPTO 的繼任者們。

1964 年利克萊德跳槽到了 IBM,第二任主任薩瑟蘭上線,他建立了革命性的 Sketchpad 程式,用於儲存計算機顯示器的記憶體,在 1965 年他與麻省理工學院的勞倫斯 · 羅伯茨簽訂了 IPTO 合同,以進一步發展計算機網路技術。

隨後,羅伯茨和托馬斯 · 梅里爾在麻省理工學院的 TX-2 計算機和加利福尼亞的 Q-32 計算機之間,通過撥號電話連線實現了第一個資料包交換

1966 年第三任主任鮑勃 · 泰勒上任,他深受利克萊德的影響,巧的是泰勒和利克萊德一樣也是個心理聲學家。

在泰勒的 IPTO 辦公室裡有三個不同的終端連線到三個不同的研究站點,他意識到這種架構將嚴重限制他擴充套件訪問多個站點的能力。

於是他想著把一個終端連線到一個可以訪問多個站點的網路上,並且從他在五角大樓的職位來說,他有這個能力去實現這個願景。

美國國防部高階研究計劃局局長查理 · 赫茨菲爾德向泰勒承諾,如果 IPTO 能夠組織起來,他將提供 100 萬美元用於建立一個分散式通訊網路。

泰勒一聽舒服了,然後他對羅伯茨的工作印象很深刻,邀請他加入並領導這項工作,然後羅伯茨卻不樂意。

泰勒不高興了,於是要求赫茨菲爾德讓林肯實驗室的主任向羅伯茨施壓,要求他重新考慮,這最終促使羅伯茨緩和了態度,於1966年12月加入 IPTO 擔任首席科學家。

在 1968 年6月3日,羅伯茨向泰勒描述了建立阿帕網的計劃,18 天后,也就是 6 月 21 日,泰勒批准了這個計劃,14 個月後阿帕網建立

當阿帕網順利發展時,泰勒於 1969 年9月將 IPTO 的管理權移交給羅伯茨。

隨後羅伯茨離開 ARPA 成為 Telenet 的 CEO ,而利克萊德再次回到 IPTO 擔任董事,以完成該組織的生命週期。

至此,這段歷史暫告一段落,可以看到阿帕網之父羅伯茨還是被施壓的才接受這項任務,最終建立了阿帕網,網際網路的始祖

也多虧了利克萊德的遠見和砸錢促進了技術的發展,ARPA 不僅成為網路誕生地,同樣也是電腦圖形、平行過程、計算機模擬飛行等重要成果的誕生地。

歷史就是這麼的巧合和有趣。

網際網路的歷史

在 1973 年 ARPA 網擴充套件成網際網路,第一批接入的有英國和挪威計算機,逐漸地成為網路連線的骨幹。

1974 年 ARPA 的羅伯特·卡恩和史丹佛的文頓·瑟夫提出TCP/IP 協議。

1986 年,美國國家科學基金會(National Science Foundation,NSF)建立了大學之間互聯的骨幹網路 NSFNET ,這是網際網路歷史上重要的一步,NSFNET 成為新的骨幹,1990 年 ARPANET 退役。

在 1990 年 ,蒂姆·伯納斯-李(下文我就稱李老) 建立了執行全球資訊網所需的所有工具:超文字傳輸協議(HTTP)、超文字標記語言(HTML)、第一個網頁瀏覽器、第一個網頁伺服器和第一個網站。

至此,網際網路開啟了快速發展之路,HTTP 也開始了它的偉大征途。

還有很多有趣的歷史,比如第一次瀏覽器大戰等等,之後有機會再談,今天我們的主角是 HTTP。

接下來我們就看看 HTTP 各大版本的演進,來看看它是如何成長到今天這個樣子的。

HTTP / 0.9 時代

在 1989 年,李老發表了一篇論文,文中提出了三項現在看來很平常的三個概念。

  • URI,統一資源識別符號,作為網際網路上的唯一標識。
  • HTML,超文字標記語言,描述超文字。
  • HTTP ,超文字傳輸協議,傳輸超文字。

隨後李老就付之於行動,把這些都搞出來了,稱之為全球資訊網(World Wide Web)。

那時候是網際網路初期,計算機的處理能力包括網速等等都很弱,所以 HTTP 也逃脫不了那個時代的約束,因此設計的非常簡單,而且也是純文字格式

李老當時的想法是文件存在伺服器裡面,我們只需要從伺服器獲取文件,因此只有 “GET”,也不需要啥請求頭,並且拿完了就結束了,因此請求響應之後連線就斷了

這就是為什麼 HTTP 設計為文字協議,並且一開始只有“GET”、響應之後連線就斷了的原因了。

在我們現在看來這協議太簡陋了,但是在當時這是網際網路發展的一大步!一個東西從無到有是最困難的

這時候的 HTTP 還沒有版本號的,之所以稱之為 HTTP / 0.9 是後人加上去了,為了區別之後的版本。

HTTP 1.0 時代

人們的需求是無止盡的,隨著影像和音訊的發展,瀏覽器也在不斷的進步予以支援。

在 1995 年又開發出了 Apache,簡化了 HTTP 伺服器的搭建,越來越多的人用上了網際網路,這也促進了 HTTP 協議的修改。

需求促使新增各種特性來滿足使用者的需求,經過了一系列的草案 HTTP/1.0 於 1996 年正式釋出。

Dave Raggett 在1995年領導了 HTTP 工作組,他希望通過擴充套件操作、擴充套件協商、更豐富的元資訊以及與安全協議相關的安全協議來擴充套件協議,這種安全協議通過新增額外的方法和頭欄位來提高效率。

所以在 HTTP/1.0 版本主要增加以下幾點:

  • 增加了 HEAD、POST 等新方法。
  • 增加了響應狀態碼。
  • 引入了頭部,即請求頭和響應頭。
  • 在請求中加入了 HTTP 版本號。
  • 引入了 Content-Type ,使得傳輸的資料不再限於文字。

可以看到引入了新的方法,填充了操作的語義,像 HEAD 還可以只拿元資訊不必傳輸全部內容,提高某些場景下的效率。

引入的響應狀態碼讓請求方可以得知服務端的情況,可以區分請求出錯的原因,不會一頭霧水。

引入了頭部,使得請求和響應更加的靈活,把控制資料和業務實體進行了拆分,也是一種解耦。

新增了版本號表明這是一種工程化的象徵,說明走上了正途,畢竟沒版本號無法管理。

引入了 Content-Type,支援傳輸不同型別的資料,豐富了協議的載體,充實了使用者的眼球。

但是那時候 HTTP/1.0 還不是標準,沒有實際的約束力,各方勢力不吃這一套,大白話就是你算老幾。

HTTP 1.1 時代

HTTP/1.1 版本在 1997 的 RFC 2068 中首次被記錄,從 1995 年至 1999 年間的第一次瀏覽器大戰,極大的推動了 Web 的發展。

隨著發展 HTTP/1.0 演進成了 HTTP/1.1,並且在 1999 年廢棄了之前的 RFC 2068,釋出了 RFC 2616。

從版本號可以得知這是一個小版本的更新,更新主要是因為 HTTP/1.0 很大的效能問題,就是每請求一個資源都得新建一個 TCP 連線,而且只能序列請求。

所以在 HTTP/1.1 版本主要增加以下幾點:

  • 新增了連線管理即 keepalive ,允許持久連線。
  • 支援 pipeline,無需等待前面的請求響應,即可傳送第二次請求。
  • 允許響應資料分塊(chunked),即響應的時候不標明Content-Length,客戶端就無法斷開連線,直到收到服務端的 EOF ,利於傳輸大檔案。
  • 新增快取的控制和管理。
  • 加入了 Host 頭,用在你一臺機子部署了多個主機,然後多個域名解析又是同一個 IP,此時加入了 Host 頭就可以判斷你到底是要訪問哪個主機。

可以看到瀏覽器大戰推進了 Web 的發展,也暴露出 HTTP/1.0 的不足之處,畢竟網路頻寬等等都在進步,總不能讓協議限制了硬體的發展。

因此提出了 HTTP/1.1 ,主要是為了解決效能的問題,包括支援持久連線、pipeline、快取管理等等,也新增了一些特性。

再後來到 2014 年對 HTTP/1.1 又做了一次修訂,因為其太過龐大和複雜,因此進行了拆分,弄成了六份小文件 RFC7230 - RFC7235

這時候 HTTP/1.1 已經成了標準,其實標準往往是在各大強力競爭對手相對穩定之後建立的,因為標準意味著統一,統一就不用費勁心思去相容各種玩意。

只有強大的勢力才能定標準,當你足夠強大的時候你也可以定標準,去挑戰老標準。

HTTP 2 時代

隨著 HTTP/1.1 的釋出,網際網路也開始了爆發式的增長,這種增長暴露出 HTTP 的不足,主要還是效能問題,而 HTTP/1.1 無動於衷。

這就是人的惰性,也符合平日裡我們對產品的演進,當你足夠強大又安逸的時候,任何的改動你是不想理會的。

別用咯。

這時候 Google 看不下去了,你不搞是吧?我自己搞我的,我自己和我自己玩,我使用者群體大,我有 Chrome,我服務多了去了。

Google 推出了 SPDY 協議,憑藉著它全球的佔有率超過了 60% 的底氣,2012年7月,開發 SPDY 的小組公開表示,它正在努力實現標準化。

HTTP 坐不住了,之後網際網路標準化組織以 SPDY 為基礎開始制定新版本的 HTTP 協議,最終在 2015 年釋出了 HTTP/2。

HTTP/2 版本主要增加以下幾點:

  • 是二進位制協議,不再是純文字。
  • 支援一個 TCP 連線發起多請求,移除了 pipeline。
  • 利用 HPACK 壓縮頭部,減少資料傳輸量。
  • 允許服務端主動推送資料。

從文字到二進位制其實簡化了整齊的複雜性,解析資料的開銷更小,資料更加緊湊,減少了網路的延遲,提升了整體的吞吐量。

支援一個 TCP 連線發起多請求,即支援多路複用,像 HTTP/1.1 pipeline 還是有阻塞的情況,需要等前面的一個響應返回了後面的才能返回

而多路複用就是完全非同步化,這減少了整體的往返時間(RTT),解決了 HTTP 隊頭阻塞問題,也規避了 TCP 慢啟動帶來的影響

HPACK 壓縮頭部,採用了靜態表、動態表和哈夫曼編碼,在客戶端和伺服器都維護請求頭的列表,所以只需要增量和壓縮過的頭部資訊,服務端拿到之後組裝一下就能得到完整的頭部資訊。

形象一點就是如下圖所示:

再具體一點就是下圖這樣:

服務端主動推送資料,這個其實就是減少了請求的次數,比如客戶端請求 1.html,我把 1.html 需要的 js 和 css 也一塊送過去,省的之後客戶端再請求我要 js ,我要這個 css。

可以看到 HTTP/2 的整體演進都是往效能優化的角度發展,因為此時的效能就是痛點,任何東西的演進都是哪裡痛醫哪裡。

當然有一些例外,比如一些意外,或者就是“閒的蛋疼”的那種捯飭。

這次推進屬於使用者揭竿而起為之,你再不給我升級我自己搞了,我有著資本,你自己掂量。

最終結果是好的,Google 後來放棄了 SPDY ,擁抱標準,而 HTTP/1.1 這個歷史包袱太重了,所以 HTTP/2 到現在也只有大致一半的網站使用它。

HTTP 3 時代

這 HTTP/2 還沒捂熱, HTTP/3 怎麼就來了?

這次又是 Google,它自己突破自己,主要也是源自於痛點,這次的痛點來自於 HTTP 依賴的 TCP。

TCP 是面向可靠的、有序的傳輸協議,因此會有失敗重傳和按序機制,而 HTTP/2 是所有流共享一個 TCP 連線,所以會有 TCP 層面的隊頭阻塞,當發生重傳時會影響多個請求響應。

並且 TCP 是基於四元組(源IP,源埠,目標IP,目標埠)來確定連線的,而在行動網路的情況下 IP 地址會頻繁的換,這會導致反覆的建連。

還有 TCP 與 TLS 的疊加握手,增加了延時。

問題就出在 TCP 身上,所以 Google 就把目光瞄向了 UDP。

UDP 我們知道是無連線的,不管什麼順序,也不管你什麼丟包,而 TCP 我在之前的文章說的很清楚了TCP疑難雜症解析不瞭解的同學可以去看看。

簡單的說就是 TCP 太無私了,或者說太保守了,現在需要一種更激進的做法。

那怎麼搞? TCP 改不動我就換!然後把 TCP 可靠、有序的功能提到應用層來實現,因此 Google 就研究出了 QUIC 協議。

QUIC 層來實現自己的丟包重傳和擁塞控制,還有出於安全的考慮我們都會用 HTTPS ,所以需要多次握手。

上面我也已經提到了關於四元組的情況,所以在移動網際網路時代這握手的消耗就更加放大了,於是 QUIC 引入了個叫 Connection ID 來標識一個連結,所以切換網路之後可以複用這個連線,達到 0 RTT 就能開始傳輸。

注意上圖是在已經和服務端握過手之後的,由於網路切換等原因才有 0 RTT ,也就是 Connection ID 在之前生成過了

如果是第一次建連還是需要多次握手的,我們來看一下簡化的握手對比圖。

所以所謂的 0RTT 是在之前已經建連的情況下。

當然還有 HTTP/2 提到的 HPACK,這個是依賴 TCP 的可靠、有序傳輸的,於是 QUIC 得搞了個 QPACK,也採用了靜態表、動態表和哈夫曼編碼。

它豐富了 HTTP/2 的靜態表,從 61 項加到了 98 項。

上面提到的動態表,是用來儲存未包含在靜態表中的頭部項,假設動態表還未收到,後面來解頭部的時候肯定要被阻塞的。

所以 QPACK 就另開一條路,在單向的 Stream 裡傳輸動態表的編解碼,單向傳輸好了,接受端到才能開始解碼,也就是說還沒好你就先別管,防止做一半卡住了

那還有前面提到的 TCP 隊頭阻塞, QUIC 是怎麼解決的呢?畢竟它也要保證有序和可靠啊。

因為 TCP 不認識每個流分別是哪個請求的,所以它只能全部阻塞住,而 QUIC 知道,因此比如請求 A 丟包了,我就把 A 卡住了就行,請求 B 完全可以全部放行,絲毫不受影響。

可以看到基於 UDP 的 QUIC 還是很強大的,而且人家使用者多,在 2018 年,網際網路標準化組織 IETF 提議將 HTTP over QUIC 更名為 HTTP/3 並獲得批准

可以看到需求又推動技術的進步,由於 TCP 自身機制的限制,我們的目光已經往 UDP 上靠了,那 TCP 會不會成為歷史呢?

我們拭目以待。

最後

今天我們大致過了一遍 HTTP 發展的歷史和它的演進之路,可以看到技術是源於需求,需求推動著技術的發展。

本質上就是人的惰性,只有痛了才會成長

而且標準其實也是巨頭們為了他們的利益推動的,不過標準確實能減輕對接的開銷,統一而方便。

當然就 HTTP 來說還是有很多內容的,有很多細節,很多演算法,比如拿 Connection ID 來說,不同的四元組你如何保證請求一定會轉發到之前的伺服器上?

所以今天我只是淺顯的談了談大致的演進,具體的實現還是得靠各位自己摸索,或者之後有機會我再寫一些。

不過相對於這些實現細節我更感興趣的是歷史的演進,這能讓我從時代背景等一些約束來得知,為什麼這東西一開始是這麼設計的,從而更深刻的理解這玩意。

而且歷史還是很有趣的,不是麼?


我是 yes,從一點點到億點點,我們下篇見

巨人的肩膀

https://www.livinginternet.com/i/ii_ipto.htm

https://jacobianengineering.com/blog/2016/11/1543/

https://w3techs.com/technologies/details/ce-http2

https://www.verizondigitalmedia.com/blog/how-quic-speeds-up-all-web-applications/

https://www.oreilly.com/content/http2-a-new-excerpt/

https://www.darpa.mil/about-us/timeline/dod-establishes-arpa

https://en.wikipedia.org/wiki/ARPANET

https://en.wikipedia.org/wiki/Internet

深入剖析HTTP/3協議 ,陶輝

透視HTTP協議 ,羅劍鋒

相關文章