一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

大話HTTP協(xié)議前世今生

馬哥Linux運維 ? 來源:taoshu.in ? 2023-02-07 16:03 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

HTTP 全稱 Hypertext Transfer Protocol,中文是超文本傳輸協(xié)議。網(wǎng)上講 HTTP 協(xié)議的資料可以說是五花八門,但大多數(shù)都在羅列 HTTP 協(xié)議具體的規(guī)定,很少有講 HTTP 協(xié)議這樣設(shè)計的原因。今天我就嘗試從解決問題的角度分析 HTTP 協(xié)議主要特性,希望能幫助大家快速理解 HTTP 協(xié)議。

HTTP 是一種通過網(wǎng)絡(luò)傳輸數(shù)據(jù)的協(xié)議。我們不希望數(shù)據(jù)在傳輸?shù)倪^程中出現(xiàn)丟失或者損壞的問題。所以 HTTP 選用 TCP 作為底層網(wǎng)絡(luò)協(xié)議,因為 TCP 是一種可靠的傳輸層協(xié)議。

通信雙方就建立 TCP 連接后立馬發(fā)現(xiàn)一個新問題:服務(wù)端要給客戶端發(fā)送什么數(shù)據(jù)呢?所以客戶端必需在連接建立后將自己想要的內(nèi)容發(fā)送給服務(wù)端,這就是所謂的「請求」,也就 HTTP Request。由此就確立了 HTTP 協(xié)議最根本的設(shè)計,即由客戶端主導(dǎo)的請求應(yīng)答式協(xié)議。

客戶端上來就給服務(wù)端發(fā)了一個「請求」。但服務(wù)端有可能收到的內(nèi)容跟客戶端并不完全一樣。等等,TCP不是可靠傳輸協(xié)議嗎?接收到的數(shù)據(jù)怎么會不一樣?這就涉及到數(shù)據(jù)分段的問題。比如客戶端發(fā)送”abcdef”,底層 TCP 協(xié)議可能分兩次傳輸 “abc” 和 “def”,也可能分好多次傳輸。不論分幾次,它們的順序是固定的,跟客戶端發(fā)送的順序完全一致。服務(wù)端可能會收到多段數(shù)據(jù),所以服務(wù)端需要把收到的數(shù)據(jù)「攢」起來,等到客戶端的數(shù)據(jù)全部收到之后才能看到客戶端「請求」的全貌。

那到什么時候算全部收到呢?這是 TCP 通信的一個基本問題。解決這個問題有兩個流派:長度流和分隔符流。

所謂長度流就是在實際發(fā)送數(shù)據(jù)之前,先發(fā)送數(shù)據(jù)的長度。服務(wù)端先讀取長度信息,然后再根據(jù)長度來「攢」后面的數(shù)據(jù)。那服務(wù)端在讀取長度的時候不會碰到分段問題嗎?其實不會,因為 TCP 只會對比較長的數(shù)據(jù)做分段。前面說的”abcdef”分兩段只是一種極端的例子,實際上很難發(fā)生。所以,只要先發(fā)送的長度數(shù)據(jù)不要太長,服務(wù)端就能一次性收到。退一步,即便是真的會分段,這類長度流協(xié)議都會規(guī)定長度數(shù)據(jù)自身的長度。比如用兩個字節(jié)表示長度,那范圍就是數(shù)據(jù)長度的范圍就是0-65535。服務(wù)端可以先收兩個字節(jié),然后再根據(jù)數(shù)據(jù)長度來接收后面的內(nèi)容。

長度流最大的優(yōu)點就是實現(xiàn)簡單,內(nèi)存效率高,服務(wù)端不用事先分配很多內(nèi)存。但缺點也比較突出,長度的范圍不夠靈活。如果我們規(guī)定長度字段為兩個字節(jié),但就不能傳輸超過64k的數(shù)據(jù)。但如果規(guī)定長度字段為八個字節(jié),那在傳輸比較短的數(shù)據(jù)時就造成浪費。如何設(shè)置最優(yōu)長度字段,大家可以參考我的另一篇文章。

此外,長度流的擴展性也比較差。如果我們想在長度之外傳輸其他信息,比如數(shù)據(jù)類型、版本號之類,我們都需要提前規(guī)定好這些數(shù)據(jù)的長度。長度一旦定好,以后就很難擴展了。最典型的長度流協(xié)議就是 IP 報文。有興趣的朋友可以去看看 IP 協(xié)議是怎么規(guī)定數(shù)據(jù)長度的。

有鑒于長度流的不足,人們又搞出了分割符流。簡單來說就是用一個特殊的分割符表示數(shù)據(jù)的結(jié)尾。最經(jīng)典的例子就是C語言的字符串,結(jié)尾用?來表示。使用這個流派的服務(wù)端程序要不停地從客戶端接收數(shù)據(jù),直到收到某一個分割符,就表明已經(jīng)收到了完整的「請求」。

因為不需要事先指定數(shù)據(jù)的長度,所以分割符流派一下子就解決了長度流長度范圍不靈活的問題。分割符流派的協(xié)議可以接收任意長度的數(shù)據(jù)。但是,分割符流派為些也付出了代價。因為長度不固定,服務(wù)端必須分配比較大的內(nèi)存或者多次動態(tài)分配內(nèi)存,這會產(chǎn)生比較大的資源消耗。惡意用戶可能通過構(gòu)造很長的數(shù)據(jù)來占滿服務(wù)器的內(nèi)存。

