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協議 ,羅劍鋒