1. 程式人生 > >HTTP 的前世今生,那些不為人知的祕密

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

> 每個時代,都不會虧待會學習的人。 大家好,我是 yes。 HTTP 協議在當今的網際網路可謂是隨處可見,一直默默的在背後支援著網路世界的執行,對於我們程式設計師來說 HTTP 更是熟悉不過。 平日裡我們都說架構是演進的,需求推動著技術的迭代、更新和進步,對於 HTTP 協議來說也是如此。 不知你是否有想過 HTTP 協議是如何誕生的,一開始是怎樣的,又是怎麼一步一步發展到今天的 HTTP/3 ? **其中經歷了哪些不為人知的祕密?** 今天我就想和大家一起來看一看 HTTP 的演進之路,來看看它是如何從一個小寶寶成長為現在統治網際網路的存在。 不過在此之前,我們先簡單的看看網際網路的始祖-阿帕網的一段小歷史,還是很有趣的。 ## 網際網路的始祖-阿帕網 在 1950 年代,通訊研究者們認識到不同計算機使用者和網路之間的需要通訊,這促使了分散式網路、排隊論和封包互動的研究。 在1958 年2月7日,美國國防部長尼爾 · 麥克爾羅伊釋出了國防部 5105.15 號指令,建立了高階研究計劃局(ARPA) 。 ![](https://upload-images.jianshu.io/upload_images/16034279-b7783db50fe681b4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ARPA 的核心機構之一 IPTO(資訊處理處)贊助的一項研究導致了阿帕網的開發。 我們來看看這段歷史。 在 1962 年,ARPA 的主任聘請約瑟夫·利克萊德擔任 IPTO 的第一任主任,他是最早預見到現代互動計算及其在各種應用的人之一。 IPTO 資助了先進的計算機和網路技術的研究,並委託十三個研究小組對人機互動和分散式系統相關技術進行研究。**每個小組獲得的預算是正常研究補助金的三十至四十倍。** 這就是財大氣粗啊,研究人員肯定是幹勁十足! 在 1963 年利克萊德資助了一個名為 MAC 的研究專案,**該專案旨在探索在分時計算機上建立社群的可能性**。 這個專案對 IPTO 和更廣泛的研究界產生了持久的影響,成為廣泛聯網的原型。 並且利克萊德的全球網路願景極大地影響了他在 IPTO 的繼任者們。 1964 年利克萊德跳槽到了 IBM,第二任主任薩瑟蘭上線,他建立了革命性的 Sketchpad 程式,用於儲存計算機顯示器的記憶體,在 1965 年他與麻省理工學院的勞倫斯 · 羅伯茨簽訂了 IPTO 合同,以進一步發展計算機網路技術。 隨後,羅伯茨和托馬斯 · 梅里爾在麻省理工學院的 TX-2 計算機和加利福尼亞的 Q-32 計算機之間,**通過撥號電話連線實現了第一個資料包交換**。 1966 年第三任主任鮑勃 · 泰勒上任,他深受利克萊德的影響,巧的是泰勒和利克萊德一樣也是個心理聲學家。 ![](https://upload-images.jianshu.io/upload_images/16034279-e522fd46bc40a7b6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在泰勒的 IPTO 辦公室裡有三個不同的終端連線到三個不同的研究站點,他意識到這種架構將嚴重限制他擴充套件訪問多個站點的能力。 於是他想著把一個終端連線到一個可以訪問多個站點的網路上,並且從他在五角大樓的職位來說,他有這個能力去實現這個願景。 美國國防部高階研究計劃局局長查理 · 赫茨菲爾德向泰勒承諾,如果 IPTO 能夠組織起來,他將提供 100 萬美元用於建立一個分散式通訊網路。 泰勒一聽舒服了,然後他對羅伯茨的工作印象很深刻,邀請他加入並領導這項工作,然後羅伯茨卻不樂意。 泰勒不高興了,於是要求赫茨菲爾德**讓林肯實驗室的主任向羅伯茨施壓,要求他重新考慮**,這最終促使羅伯茨緩和了態度,於1966年12月加入 IPTO 擔任首席科學家。 ![](https://upload-images.jianshu.io/upload_images/16034279-37819d9a34a4e5bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 在 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)、第一個網頁瀏覽器、第一個網頁伺服器和第一個網站。 ![](https://upload-images.jianshu.io/upload_images/16034279-f0c986ce9120d0fe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 至此,網際網路開啟了快速發展之路,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 工作組,他希望通過擴充套件操作、擴充套件協商、更豐富的元資訊以及與安全協議相關的安全協議來擴充套件協議,這種安全協議通過新增額外的方法和頭欄位來提高效率。 ![](https://upload-images.jianshu.io/upload_images/16034279-f8778d1dd0b69140.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 所以在 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 頭就可以判斷你到底是要訪問哪個主機。 ![](https://upload-images.jianshu.io/upload_images/16034279-63854b4cd2d7503f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 可以看到瀏覽器大戰推進了 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 無動於衷。 這就是人的惰性,也符合平日裡我們對產品的演進,當你足夠強大又安逸的時候,任何的改動你是不想理會的。 **別用咯。** ![](https://upload-images.jianshu.io/upload_images/16034279-8e23657693e7e0d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 這時候 Google 看不下去了,你不搞是吧?我自己搞我的,我自己和我自己玩,我使用者群體大,我有 Chrome,我服務多了去了。 Google 推出了 SPDY 協議,憑藉著它全球的佔有率超過了 60% 的底氣,2012年7月,開發 SPDY 的小組公開表示,它正在努力實現標準化。 HTTP 坐不住了,之後網際網路標準化組織以 SPDY 為基礎開始制定新版本的 HTTP 協議,最終在 2015 年釋出了 HTTP/2。 HTTP/2 版本主要增加以下幾點: - 是二進位制協議,不再是純文字。 - 支援一個 TCP 連線發起多請求,移除了 pipeline。 - 利用 HPACK 壓縮頭部,減少資料傳輸量。 - 允許服務端主動推送資料。 **從文字到二進位制**其實簡化了整齊的複雜性,解析資料的開銷更小,資料更加緊湊,減少了網路的延遲,提升了整體的吞吐量。 ![](https://upload-images.jianshu.io/upload_images/16034279-37112ff1c687ed90.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **支援一個 TCP 連線發起多請求**,即支援多路複用,像 HTTP/1.1 pipeline 還是有阻塞的情況,**需要等前面的一個響應返回了後面的才能返回**。 而多路複用就是完全非同步化,這減少了整體的往返時間(RTT),**解決了 HTTP 隊頭阻塞問題,也規避了 TCP 慢啟動帶來的影響**。 **HPACK 壓縮頭部**,採用了靜態表、動態表和哈夫曼編碼,在客戶端和伺服器都維護請求頭的列表,所以只需要增量和壓縮過的頭部資訊,服務端拿到之後組裝一下就能得到完整的頭部資訊。 形象一點就是如下圖所示: ![](https://upload-images.jianshu.io/upload_images/16034279-e819942e07aa6429.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 再具體一點就是下圖這樣: ![](https://upload-images.jianshu.io/upload_images/16034279-f65544507a82d6f9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) **服務端主動推送資料**,這個其實就是減少了請求的次數,比如客戶端請求 1.html,我把 1.html 需要的 js 和 css 也一塊送過去,省的之後客戶端再請求我要 js ,我要這個 css。 可以看到 HTTP/2 的整體演進都是往效能優化的角度發展,因為此時的效能就是痛點,**任何東西的演進都是哪裡痛醫哪裡。** 當然有一些例外,比如一些意外,或者就是“閒的蛋疼”的那種捯飭。 這次推進屬於使用者揭竿而起為之,你再不給我升級我自己搞了,我有著資本,你自己掂量。 最終結果是好的,Google 後來放棄了 SPDY ,擁抱標準,而 HTTP/1.1 這個歷史包袱太重了,所以 HTTP/2 到現在也只有大致一半的網站使用它。 ![](https://upload-images.jianshu.io/upload_images/16034279-9c861c693d5637c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 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疑難雜症解析](https://mp.weixin.qq.com/s/DTUswaTsoUOCv4JL-t-mJg)不瞭解的同學可以去看看。 簡單的說就是 TCP 太無私了,或者說太保守了,現在需要一種更激進的做法。 那怎麼搞? TCP 改不動我就換!然後把 TCP 可靠、有序的功能提到應用層來實現,因此 Google 就研究出了 QUIC 協議。 ![](https://upload-images.jianshu.io/upload_images/16034279-5911790efe742a52.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) QUIC 層來實現自己的丟包重傳和擁塞控制,還有出於安全的考慮我們都會用 HTTPS ,所以需要多次握手。 ![](https://upload-images.jianshu.io/upload_images/16034279-7fbfd9406d4cf240.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 上面我也已經提到了關於四元組的情況,所以在移動網際網路時代這握手的消耗就更加放大了,於是 QUIC 引入了個叫 Connection ID 來標識一個連結,所以切換網路之後可以複用這個連線,達到 0 RTT 就能開始傳輸。 ![](https://upload-images.jianshu.io/upload_images/16034279-346d309c06855f02.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 注意上圖是在已經和服務端握過手之後的,由於網路切換等原因才有 0 RTT ,**也就是 Connection ID 在之前生成過了**。 如果是第一次建連還是需要多次握手的,我們來看一下**簡化**的握手對比圖。 ![](https://upload-images.jianshu.io/upload_images/16034279-0ee1f65adc98188a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 所以所謂的 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協議 ,羅劍鋒*