但是 HTTP 協(xié)議還是加入了這個流派,它用的分割符是 。這里的 表示回車,就是讓打印機把打印頭回到最左邊的位置。 表示換行,就是讓打印機把紙向上挪一行,準備打印新的實符。上古時代的電腦沒用現(xiàn)在的液晶屏,用電傳打印機來「顯示」內(nèi)容,所以需要傳輸 兩個字符?,F(xiàn)在這些都淘汰了,理論上用 也可以,像 Nginx 就支持只用 。

所以,一個最簡單的 HTTP 請求長這個樣子:

GET/mypage.html

這里的GET是一種擬人的說法,從服務(wù)拿什么東西。這也是 HTTP 語義化設(shè)計的開端(所謂語義化就是普通人能看懂)。后面跟一個空格,再后面是文件的路徑。最后是分割符 。因為最后是 ,所以上面的數(shù)據(jù)也叫請求行(request line)。

客戶端跟服務(wù)器建立連接后就立即發(fā)送上面的數(shù)據(jù)。服務(wù)端等收到 后開始解析,也就是把/mypage.html提取出來,然后找到對應(yīng)的文件,把文件內(nèi)容發(fā)送給客戶端。

到這里,客戶端就收到了服務(wù)端發(fā)送的文件內(nèi)容,也叫「響應(yīng)」。但是,客戶端馬上面臨服務(wù)端同樣的問題:如何確定已經(jīng)收到了 mypage.html 的完整的內(nèi)容呢?服務(wù)端要不要在最后發(fā)送分割符 呢?不能!因為 mypage.html 的內(nèi)容里本身就可能包含 。如果客戶端還是以 當(dāng)作結(jié)束標記,那可能會丟失數(shù)據(jù)。

為此 Tim Berners-Lee (HTTP 協(xié)議之父) 采用了更簡單的辦法——關(guān)閉連接。也就是說,服務(wù)器在傳輸完成之后要主動關(guān)閉 TCP 連接,這樣客戶端就明確知道所有的內(nèi)容已經(jīng)傳輸完成了。

以上就是最原始的 HTTP 協(xié)議,大約在1990發(fā)布?,F(xiàn)在稱這個時代的 HTTP 協(xié)議為 HTTP/0.9,主要是跟后面標準化之后的 1.x 進行區(qū)分。就這樣,萬維網(wǎng)的時代開啟了。

HTTP/0.9 發(fā)布后得到了廣泛的應(yīng)用。但它的功能太簡單了,所以很多瀏覽器都在它的基礎(chǔ)上做了擴展。最主要的擴展功能有如下幾個:

添加版本信息

添加擴展頭信息

添加返回狀態(tài)信息

添加版本信息是為了方便客戶端和服務(wù)端相互識別,這樣才能開啟擴展功能。添加之后的請求行如下:

GET/mypage.htmlHTTP/1.0

添加擴展頭信息是為了傳遞更多的擴展信息。比如,這時候不同的瀏覽器會在請求中標記自己的身份。為方便后續(xù)添加各種不同的擴展信息,HTTP協(xié)議繼續(xù)使用「行」和分割符的概念。

首先,跟請求行保持一致,每一條擴展信息占一行,以冒號分割,以 結(jié)尾,比如:

User-Agent:NCSA_Mosaic/2.0(Windows3.1)

其次,這種信息可以有多行。那服務(wù)端怎么確定到底有幾行呢,這還得用到分割符 。HTTP 協(xié)議用一個空行表示后面擴展信息都結(jié)束了。所以完整的請求是:

GET/mypage.htmlHTTP/1.0

Host:taoshu.in

User-Agent:NCSA_Mosaic/2.0(Windows3.1)



服務(wù)端先接收一行,提取文件路程,然后再根據(jù) 逐行提取擴展信息。如果收到一個空行,則說明擴展信息接收完成。這些擴展信息也叫頭信息(header),后續(xù) HTTP 協(xié)議的各種特性都是基于它來實現(xiàn)。

HTTP/0.9 收到請求后直接傳輸文件內(nèi)容。但用些場景需要返回其他信息,比如文件不存在之類的,所以人們給它添加了返回狀態(tài)信息。此外,擴展后的 HTTP 協(xié)議也支持服務(wù)端在發(fā)送數(shù)據(jù)前返回多個頭信息。一個典型的擴展響應(yīng)為:

200OK

Date:Tue,15Nov19940832GMT

Server:CERN/3.0libwww/2.17

Content-Type:image/gif



(imagecontent)

服務(wù)器首先會發(fā)一行數(shù)據(jù)200 OK 。這里的200是狀態(tài)碼,表示成功。后面的OK是給人看的語義部分。這一行也叫 status code line。緊接著就是擴展信息,形式跟請求里的一模一樣,每行一條,以空行表示結(jié)束。最后才是文件內(nèi)容。

因為有了頭信息,HTTP協(xié)議的擴展性直接起飛。人們不斷給 HTTP 協(xié)議添加各種種樣的特性。

HTTP/0.9 只能傳輸純文本文件。因為有了 Header,我們可以傳輸更多的描述信息,比如文件在的類型、長度、更新時間等等。這些傳輸數(shù)據(jù)的描述信息也被稱為 Entity Header,數(shù)據(jù)本身稱為 Entiy。

常見的 Entiy Header 有:

Content-Type 內(nèi)容類型

Content-Length 內(nèi)容長度

Content-Encoding 數(shù)據(jù)編碼

Content-Type 表示數(shù)據(jù)類型,比如 gif 的類型是image/gif。類型的取值最終被標準化為 Multipurpose Internet Mail Extensions(MIME)。

Content-Length 表示數(shù)據(jù)長度。但我們前面說過,HTTP/0.9 的服務(wù)器不需要返回文件長度,等傳輸完畢后關(guān)閉 TCP 連接就好了。為什么又要定義長度信息呢?

這里有兩個問題。第一個是在請求里支持上傳內(nèi)容,第二個是連接優(yōu)化問題。

HTTP/0.9 只有一種 GET 請求。顯然光下載是不夠的。人們陸續(xù)引入了 HEAD 和 POST 等請求,用來給服務(wù)器提交數(shù)據(jù)。一但要提交數(shù)據(jù),光用分割符就不夠了。因為提交的數(shù)據(jù)本身就可能包含分割符。所以需要事先指定數(shù)據(jù)的長度。這個長度用的就是 Content-Length 頭來指定。

另外一個是連接優(yōu)化問題。其實 HTTP 協(xié)議的發(fā)展史很大程度上就是傳輸性能的優(yōu)化史。

HTTP/0.9每次請求都會創(chuàng)建一個 TCP 連接,讀取結(jié)束后連接就會被關(guān)閉。如果一次只下載一個文件也沒什么問題。但后來 HTML 頁面支持嵌入圖片等內(nèi)容,一個頁面可能有多個圖片。這樣瀏覽器打開一個 HTML 頁面的時候就需要發(fā)起多次 HTTP 請求,每次請求都要反復(fù)建立和關(guān)閉 TCP 連接。不但浪費服務(wù)器資源,還會拖慢頁面的加載速度。

所以,大家就想辦法復(fù)用底層的 TCP 連接。簡單來說就是服務(wù)器在內(nèi)容發(fā)送完成后不主動關(guān)閉連接。但不關(guān)閉就會出現(xiàn)前面說的問題,客戶端不知道響應(yīng)內(nèi)容什么時候傳輸完畢。所以需要事先指定數(shù)據(jù)的長度。因為 HTTP 協(xié)議已經(jīng)有了 header 機制,所以添加 Content-Length 就是最自然的辦法。

這里還有一個兼容性問題。如果客戶端不支持復(fù)用 TCP 連接,那服務(wù)端不關(guān)閉連接的話客戶端就會一直在等待。所以復(fù)用 TCP 連接這個功能不能默認開啟,而是應(yīng)該由客戶端決定要不要使用。這就引出了Connection:Keep-Alive這個頭信息。如果客戶在請求中指定 Keep-Alive,服務(wù)端才不會主動關(guān)閉 TCP 連接。

除了復(fù)用 TCP 連接之外,HTTP/0.9 另一個值得優(yōu)化的地方就是數(shù)據(jù)壓縮。那個時代網(wǎng)速很慢,如果能把數(shù)據(jù)壓縮之后再傳輸可以顯著降低傳輸耗時。服務(wù)端不能隨意壓縮,因為有的客戶端可能不支持。所以就先引入了Accept-Encoding這個頭,可能的取值如compress或者gzip。服務(wù)端收到這個請求之后才對內(nèi)容做壓縮。因為瀏覽器可能支持多種壓縮算法,瀏覽器需要選擇一種自己也支持的來壓縮數(shù)據(jù),所以就需要在返回內(nèi)容的時候指定自己用了哪種算法。這就是Content-Encoding頭的用途。

不論是前面的 Connection 還是后面的 Accept-Encoding,為了盡可能地兼容不同客戶端,HTTP 協(xié)議會通過添加新的 header 來協(xié)商是否使用擴展特性。這種協(xié)商由客戶端來主導(dǎo),服務(wù)器需要根據(jù)客戶端的請求來配合完成。

還是因為網(wǎng)絡(luò)比較慢而且成本很高,HTTP協(xié)議需要進一步優(yōu)化數(shù)據(jù)傳輸效率。一個典型的場景是客戶端已經(jīng)下載過某文件內(nèi)容。當(dāng)客戶端再次請求的時候,服務(wù)端還要不要返回。如果不返回,則客戶端拿不到最新的內(nèi)容;如果返回,當(dāng)服務(wù)端的文件沒有變化的時候,客戶端會花很長時間加載一個已經(jīng)下載過的文件。怎么優(yōu)化這個問題呢?

人們引入了如下 Entity Header:

Last-Modified 最近修改時間

Expires 過期時間

如果文件不經(jīng)常改動,服務(wù)器可以對過 Last-Modified 把最近修改時間發(fā)送給瀏覽器。瀏覽器如果支持,可以在下次請求該資源的時候帶上這個時間,也就是在請求里添加下面的頭:

If-Modified-Since:Sat,29Oct19941931GMT

服務(wù)器收到后會跟文件的當(dāng)前修改時間做對比,如果沒有修改則直接返回304:

304NotModified

這種叫作條件請求,可以顯著減少不必要的網(wǎng)絡(luò)傳輸。

即使如此,客戶端還是發(fā)起一次 HTTP 請求才能拿到 304 響應(yīng),也會產(chǎn)生網(wǎng)絡(luò)傳輸和服務(wù)端開銷。為了進一步優(yōu)化,HTTP又引入了 Expires 頭,它的含義是一個未來的過期時間。在這個時間之前瀏覽器可以安全使用本地緩存的副本,不需要從服務(wù)器下載。這樣連條件請求都不需要發(fā)起了。

不過 Expires 特性有一個副作用,文件一旦下發(fā),在過期之前根本無法修改。

大約是在1991-1995這個時間,各瀏覽器廠商陸續(xù)實現(xiàn)了上述功能。但不同瀏覽器和服務(wù)端軟件支持的功能不同,帶來各種兼容問題。于是到 1996 年,IETF 發(fā)布 RFC1945。RFC1945 只能說是當(dāng)前最佳實踐的總結(jié),并不是推薦標準。但人們還是稱它為 HTTP/1.0。

沒過一年,也就是1997年,IETF就發(fā)布了RFC2068,也就是大名鼎鼎的 HTTP/1.1 協(xié)議規(guī)范。

HTTP/1.1 是對 HTTP/1.0 的梳理和擴展。核心的改動有:

默認開啟 TCP 連接復(fù)用,客戶端不需要再發(fā)送 Connection:Keep-Alive

添加了所謂 pipeline 特性,進一步優(yōu)化傳輸效率

支持 chunked 傳輸編碼

擴展緩存控制

內(nèi)容協(xié)商,包括語言、傳輸編碼、類型等

在同一IP上建立多個 HTTP 網(wǎng)站

所謂的 pipeline 特性是對 HTTP 協(xié)議傳輸效率的進一步優(yōu)化,但最終失敗了。

HTTP 協(xié)議是請求應(yīng)答式協(xié)議??蛻舳税l(fā)一個請求,然后等待服務(wù)端返回內(nèi)容。雖然在 HTTP/1.0 時代就有了 TCP 連接復(fù)用、內(nèi)容壓縮和條件請求等優(yōu)化機制,但客戶端發(fā)起新請求之前必須等待服務(wù)器返回內(nèi)容。換言之就是客戶端無法在一個連接上并行發(fā)起多個請求。為此,HTTP/1.1 的 pipeline 就規(guī)定客戶端可以依次發(fā)起多個 HTTP 請求,然后等待服務(wù)器返回結(jié)果。服務(wù)器需要按照請求順序依次返回對應(yīng)的響應(yīng)內(nèi)容。

cscs
|req1||req1|
|------->||------->|
|resp1||req2|
|<-------|??????????????|------->|
|req2||req3|
|------->||------->|
|resp2||resp1|
|<-------|??????????????|<-------|
??|??req3??|??????????????|??resp2?|
??|------->||<-------|
??|??resp3?|??????????????|??resp3?|
??|<-------|??????????????|<-------|
??
without?pipeline?????????with?pipeline

雖然服務(wù)器收到多個請求的時候可以并發(fā)處理,這種并發(fā)帶來的優(yōu)化有限,而且 pipeline 特性并沒有減少實際的網(wǎng)絡(luò)傳輸。幾乎沒有軟件實現(xiàn) pipeline 特性,所以這個優(yōu)化設(shè)計以失敗告終。

chunked 編碼是一項非常成功的優(yōu)化,主要解決服務(wù)端動態(tài)生成響應(yīng)內(nèi)容的情況。

HTTP/1.0 只能使用 Content-Length 指定內(nèi)容長度,而且是先發(fā)送 header 再發(fā)送 body。這就要求必須在傳輸內(nèi)容之前確定內(nèi)容的長度。對于靜態(tài)文件,這當(dāng)然不是問題。但如果要加載一個由 PHP 動態(tài)渲染的 HTML 就有問題了。因為 HTML 是程序動態(tài)生成的,沒法事先確定內(nèi)容長度。如果還用原來的辦法,只能先把內(nèi)容生成好保存到一個臨時文件,再發(fā)送給客戶端。顯然這種性能太差。

為了解決這個問題,HTTP/1.1 引入 chunked 編碼。簡單來說就是回到之前的長度流,將數(shù)據(jù)逐段發(fā)送給客戶端,每一段前面加上長度信息:

HTTP/1.1200OK

Content-Type:text/plain

Transfer-Encoding:chunked


7

Mozilla

9

Developer

7

Network

0



Transfer-Encoding 指定為 chunked。接下來的數(shù)據(jù)也是分行傳輸。一行長度,一行數(shù)據(jù)。結(jié)束的時候長度指定為零,然后再加一個空行。這樣服務(wù)端就不需要事先確定響應(yīng)內(nèi)容的長度,PHP 就可以有邊渲染一邊發(fā)送。這個特性還是 WebSocket 沒有普及的年代被用于實現(xiàn)消息推送。大家可以搜索 Comet 或者 HTTP 長輪詢了解更多信息。

HTTP/1.1 對緩存做了更粗細化的定義,引入了 Cache-Control 擴展信息。這一部分內(nèi)容比較復(fù)雜,除了會影響瀏覽器的緩存行為之外,還會影響 CDN 節(jié)點的行為。部分 CDN 廠商還會擴展 標準緩存指令的語義。限于篇幅,在此就不展開了。

但 HTTP/1.1 對條件請求做了擴展,可以說一下。

操作系統(tǒng)會自動記錄文件的修改時間,讀取該時間也非常方便,但 Last-Modified 不能覆蓋所有情況。有時候我們需要用程序定時生成某些文件,它的修改時間會周期性變化,但內(nèi)容不一定有改變。所以光用 Last-Modified 還是可能產(chǎn)生不必要的網(wǎng)絡(luò)傳輸。于是 HTTP 協(xié)議引入了一個新的頭信息 Etag。

Etag 的語義是根據(jù)文件內(nèi)容計算一個值,只有在修改內(nèi)容的時候才會產(chǎn)生新的 Etag??蛻舳嗣看握埱蟮臅r候把上一次的 Etag 帶回來,也就是添加下面的頭:

If-None-Match:"c3piozzzz"

服務(wù)端收到后會對比 Etag,只有發(fā)生變化的時候才會返回新的文件內(nèi)容。

那個時候的網(wǎng)絡(luò)很不穩(wěn)定,斷網(wǎng)是家常便飯。想想一個文件下載到99%然后斷網(wǎng)了是一種怎樣的體驗。為了減少不必要的數(shù)據(jù)傳輸,人們很快就給 HTTP 協(xié)議添加了「斷點續(xù)傳」功能。其實斷點續(xù)傳是從客戶端視角來看的。從協(xié)議角度來看,需要添加的功能是根據(jù)指定范圍傳輸數(shù)據(jù)。也就是說原來的文件是100字節(jié),客戶端可以指定只下載最后的10字節(jié):

Content-Range: bytes 91-100/100 這里的91-100表示要下載的范圍,后面的100表示整個文件的長度。如果服務(wù)器支持,則會返回:

HTTP/1.1206Partialcontent

Date:Wed,15Nov19950624GMT

Last-modified:Wed,15Nov19950408GMT

Content-Range:bytes91-100/100

Content-Length:10

Content-Type:image/gif



(imagedata)

該功能除了用于斷點續(xù)傳外,還可以實現(xiàn)并行下載加速??蛻舳丝梢云鸲鄠€線程,建立多條 TCP 連接,每個線程下載一部分,最后把有的內(nèi)容連到一直。就這么簡單。

另外,HTTP/1.1 還要求客戶端在請求的時候必須發(fā)送 Host 頭信息。這里面保存著當(dāng)前請求對應(yīng)的網(wǎng)站域名。服務(wù)器收到請求后會根據(jù) Host 里的域名和請求行里的路徑來確定需要返回的內(nèi)容。這樣就能實現(xiàn)在同一個 IP 上搭建不同域名的網(wǎng)站,也就是所謂的虛擬主機。這大大降低了網(wǎng)站的建設(shè)成本,對 Web 生態(tài)的發(fā)展起到了至關(guān)重要的作用。

除了擴展 HTTP/1.0 原來的功能外,HTTP/1.1 還引入了連接升級功能。其實這個功能后面用的不多,但有一個重量級的協(xié)議 WebSocket 在用,所以不得不說。

所以連接升級就是把當(dāng)前用于 HTTP 會話的 TCP 連接切換到其他協(xié)議。以 WebSocket 為例:

GET/chatHTTP/1.1
Host:taoshu.in
Upgrade:websocket
Connection:Upgrade

這里把 Connection 設(shè)成了 Upgrade,表示希望切換協(xié)議。而 Upgrade:websocket 表示要切換到 websocket 協(xié)議。在切換之前,這還是一個普通的 HTTP 請求。服務(wù)器可以對該請求做各種鑒權(quán)等 HTTP 動作。服務(wù)器如果接受用戶的請求,則會返回:

HTTP/1.1101SwitchingProtocols
Upgrade:websocket
Connection:Upgrade

從這一該起,雙方就不能在該 TCP 連接上發(fā)送 HTTP 協(xié)議數(shù)據(jù)了。因為協(xié)議已經(jīng)切換到 WebSocket。

從 1999 年開始,到 2015 年 HTTP/2 發(fā)布,HTTP 協(xié)議有15年的時候沒有大的變化。與此同時,互聯(lián)網(wǎng)蓬勃發(fā)展,從 Web 1.0 過渡到 Web 2.0,從 PC 互聯(lián)網(wǎng)發(fā)展到移動互聯(lián)網(wǎng),從明文 HTTP 也切換到加密 HTTPS。整個過程 HTTP 協(xié)議都發(fā)揮了核心作用。這從側(cè)面也說明 HTTP 協(xié)議是一種擴展性非常好的協(xié)議。

但 HTTP/1.1 畢竟是九十年代設(shè)計的協(xié)議。2010年之后,移動互聯(lián)網(wǎng)興起,業(yè)界希望對 HTTP 的問題做夠進一步優(yōu)化。那還有哪些問題可以優(yōu)化呢?主要有幾個方面:

協(xié)議使用文本格式,傳輸和解析效率都比較低

Header 部分信息無法壓縮,但現(xiàn)實情況是 Header 體積也不?。ū热?cookie)

無法在單一 TCP 連接上并發(fā)請求資源(pipeline 失敗了)

服務(wù)端無法主動給客戶發(fā)送內(nèi)容

文本格式其實是 HTTP 的一大特色。我們在調(diào)試的時候可以直接使用 telnet 連接服務(wù)器,然后用肉眼看服務(wù)器的返回結(jié)果。但對人類友好的設(shè)計對機器一定不友好。HTTP協(xié)議使用 作為分割符,雙不限制頭信息的數(shù)量,這必然導(dǎo)致解析的時候需要動態(tài)分配內(nèi)存。而且還要把數(shù)字、日期等信息轉(zhuǎn)換成對應(yīng)的二進制格式,這都需要額外的解析成本。

HTTP/1.x 支持壓縮數(shù)據(jù)內(nèi)容,而且使用頭信息保存壓縮算法。所以就不能用相同的算法壓縮頭信息了。只能另辟蹊徑。

HTTP/1.1 的 pipeline 已然失敗,無法充分復(fù)用 TCP 連接。HTTP 從一開始就是請求應(yīng)答式的設(shè)計,服務(wù)器沒辦法主動推送內(nèi)容到客戶端。

為了解決這幾個問題,Google 挾 YouTube 和 Chrome 兩大殺器,推出了 SPDY 協(xié)議。該協(xié)議有兩個特點:

兼容 HTTP 語義

使用二進行格式傳輸數(shù)據(jù) SPDY 引入了幀做為最小的傳輸單位:

+-----------------------------------------------+
|Length(24)|
+---------------+---------------+---------------+
|Type(8)|Flags(8)|
+-+-------------+---------------+-------------------------------+
|R|StreamIdentifier(31)|
+=+=============================================================+
|FramePayload(0...)...
+---------------------------------------------------------------+

Figure1:FrameLayout

每一幀前三個字節(jié)表示數(shù)據(jù)長度,然后用一個字節(jié)表示類型,再用一個字節(jié)保存一些擴展標記。然后就是四個字節(jié)的 stream ID,最后是真正的數(shù)據(jù)。這其實就表明 HTTP 協(xié)議從分割符流轉(zhuǎn)向了長度流。

在同一個 TCP 連接上,數(shù)據(jù)幀可以交替發(fā)送,不再受請求應(yīng)答模式制約。也就是說服務(wù)端也可以主動給客戶端發(fā)消息了。同一個請求的 header 和數(shù)據(jù)部分也可以分開發(fā)送,不再要求先發(fā) header 再發(fā) body。也正是因為數(shù)據(jù)幀交錯傳輸,同一個會話下的數(shù)據(jù)需要能關(guān)聯(lián)起來,所以 SPDY 給每一幀添加了 stram ID。換句話說 SPDY 在一個 TCP 連接上虛擬出了多個 stream,每一個 stream 從效果看都是一個 TCP 連接。不同的 HTTP 請求和響應(yīng)數(shù)據(jù)可以使用自己的 stream 并發(fā)傳輸,互不影響。這樣一下子就解決了上面的一、三和四這三個問題。

第二個問題比較麻煩。但解決思路也很簡單。HTTP/1.x 的頭信息都是 K-V 型的,而且都是字符串。這里的 K-V 都很少變化。比如只要是訪問我的博客,不論有多少請求,都得發(fā)送 Host: taoshu.in。對于這種不變的,我們完全可以在兩端各保存一張映射表,給每個 Key 和 Value 都指定一個編號。這樣后續(xù)的請求只要傳 Key 和 Value 的編號就行了,從而實現(xiàn)壓縮的效果。單看 Host 可能不覺得有多少進步。但大家想想自己的 cookie,里面有登錄會話信息,每次都重復(fù)發(fā)送浪費相當(dāng)驚人。所以壓縮頭信息帶來的優(yōu)化還是驚人的。

因為谷歌一邊控制著市場份額最大的 Chrome 瀏覽器,另一邊又控制像 Google/YouTube 這樣的內(nèi)容服務(wù),所以開發(fā)下一代 HTTP 協(xié)議便一件非常容易的事情。SPDY 于 2012 年發(fā)布,最終在 IETF 完成標準化,并于 2015 年發(fā)布,也就是RFC7540。

隨著社會的發(fā)展,隱私保護成了人們關(guān)注的重要課題。為了保護用戶信息,業(yè)界一真在推動 HTTP + TLS 也就是 HTTPS 的普及。HTTPS 服務(wù)使用 443 端口。我們前面講過,HTTP/2 使用二進制編碼,跟 HTTP/1.x 并不兼容。但客戶端又不會一夜之間都升級的 HTTP/2。那怎么才能在一個端口上同時支持兩種 HTTP 協(xié)議呢?這就用到了 TLS 協(xié)議的 ALPN 擴展。簡單來說就是客戶端在發(fā)起 TLS 會話的時候會通過 ALPN 擴展附帶自己支持的應(yīng)用層協(xié)議,比如 http/1.1 和 h2。服務(wù)端收到后會把自己支持的應(yīng)用層協(xié)議返回給客戶端。這樣雙方就能確定接下來在 TLS 會話是使用什么協(xié)議。

理論上 HTTP/2 可以通過 HTTP/1.1 的升級機制來協(xié)商,這樣也能解決兩個版本共用 TLS 會話的問題。但這種升級會再來額外的延遲,所以主流的瀏覽器都不支持。

HTTP/2 發(fā)布之后,整個業(yè)界都在積極遷移到新的協(xié)議。但實踐證明,HTTP/2并沒有想象中的那么好。為什么呢?因為對于同一個域名,瀏覽器默認只會開一個連接,所有請求都使用一個TCP連接收發(fā)。雖然不同的請求使用不同的 stream,但底層的連接只有一個。如果網(wǎng)絡(luò)出現(xiàn)抖動,不論是哪一個請求的數(shù)據(jù)需要重傳,其他請求的數(shù)據(jù)都必須等待。這就是所謂的 Head of Line blocking 問題。HTTP/2 非但沒有優(yōu)化,甚至還比 HTTP/1.x 還要差。因為在 HTTP/1.x 時代,瀏覽器自知 HTTP 無法復(fù)用連接,所以會為同一個域名創(chuàng)建多個 TCP 連接。不同的請求可能會分布到不同的連接上,出現(xiàn)網(wǎng)絡(luò)抖動的影響比只用一個連接要好一點。

HTTP/2 的另一個問題就是功能太復(fù)雜。比如它支持在服務(wù)器主動推送資源(比如 CSS 文件)到瀏覽器,這樣客戶端在加載的時候就需要等待網(wǎng)絡(luò)傳輸。但該功能非常復(fù)雜,而且效果有限,最終連 Chrome 自己都放棄支持該功能了。這部分功能被 HTTP 103 Early Hints 狀態(tài)碼代替,具體可以參考RFC8297。

一計不成,再生一計。谷歌的工程師跟 Head of Line blocking 問題死磕。這次他們把矛頭指向了問題的根源 TCP 協(xié)議。因為 TCP 是可靠傳輸協(xié)議,數(shù)據(jù)必須按順序收發(fā),而且要邊確認邊發(fā)送。如果底層用 TCP 連接,就不可能解決 Head of Line blocking 問題。為此,他們基于 UDP 協(xié)議設(shè)計了 QUIC 協(xié)議。

QUIC 協(xié)議簡單來說就是一種面向消息的傳輸協(xié)議(TCP 是面向數(shù)據(jù)流的傳輸協(xié)議)。QUIC 也有 stream 的概念,每個會話可以有多個流。不同的流的數(shù)據(jù)都使用 UDP 收發(fā),互不干擾。跟 TCP 一樣,數(shù)據(jù)發(fā)出后也需要對方確認。然后再把 QUIC 跟 HTTP/2 的幀映射到一起,最終形成 HTTP/3 協(xié)議,也就是RFC9114。

那 QUIC 有沒有問題呢?也有,但基本都不是設(shè)計上的問題。

第一個問題就是運營商可能對 UDP 流量做限流,很多防火墻可能會阻止 QUIC 流量。這是之前 UDP 通信使用不廣泛導(dǎo)致的。隨著 HTTP/3 技術(shù)的普及,這些問題會逐漸改善。

第二個問題是 HTTP/3 啟動延遲的問題。HTTP/3 使用 UDP 通信,跟 HTTP/1.x 和 HTTP/2 不兼容,所以瀏覽器沒法判斷服務(wù)器是否支持 HTTP/3。

目前主流的做法是網(wǎng)站同時支持 HTTP/2 和 HTTP/3。瀏覽器先通過過 TCP 連接訪問服務(wù)器。服務(wù)器在第一個響應(yīng)中返回一個特殊的 Header:

Alt-Svc:h3=":4430";ma=3600

這里的意思是在 UDP 的 4430 端口提供 HTTP/3 服務(wù),該信息的有效時間為 3600 秒。后面瀏覽器就可以使用 QUIC 連接 4430 端口了。

明眼人一看就知道這里有問題,建立 HTTP/3 會話之前還得先用一下 HTTP/2 啟動有把。這不科學(xué)而且這會帶來額外的耗時。為此,人們又開始想別的辦法,這就是 DNS SVCB/HTTPS 記錄。

DNS SVCB/HTTPS 簡單來說就是用一種特殊的 DNS 記錄把前面的 Alt-Svc 信息曝露出來。瀏覽器在訪問網(wǎng)站之前先通過 DNS 查詢是否支持 HTTP/3 以及對應(yīng)的 UDP 端口,然后就直接發(fā)起 HTTP/3 會話就好。這樣就完全不依賴 TCP 連接了。關(guān)于 DNS SVCB/HTTPS 記錄的更多信息請看我的專門文章。

順便說一句,HTTP/3 默認可以工作在任意 UDP 端口,不像 HTTPS 那樣默認工作在 443 端口。如果運營商封掉 443 就沒法對外服務(wù)。等 HTTP/3 普及了,所有人都可以使用自家的寬帶搭建網(wǎng)站具體做法可以參考我的這篇文章。

好了,到現(xiàn)在快肝了一萬字了。我認為基本講清楚了 HTTP 協(xié)議的發(fā)展脈絡(luò)。現(xiàn)于篇幅,沒能詳細討論 HTTP/2 和 HTTP/3 的技術(shù)細節(jié),不能說不是個遺憾。先開個坑,后面有時間再補上。希望本文能幫助你更好地理解 HTTP 協(xié)議。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 通信
    +關(guān)注

    關(guān)注

    18

    文章

    6191

    瀏覽量

    137593
  • HTTP
    +關(guān)注

    關(guān)注

    0

    文章

    524

    瀏覽量

    32610
  • TCP
    TCP
    +關(guān)注

    關(guān)注

    8

    文章

    1401

    瀏覽量

    80661

原文標題:大話 HTTP 協(xié)議前世今生

文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    藍牙技術(shù)的前世今生

    藍牙是一種支持設(shè)備短距離通信的低功耗、低成本無線電技術(shù)。它利用短程無線鏈路取代專用電纜,便于人們在室內(nèi)或戶外流動操作。那么這種技術(shù)為什么叫藍牙?又歷經(jīng)了怎樣的發(fā)展?本文將帶你了解藍牙技術(shù)的前世今生
    的頭像 發(fā)表于 05-09 09:46 ?2847次閱讀
    藍牙技術(shù)的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    芯片開源架構(gòu)RISC-V的前世今生

    芯片春秋 開源架構(gòu)RISC-V前世今生
    發(fā)表于 05-21 10:04

    芯片春秋——ARM前世今生

    芯片春秋 ARM前世今生
    發(fā)表于 05-25 15:05

    嵌入式ARM開發(fā)的前世今生,看完你就懂了

    嵌入式ARM的開發(fā)方向是什么?嵌入式ARM開發(fā)的前世今生
    發(fā)表于 04-20 06:39

    關(guān)于汽車操作系統(tǒng)的前世今生看完你就懂了

    關(guān)于汽車操作系統(tǒng)的前世今生看完你就懂了
    發(fā)表于 09-26 06:40

    汽車總線前世今生

    汽車總線前世今生
    發(fā)表于 01-24 15:41 ?24次下載

    六張圖看懂人工智能的前世今生

    關(guān)于人工智能的前世今生、內(nèi)涵意義,下圖可以說是相當(dāng)清楚全面了。人工智能是未來一大熱點,如果你也看好這一趨勢,
    的頭像 發(fā)表于 08-25 11:00 ?4654次閱讀

    初探工業(yè)互聯(lián)網(wǎng)的前世今生

    為了能夠幫助大家解答這些疑問, 我們將會用幾篇文章,由淺入深的給大家講解工業(yè)互聯(lián)網(wǎng)的前世今生,而今天的這篇文章將初探工業(yè)互聯(lián)網(wǎng)的概念和來源。
    的頭像 發(fā)表于 12-13 16:14 ?3375次閱讀

    聊聊MSP和CMP的前世今生

    聊聊MSP和CMP的前世今生 伴隨著云的普及,云的生態(tài)角色變得越來越細分,比如MSP和CMP,受到了越來越多企業(yè)客戶的青睞,玩家也不斷增加,越來越多的公司致力于在這些領(lǐng)域創(chuàng)新發(fā)展。 在接觸客戶和友商
    發(fā)表于 12-30 20:45 ?1896次閱讀

    MiniLED背光的前世今生

    了2019年的最熱門話題。關(guān)于Mini LED顯示,市場進入了產(chǎn)業(yè)化的沖刺階段;而關(guān)于Mini LED背光,我們今天就來簡單地聊聊它的“前世今生”。
    發(fā)表于 07-08 15:17 ?9981次閱讀

    人工智能的前世今生

    人工智能和機器學(xué)習(xí)概念目前在各種場合被頻頻提到,移動互聯(lián)網(wǎng)時代后的未來被預(yù)測為人工智能時代,那么人工智能的前世今生是怎樣的,到底會給我們的未來帶來什么呢?為了弄清這個問題,我們可以簡單回顧一下人工智能的發(fā)展歷史。
    的頭像 發(fā)表于 12-10 14:28 ?3988次閱讀

    高壓電源創(chuàng)新:前世今生

    高壓電源創(chuàng)新:前世今生
    發(fā)表于 11-03 08:04 ?1次下載
    高壓電源創(chuàng)新:<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    電池管理技術(shù)的前世今生

    電池管理技術(shù)的前世今生
    發(fā)表于 11-04 09:51 ?5次下載
    電池管理技術(shù)的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>

    帶你探索吹風(fēng)筒的前世今生【其利天下高速風(fēng)筒方案開發(fā)】?

    電吹風(fēng)作為如今生活中不可或缺的小家電之一,這個看似簡單的設(shè)備,已經(jīng)走過了漫長的發(fā)展歷程,從它的前世今生,經(jīng)歷了許多變革和創(chuàng)新,本文將帶您穿越時間,探索其前世
    的頭像 發(fā)表于 11-02 16:15 ?3035次閱讀
    帶你探索吹風(fēng)筒的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>【其利天下高速風(fēng)筒方案開發(fā)】?

    二極管的前世今生

    二極管的前世今生
    的頭像 發(fā)表于 12-14 18:35 ?1691次閱讀
    二極管的<b class='flag-5'>前世</b><b class='flag-5'>今生</b>