了解 HTTP 看這一篇就夠

微笑橙子mr 發佈 2023-01-17T03:29:37.930142+00:00

Google 在推 SPDY 的時候就已經意識到了這個問題,於是就又發明了一個新的QUIC協議,讓 HTTP 跑在 QUIC 上而不是 TCP 上。

請關注 點讚 轉發

1. http的發展史

在學習網絡之前,了解它的歷史能夠幫助我們明白為何它會發展為如今這個樣子,引發探究網絡的興趣。下面的這張圖片就展示了「網際網路」誕生至今的發展歷程。

2. http是什麼?

HyperTextTransferProtocol 直譯為『超文本傳輸協議'

  1. 超文本:指文字、圖片、視頻、音頻等的混合體,比如最熟悉的html。
  2. 傳輸:http是一個「雙向協議」,傳輸的是請求方和響應方之間的數據,不限制請求方和響應方之間的角色,傳遞的過程中可以存在任意「中間人」。
  3. 協議:協是兩個或多個參與者之間的交流,議是指對參與者之間的約定和規範。所以,http協議可以理解為作用在計算機之間,使用計算機能夠理解的語言確立計算機之間交流通信的規範,以及相關的各種控制和錯誤處理方式。

所以對與以上的問題可以有這樣的總結:http是一個在計算機世界裡專門在兩點之間傳遞文字、圖片、音頻、視頻等超文本數據的約定和規範。

3. 與http相關的一些概念

瀏覽器(web Browser): 瀏覽器的本質是http中的請求方,使用http協議獲得網絡上的各種資源。在HTTP協議里,瀏覽器的角色被稱為"User Agent"即用戶代理,意思是作為訪問者的」代理來發起HTTP請求。下圖是一些主流瀏覽器及其內核。

伺服器(web Server): 硬體含義就是物理形式或「雲」形式的機器。軟體含義的 Web 伺服器就是提供 Web 服務的應用程式,通常會運行在硬體含義的伺服器上。它利用強大的硬體能力響應海量的客戶端 HTTP 請求,返回動態的信息。常見的Web伺服器有Apache、Nginx。

CDN(Content Delivery Network): CDN是為了解決長距離網絡訪問速度慢的問題而誕生的一種網絡應用服務,全稱為「內容分發網絡」。CDN最核心的原則是「就近訪問」,使用HTTP協議里的代理和緩存技術,用戶在上網的時候不直接訪問原網站,而是訪問離他最近的一個CDN節點,節省了訪問過程中的時間成本。(負載均衡,安全防護,邊緣計算).

爬蟲(Crawler):「機器人」形式的用戶代理,是一種可以自動訪問Web資源的應用程式。

HTML(Hyper Text Markup Language): 超文本標記語言,用於描述超文本頁面,用標籤定義圖片、文字、排版布局,最終由瀏覽器渲染。

web Service: 由W3C定義的應用服務開發規範,使用client-server主從架構。是一個基於Web(HTTP)的服務架構技術。

WAF: 網絡應用防火牆,位於Web伺服器之前,專門檢測http流量,是防護web應用安全的技術。可以阻止SQL注入,跨站腳本攻擊,可以完全集成進Apache或Nginx。

TCP/IP: 一系列網絡通信協議的統稱,其中最核心的是TCP和IP協議。其他的還有UDP,ICMP,ARP等,共同構成一個複雜但有層次的協議棧。IP(Internet Protocol)協議主要解決尋址和路由問題,以及如何在兩點之間傳輸數據包。TCP(Transmission Control Protoco)協議位於IP協議之上,意思是「傳輸控制協議」,基於IP協議提供可靠地、字節流形式的通信,是HTTP協議實現的基礎。網際網路上的 HTTP 協議運行在 TCP/IP 上,HTTP 也就可以更準確地稱為「HTTP over TCP/IP」。

DNS(Domain Name System): 域名系統,用有意義的名字來作為 IP 地址的等價替代。在 DNS 中,「域名」(Domain Name)又稱為「主機名」(Host)。域名用「.」分隔成多個單詞,級別從左到右逐級升高,最右邊的被稱為「頂級域名」。但想要使用 TCP/IP 協議來通信仍然要使用 IP 地址,所以需要把域名做一個轉換,「映射」到它的真實 IP,這就是所謂的「域名解析」。

URI/URL: URI(Uniform Resource Identifier)中文名稱是統一資源標識符。DNS 和 IP 地址只是標記了網際網路上的主機,URI能夠唯一地標記網際網路上資源。URI 另一個更常用的表現形式是 URL(Uniform Resource Locator), 統一資源定位符,也就是我們俗稱的「網址」,它實際上是 URI 的一個子集,通常不會做嚴格的區分。

URI 主要有三個基本的部分構成:

  • 協議名:即訪問該資源應當使用的協議
  • 主機名:即網際網路上主機的標記,可以是域名或 IP 地址
  • 路徑:即資源在主機上的位置,使用「/」分隔多級目錄

HTTPS: 全稱是「HTTP over SSL/TLS」,也就是運行在 SSL/TLS 協議上的 HTTP。它是一個負責加密通信的安全協議,建立在 TCP/IP 之上,所以也是個可靠的傳輸協議,可以被用作 HTTP 的下層,相當於「HTTP+SSL/TLS+TCP/IP」。

代理(Proxy): 是 HTTP 協議中請求方和應答方中間的一個環節,作為「中轉站」,既可以轉發客戶端的請求,也可以轉發伺服器的應答。

代理有很多的種類,常見的有:

  • 匿名代理:完全「隱匿」了被代理的機器,外界看到的只是代理伺服器;
  • 透明代理:顧名思義,它在傳輸過程中是「透明開放」的,外界既知道代理,也知道客戶端;
  • 正向代理:靠近客戶端,代表客戶端向伺服器發送請求;
  • 反向代理:靠近伺服器端,代表伺服器響應客戶端的請求;

4.網絡的分層模型

網絡分層模型層級是從下往上數的,一般我們比較常接觸到的是TCP/IP四層模型,也是比較早出現的分層模型。

  • 第一層是鏈路層(link layer),負責在底層網絡上發送原始數據包,工作在網卡這個層次,使用 MAC 地址來標記網絡上的設備,所以有時候也叫 MAC 層。對應的是ISO模型的"數據鏈路層"。
  • 第二層叫網絡層(internet layer),IP協議就處在這一層。因為IP協議定義了"IP 地址"的概念,所以就可以在"鏈路層"的基礎上,用IP位址取代MAC地址,在這個網絡里找設備時只要把IP 地址再翻譯成MAC地址就可以了。對應的是ISO模型的"網絡層"。
  • 第三層叫"傳輸層"(transport layer),這個層次協議的職責是保證數據在IP位址標記的兩點之間可靠地傳輸,是TCP協議和UDP協議工作的層次。對應的是ISO模型的"傳輸層"。
  • 第四層叫"應用層"(application layer),由於下面的三層把基礎打得非常好,所以在這一層就"百花齊放"了,有各種面向具體應用的協議。例如 Telnet、SSH、FTP、SMTP 等等,當然還有我們的 HTTP。對應的是ISO模型的"會話層","表示層","應用層"。

利用TCP/IP協議族進行網絡通信時,會通過分層順序與對方進行通信(發送端從應用層往下走,接收端從應用層往上走)。

5.域名

域名是一個有層次的結構,是一串用「.」分隔的多個單詞,最右邊的被稱為「頂級域名」,然後是「二級域名」,層級關係向左依次降低。最左邊的是主機名,通常用來表明主機的用途,比如「www」表示提供全球資訊網服務、「mail」表示提供郵件服務,不過這也不是絕對的。

可以通過下面的例子了解一下協議 主機 域名之間的層次關係。域名就像人的名字一樣,名字的關鍵是要讓我們容易記憶。除了標識身份之外,域名還可以代替ip地址。

6.DNS

我們經常會使用域名訪問網站,但其實在網絡查找的工程當中是使用ip定位資源的,域名必須解析為ip地址才可以正確的拿到資源。DNS就是用來將域名變為ip的協議。

DNS 的核心系統是一個三層的樹狀、分布式服務,基本對應域名的結構:

  • 根域名伺服器(Root DNS Server):管理頂級域名伺服器,返回"com","net","cn"等頂級域名伺服器的 IP 地址
  • 頂級域名伺服器(Top-level DNS Server):管理各自域名下的權威域名伺服器,比如 cn 頂級域名伺服器可以返回 123.cn域名伺服器的 IP 地址;
  • 權威域名伺服器(Authoritative DNS Server):管理自己域名下主機的 IP 地址,比如 123.cn 權威域名伺服器可以返回 www.123.cn 的 IP 地址。

雖然DNS的服務,遍布全球,服務能力也很厲害,但是全世界的網民都在使用這個服務,也會對伺服器造成很大的壓力。在核心 DNS 系統之外,還有兩種手段用來減輕域名解析的壓力,並且能夠更快地獲取結果,基本思路就是「緩存」。

DNS的解析結果可以保存在大公司自己的DNS伺服器里,或者作業系統緩存、hosts 文件當中,很多域名解析的工作就都不用請求根DNS伺服器了,直接在本地或本機就能解決,不僅方便了用戶,也減輕了各級 DNS 伺服器的壓力,效率就大大提升了。

基於域名和DNS伺服器,我們可以實現重定向。因為域名代替了ip地址,所以可以對外域名不變,而主機IP可以任意變動。當主機有情況需要下線、遷移時,可以更改 DNS 記錄,讓域名指向其他的機器。

我們應該都聽說過負載均衡吧,DNS在域名解析階段就可以進行負載均衡的操作。

  • 第一種方式,因為域名解析可以返回多個 IP 地址,所以一個域名可以對應多台主機,客戶端收到多個 IP 地址後,就可以自己使用輪詢算法依次向伺服器發起請求,實現負載均衡。
  • 第二種方式,域名解析可以配置內部的策略,返回離客戶端最近的主機,或者返回當前服務質量最好的主機,這樣在 DNS 端把請求分發到不同的伺服器,實現負載均衡。

7.HTTP/1.X

前面我們說了HTTP就是「超文本傳輸協議」,是一個在計算機世界裡專門在兩點之間傳遞文字、圖片、音頻、視頻等超文本數據的約定和規範。在學習過網絡的層次模型之後我們又了解了HTTP是一個應用層的協議。在這個環節我們開始正式深入HTTP的世界(基於HTTP/1.1)。

HTTP報文

HTTP 協議的請求報文和響應報文的結構基本相同,由三大部分組成:

  • 起始行(start line):描述請求或響應的基本信息;
  • 頭部欄位集合(header):使用 key-value 形式更詳細地說明報文;
  • 消息正文(entity):實際傳輸的數據,它不一定是純文本,可以是圖片、視頻等二進位數據。

請求行

請求行一般用來描述客戶端要怎樣操作服務端的資源,一般由三個部分組成。通常使用空格(space)來分隔,最後要用 CRLF 換行表示結束。

狀態行

狀態行一般用來描述服務端對於客戶端的請求回復的狀態,一般也是由三個部分組成。

頭部欄位

請求行或狀態行再加上頭部欄位集合就構成了 HTTP 報文里完整的請求頭或響應頭。除了起始行以外,請求頭和響應頭的結構基本相同。HTTP 頭欄位非常靈活,不僅可以使用標準里的 Host、Connection 等已有頭,也可以任意添加自定義頭。不過使用頭欄位需要注意下面幾點:

  • 欄位名不區分大小寫,例如「Host」也可以寫成「host」,但首字母大寫的可讀性更好;
  • 欄位名里不允許出現空格,可以使用連字符「-」,但不能使用下劃線「_」。例如,「test-name」是合法的欄位名,而「test name」「test_name」是不正確的欄位名;
  • 欄位名後面必須緊接著「:」,不能有空格,而「:」後的欄位值前可以有多個空格;
  • 欄位的順序是沒有意義的,可以任意排列不影響語義;
  • 欄位原則上不能重複,除非這個欄位本身的語義允許,例如 Set-Cookie。

HTTP請求方法

目前 HTTP/1.1 規定了八種方法,單詞都必須是大寫的形式,下面就來看看這些方法:

  • GET:獲取資源,可以理解為讀取或者下載數據;
  • HEAD:獲取資源的元信息;
  • POST:向資源提交數據,相當於寫入或上傳數據;
  • PUT:類似 POST;
  • DELETE:刪除資源;
  • CONNECT:建立特殊的連接隧道;
  • OPTIONS:列出可對資源實行的方法;
  • TRACE:追蹤請求 - 響應的傳輸路徑。

這幾個是我們比較常用的方法,有必要好好學習一下。

  • GET和HEAD
  • GET適用於向伺服器請求資源,一般將數據攜帶於url上。
  • HEAD類似於簡化版的GET請求,服務端收到HEAD請求時只返迴響應頭並且響應頭與GET完全一致
  • POST和PUT
  • POST 適用於向服務端發送數據,將數據攜帶在body當中,通常表示的是「create」的含義
  • PUT 類似於POST方法,也可以向伺服器提交數據,是「update」的含義。
  • GET和POST的區別

在這裡特別容易被問到的問題是GET和POST的區別,我也想在這塊詳細的寫一下。以下是基於我個人的理解

1. 大小: GET通常將數據帶在URL當中而POST將數據放在body里(是RFC在語義上的要求,語法上GET也可以使用body傳輸數據而POST同樣可以把參數放在URL里),因此由於瀏覽器對於URL長度的限制,GET請求能攜帶的數據大小一般不超過2KB。值得一提Chrome瀏覽器對URL的長度限制已經增加到2MB,但是我們考慮到兼容性,URL的長度應該以最大限制的最小標準為主(IE瀏覽器限制為2KB),除了瀏覽器的限制,還應該考慮到服務端的限制。

2. 安全: 安全是指請求的方法是否會對伺服器當中的資源造成影響,因為GET方法是只讀的,只要伺服器沒有「曲解」客戶端的請求,服務端上的數據就是安全的。而POST會對服務端的數據進行「增刪改」的操作,因此是不安全的。

3. 冪等: 冪等的意思是說多次重複執行操作,產生的效果是否相同。顯然因為GET方法只對伺服器上的資源做只讀操作,因此是冪等的。POST在RFC中的定義是「新增或提交數據」,多次提交數據會創建多個資源,所以不是冪等的(而 PUT 是「替換或更新數據」,多次更新一個資源,所以是冪等的)。

4. 緩存: 就是說這個方法的可緩存性,絕大多數的瀏覽器的實現里僅僅支持GET緩存。因為GET因為是讀取,就可以對GET請求的數據做緩存。而POST不冪等也就意味著不能隨意多次執行。因此也就不能緩存。

URI是什麼

URI,也就是統一資源標識符(Uniform Resource Identifier)。因為它經常出現在瀏覽器的地址欄里,所以俗稱為「網絡地址」,簡稱「網址」。URI 不完全等同於網址,它包含有 URL 和 URN 兩個部分,在 HTTP 世界裡用的網址實際上是 URL——統一資源定位符(Uniform Resource Locator)。但因為 URL 實在是太普及了,所以常常把這兩者簡單地視為相等。

URI 本質上是一個字符串,這個字符串的作用是唯一地標記資源的位置或者名字。

上面這個圖片就是一個完整的URI,下面詳細拆解一下它的結構。

scheme 協議名,表示資源應該使用哪種協議來訪問。最常見的當然就是「http」了,表示使用 HTTP 協議。另外還有「https」,表示使用經過加密、安全的 HTTPS 協議。此外還有其他不是很常見的 scheme,例如 ftp、ldap、file、news 等。

:// 分隔符,在 scheme 之後,必須是三個特定的字符「://」,它把 scheme 和後面的部分分離開。沒有特定的意義。

user:passwd@ 身份信息,表示登錄主機時的用戶名和密碼,但現在已經不推薦使用這種形式了,因為它把敏感信息以明文形式暴露出來,存在嚴重的安全隱患。

host:port 主機名,表示資源所在的主機名,通常的形式是「host:port」,即主機名加埠號。

path 路徑,表示資源所在位置,採用了類似文件系統「目錄」的表示方式,通常以『/』開始

query 查詢參數,用一個「?」開始,但不包含「?」,表示對資源附加的額外要求。path是多個「key=value」的字符串,這些字符串用字符「&」連接,瀏覽器和伺服器都可以按照這個格式把長串的查詢參數解析成可理解的字典或關聯數組形式。

#fragment 片段標識符,它是 URI 所定位的資源內部的一個「錨點」,瀏覽器可以在獲取資源後直接跳轉到它指示的位置。但片段標識符僅能由瀏覽器這樣的客戶端使用,伺服器是看不到的。

在 URI 里只能使用 ASCII 碼,對於 ASCII 碼以外的字符集和特殊字符做一個特殊的操作,把它們轉換成與 URI 語義不衝突的形式。這在 RFC 規範里稱為「escape」和「unescape」,俗稱「轉義」。URI 轉義的規則有點「簡單粗暴」,直接把非 ASCII 碼或特殊字符轉換成十六進位字節值,然後前面再加上一個「%」。

狀態碼

在HTTP報文部分我們說了HTTP的狀態行,我們在這個部分就來看看狀態行中的狀態碼。

狀態碼是一個十進位的數字,RFC 標準把狀態碼分成了五類,用數字的第一位表示分類,而 0 ~ 99 不用,這樣狀態碼的實際可用範圍就變成了 100~599。這五類的具體含義是:

  • 1××:提示信息,表示目前是協議處理的中間狀態,還需要後續的操作;
  • 2××:成功,報文已經收到並被正確處理;
  • 3××:重定向,資源位置發生變動,需要客戶端重新發送請求;
  • 4××:客戶端錯誤,請求報文有誤,伺服器無法處理;
  • 5××:伺服器錯誤,伺服器在處理請求時內部發生了錯誤。

1××

1×× 類狀態碼屬於提示信息,是協議處理的中間狀態,實際能夠用到的時候很少。

"100 Continue"因該是比較常接觸到的,會在POST請求發送大文件給伺服器時詢問伺服器是否能夠接受時使用,需要帶上請求頭Expect: 100-continue。這個過程也就是我們常說的POST發送兩個TCP包給伺服器的說法的來源,不過客戶端不需要一直等待服務端的回應,在一定時間內沒有收到否定的回答還是會將數據主體發送給伺服器。

2××

2×× 類狀態碼表示伺服器收到並成功處理了客戶端的請求,這也是客戶端最願意看到的狀態碼。

「200 OK」是最常見的成功狀態碼,表示一切正常,伺服器如客戶端所期望的那樣返回了處理結果,如果是非 HEAD 請求,通常在響應頭後都會有 body 數據。

「204 No Content」是另一個很常見的成功狀態碼,它的含義與「200 OK」基本相同,但響應頭後沒有 body 數據。所以對於 Web 伺服器來說,正確地區分 200 和 204 是很必要的。

「206 Partial Content」是 HTTP 分塊下載或斷點續傳的基礎,在客戶端發送「範圍請求」、要求獲取資源的部分數據時出現,它與 200 一樣,也是伺服器成功處理了請求,但 body 里的數據不是資源的全部,而是其中的一部分。狀態碼 206 通常還會伴隨著頭欄位Content-Range,表示響應報文里 body 數據的具體範圍,供客戶端確認,例如「Content-Range: bytes 0-99/2000」,意思是此次獲取的是總計 2000 個字節的前 100 個字節。

3××

3××類狀態碼表示客戶端請求的資源發生了變動,客戶端必須用新的 URI 重新發送請求獲取資源,也就是通常所說的「重定向」,包括著名的 301、302 跳轉。

「301 Moved Permanently」 俗稱「永久重定向」,含義是此次請求的資源已經不存在了,需要改用改用新的 URI 再次訪問。

「302 Found」,曾經的描述短語是「Moved Temporarily」,俗稱「臨時重定向」,意思是請求的資源還在,但需要暫時用另一個 URI 來訪問。

「304 Not Modified」 是一個比較有意思的狀態碼,它用於 If-Modified-Since 等條件請求,表示資源未修改,用於緩存控制。它不具有通常的跳轉含義,但可以理解成「重定向已到緩存的文件」(即「緩存重定向」)。

4××

4××類狀態碼表示客戶端發送的請求報文有誤,伺服器無法處理,它就是真正的「錯誤碼」含義了。

「400 Bad Request」 是一個通用的錯誤碼,表示請求報文有錯誤,只是一個籠統的錯誤,沒有明確含義的狀態碼。

「403 Forbidden」 實際上不是客戶端的請求出錯,而是表示伺服器禁止訪問資源。

「404 Not Found」 原意是資源在本伺服器上未找到,所以無法提供給客戶端。但現在已經被「用濫了」,只要伺服器「不高興」就可以給出個 404,而我們也無從得知後面到底是真的未找到,還是有什麼別的原因,某種程度上它比 403 還要令人討厭。

5××

5××類狀態碼表示客戶端請求報文正確,但伺服器在處理時內部發生了錯誤,無法返回應有的響應數據,是伺服器端的「錯誤碼」。

「500 Internal Server Error」 與 400 類似,也是一個通用的錯誤碼,伺服器究竟發生了什麼錯誤我們是不知道的。不過對於伺服器來說這應該算是好事,通常不應該把伺服器內部的詳細信息,例如出錯的函數調用棧告訴外界。雖然不利於調試,但能夠防止黑客的窺探或者分析。

「501 Not Implemented」 表示客戶端請求的功能還不支持,這個錯誤碼比 500 要「溫和」一些,和「即將開業,敬請期待」的意思差不多,不過具體什麼時候「開業」就不好說了。

「502 Bad Gateway」 通常是伺服器作為網關或者代理時返回的錯誤碼,表示伺服器自身工作正常,訪問後端伺服器時發生了錯誤,但具體的錯誤原因也是不知道的。

「503 Service Unavailable」 表示伺服器當前很忙,暫時無法響應服務,我們上網時有時候遇到的「網絡服務正忙,請稍後重試」的提示信息就是狀態碼 503。503 是一個「臨時」的狀態,很可能過幾秒鐘後伺服器就不那麼忙了,可以繼續提供服務,所以 503 響應報文里通常還會有一個「Retry-After」欄位,指示客戶端可以在多久以後再次嘗試發送請求。

HTTP的特點

  1. 靈活可擴展:HTTP在誕生之初只規定了報文的基本格式,比如用空格分隔單詞,用換行分隔欄位,「header+body」等,報文里的各個組成部分都沒有做嚴格的語法語義限制,可以由開發者任意定製。而那些 RFC 文檔,實際上也可以理解為是對已有擴展的「承認和標準化」,實現了「從實踐中來,到實踐中去」的良性循環。
  2. 可靠傳輸: 因為 HTTP 協議是基於 TCP/IP 的,而 TCP 本身是一個「可靠」的傳輸協議,所以 HTTP 自然也就繼承了這個特性,能夠在請求方和應答方之間「可靠」地傳輸數據。
  3. 應用層的協議: HTTP 憑藉著可攜帶任意頭欄位和實體數據的報文結構,以及連接控制、緩存代理等方便易用的特性,只要不太苛求性能,HTTP 幾乎可以傳遞一切東西,滿足各種需求,稱得上是一個「萬能」的協議。
  4. 請求 - 應答:請求 - 應答模式是 HTTP 協議最根本的通信模型,通俗來講就是「一發一收」。請求 - 應答模式也明確了 HTTP 協議里通信雙方的定位,永遠是請求方先發起連接和請求,是主動的,而應答方只有在收到請求後才能答覆,是被動的,如果沒有請求時不會有任何動作。
  5. 無狀態: 「狀態」其實就是客戶端或者伺服器里保存的一些數據或者標誌,記錄了通信過程中的一些變化信息。HTTP在整個協議里沒有規定任何的「狀態」,但不要忘了 HTTP 是「靈活可擴展」的,雖然標準里沒有規定「狀態」,但完全能夠在協議的框架里給它「打個補丁」,增加這個特性(cookie)。
  6. 明文傳輸: 「明文」意思就是協議里的報文(準確地說是 header 部分)不使用二進位數據,而是用簡單可閱讀的文本形式。
  7. 不安全: 安全有很多的方面,明文只是「機密」方面的一個缺點,在「身份認證」和「完整性校驗」這兩方面 HTTP 也是欠缺的。

HTTP的實體數據

數據類型

Accept

在TCP/IP協議棧里,數據的傳輸都是Header+body的形式。在傳輸層協議中,不需要關心數據是什麼,但在應用層必須要告訴上層數據的類型,否則上層就不知該如何處理。最早的HTTP協議中,並沒有附加的數據類型信息,所有傳送的數據都被客戶程序解釋為HTML文檔,而為了支持多媒體數據類型,HTTP協議中就使用了附加在文檔之前的MIME(Multipurpose Internet Mail Extensions 多用途網際網路郵件擴展類型)指定的數據類型信息來標識數據類型。MINE將數據分為七大類(video、image、application、text、audio、multipart、message),再以type/subtype的格式細分出其下的子類。例如我們常用到的text/html 、text/css 、image/jpeg 、 applaction/json等。

Accept-encoding

此外HTTP協議還制定了數據的壓縮格式:

  • gzip:GNU zip 壓縮格式,也是網際網路上最流行的壓縮格式;
  • deflate:zlib(deflate)壓縮格式,流行程度僅次於 gzip;
  • br:一種專門為 HTTP 優化的新壓縮算法(Brotli)。

Accept-Language

標記了客戶端可理解的自然語言,也允許用「,」做分隔符列出多個類型,例如:Accept-Language: zh-CN, zh, en

數據類型在請求頭中的表現

在 HTTP 協議里用 Accept、Accept-Encoding、Accept-Language 等請求頭欄位進行內容協商的時候,還可以用一種特殊的「q」參數表示權重來設定優先級,這裡的「q」是「quality factor」的意思。權重的最大值是 1,最小值是 0.01,默認值是 1,如果值是 0 就表示拒絕。具體的形式是在數據類型或語言代碼後面加一個「;」,然後是「q=value」。伺服器會在響應頭裡多加一個 Vary 欄位,記錄伺服器在內容協商時參考的請求頭欄位。

HTTP如何傳輸大文件

  • 數據壓縮
    前面提到的
    accept-encoding請求頭可以算是是一種傳輸大文件的解決方式,伺服器可以選擇一種瀏覽器支持的數據壓縮方式放進content-encoding響應頭裡,再把原數據壓縮後返回給客戶端。缺點是這種方式只對文本有較好地壓縮率,對於圖片音頻等本身就已經高度壓縮的多媒體數據束手無策。
  • 分塊傳輸
    在HTTP頭部表示為
    Transfer-Encoding: chunked,指報文里的body部分不是一次性發過來的,而是分為許多chunked分塊發送。Transfer-Encoding: chunkedContent-Length這兩個欄位是互斥的,也就是說響應報文里這兩個欄位不能同時出現,一個響應報文的傳輸要麼是長度已知,要麼是長度未知(chunked),這一點你一定要記住。
  • 範圍請求
    如果想獲取某個大文件其中的片段,分塊傳輸就沒辦法滿足這樣的需求。HTTP協議提出了範圍請求這樣的概念,允許客戶端只獲取文件的某一部分。客戶端先發個HEAD請求看看伺服器是否支持範圍請求,伺服器必須在
    Accept-Ranges響應頭中告知客戶端是否具有範圍請求的能力。請求頭Ranges是HTTP範圍請求的專用欄位,值的格式是bytes=x-y表示x ~ y之間的範圍。服務端在收到 Ranges請求頭時,首先驗證x-y的範圍是否合法(x和y可以省略,省略x則表示從後往前,省略y則表示從前往後),其次計算讀取偏移量,返回206狀態碼和所讀取的文件 ,最後在響應頭加上Content-Range表示實際返回的偏移量和總數,格式為bytes x-y/length
    範圍請求還支持在一個頭裡定義多個x-y,這種情況需要一種特殊的MIME類型multipart/byteranges,表示報文是有多段組成。

HTTP連接管理

http的通信過程採取請求/應答模式,在http0.9/1.0時期,每次發起請求都需要建立連接->發送數據->斷開連接,由於整個請求的過程非常短暫,早起的http也稱為短連結無連結的協議。由於TCP簡歷連接要經過三次握手四次揮手,整個過程需要3個RTT,而HTTP的一次簡單請求通常只需要2個RTT,那麼被浪費掉的時間有60%。

Connection:keep-alive

HTTP1.1提出了長連接的概念,也就是Keep-alive。在長連接上建立一次TCP連接可以發送多個HTTP請求。但因為連接是alive的,如果一直不關閉,就會占用大量的伺服器資源,導致服務無法及時響應真正的請求,所以我們也需要及時關閉連接。可以通過在客戶端請求頭添加Connection: close欄位主動關閉連接。服務端通常不會主動關閉連接,但我們也可以通過設置時長、請求數等方式約定斷開連接的條件。

隊頭阻塞

基於請求-應答模式的http協議,形成了串行的請求隊列(http1.1還提出了管道機制,即在同一個TCP連接上不用等待上一個請求的響應即可發出下個請求,不過客戶端還是按照正常順序接受響應,這種做法並沒帶來任何性能上的改善,所以默認保持關閉),如果隊首的請求處於阻塞狀態,那麼後面的請求也無法正常響應結果就是更長時間的性能浪費。

並發連接域名分片是對隊頭阻塞的針對性優化策略,瀏覽器限制每個客戶端可以並發建立6~8個連接,又可以將多個域名指向同一個伺服器,這樣實際的連接數量就更多了,是一種用數量解決質量的思路。

重定向

當我們在瀏覽器輸入一個url再按下回車,頁面跳轉到我們輸入的地址中,這種行為就是主動跳轉。瀏覽器還支持被動跳轉,也就是HTTP的重定向。

狀態碼

在前面了解過HTTP狀態碼,3XX即表示為重定向。下面詳細介紹下各個狀態碼的含義。

301指永久重定向,可能是域名下線,域名遷移等原因,原地址不再維護。此時瀏覽器在重定向的同時記錄重定向後的地址,下次訪問該域名就自動訪問新的URI了。

302指臨時重定向,可能是伺服器維護、臨時關閉等原因,臨時跳轉到新的地址上,此時瀏覽器不會記錄重定向的地址,認為原地址還是有效的,下次訪問時還是優先訪問原地址。

303類似 302,但要求重定向後的請求改為 GET 方法,訪問一個結果頁面,避免 POST/PUT 重複操作。

307類似 302,但重定向後請求里的方法和實體不允許變動,含義比 302 更明確。

308類似 307,不允許重定向後的請求變動,但它是 301「永久重定向」的含義。

可以在地址欄輸入bing.com,瀏覽器控制台中的狀態如下圖所示:

客戶端是如何處理重定向的

在瀏覽器地址欄輸入bing.con我們可以看到,狀態碼如下圖所示:

我們瀏覽器收到響應之後根據響應頭中的Location欄位判斷重定向的地址,然後進行被動跳轉。

雖然重定向的用途很廣,但是隨之而來的也有跟多問題。

  • 第一個問題是「性能損耗」。很明顯,重定向的機制決定了一個跳轉會有兩次請求 - 應答,比正常的訪問多了一次。雖然 301/302 報文很小,但大量的跳轉對伺服器的影響也是不可忽視的。站內重定向可以長連接復用,站外重定向就要開兩個連接。
  • 第二的問題時循環重定向,比如 A->B->C->A,當我們訪問A時就會發生無限跳轉。所以HTTP協議特別規定,瀏覽器必須具有檢測「循環跳轉」的能力,在發現這種情況時應當停止發送請求並給出錯誤提示。

cookie

HTTP 是「無狀態」的,這既是優點也是缺點。優點是伺服器沒有狀態差異,可以很容易地組成集群,而缺點就是無法支持需要記錄狀態的事務操作。好在 HTTP 協議是可擴展的,後來發明的 Cookie 技術,給 HTTP 增加了「記憶能力」。
cookie同樣存在於HTTP頭部欄位里。服務端可以使用
set-cookie標識客戶端身份,客戶端則在請求時攜帶cookie告訴服務端自己的信息。cookie欄位以key=value的格式保存,瀏覽器在一個cookie欄位里可以存放多對數據,用;分割。

Cookie 主要用於以下三個方面:

  • 會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)
  • 個性化設置(如用戶自定義設置、主題等)
  • 瀏覽器行為跟蹤(如跟蹤分析用戶行為等)

相關屬性

生存周期

Expires俗稱「過期時間」,用的是絕對時間點,可以理解為「截止日期」(deadline)。

Max-Age用的是相對時間,單位是秒,瀏覽器用收到報文的時間點再加上 Max-Age,就可以得到失效的絕對時間。

ExpiresMax-Age 可以同時出現,兩者的失效時間不一致時瀏覽器會優先採用Max-Age計算失效期。如果伺服器不設置Max-Age、Expries或者欄位值為0指不能緩存cookie,但在會話期間是可用的,瀏覽器會話關閉之前可以用cookie記錄用戶的信息。

作用域

DomainPath指定了 Cookie 所屬的域名和路徑,瀏覽器在發送 Cookie 前會從 URI 中提取出 host 和 path 部分,對比 Cookie 的屬性。如果不滿足條件,就不會在請求頭裡發送 Cookie。通常 Path 就用一個「/」或者直接省略,表示域名下的任意路徑都允許使用 Cookie。

安全性

HttpOnly表示此 Cookie 只能通過瀏覽器 HTTP 協議傳輸,禁止其他方式訪問。這也是預防「跨站腳本」(XSS)攻擊的有效手段。

SameSite可以防範「跨站請求偽造」(XSRF)攻擊,SameSite = strict表示禁止cookie在跳轉連結時跨域傳輸。SameSite = lax稍微寬鬆一點,允許在GETHEAD等安全請求方式中跨域攜帶。默認值為none,表示不限制cookie的攜帶和傳輸。

Secure表示這個cookie僅能用HTTPS協議加密傳輸,明文的HTTP協議會禁止發送。但Cookie本身不是加密的,瀏覽器里還是以明文的形式存在。

HTTP緩存控制

伺服器的緩存控制

瀏覽器在訪問頁面資源時首先會查找緩存數據,如果沒有再發送請求,向伺服器獲取資源;伺服器響應請求,返回資源,同時標記資源的有效期;瀏覽器緩存資源,等待下次重用。這就是客戶端緩存。

伺服器標記資源有效期使用的頭欄位是Cache-Control,裡面的值max-age=xxx就是資源的有效時間(與cookie的max-age不同,這裡的max-age時間的計算起點是響應報文的創建時刻)。

此外在響應報文里還可以用其他的值來更精確地指示瀏覽器應該如何使用緩存:no-store: 不允許緩存,用於某些變化非常頻繁的數據,例如秒殺頁面;no-cache: 可以緩存,但在使用之前必須要去伺服器驗證是否過期;must-revalidate: 如果緩存不過期就可以繼續使用,但過期了就必須去伺服器驗證。

客戶端的緩存控制

瀏覽器也可以發Cache-Control,也就是說請求 - 應答的雙方都可以用這個欄位進行緩存控制,互相協商緩存的使用策略。在瀏覽器前進、後退、重定向時cache-control就生效了,響應頭裡有from disk cache字樣,就說明瀏覽器未發送請求,而是直接使用了本地緩存。

條件請求

瀏覽器在刷新頁面時相當於在請求頭中添加了Cache-Control:no-cache,這樣在刷新頁面時,還是向服務端發送了請求,並沒有很好的利用到緩存。所以HTTP協議又定義了一系列「If」開頭的「條件請求」欄位,專門用來檢查驗證資源是否過期。

條件請求一共有 5 個頭欄位,我們最常用的是if-Modified-SinceIf-None-Match這兩個。需要第一次的響應報文預先提供Last-modified(最後修改時間)和ETag(資源唯一標識),然後第二次請求時就可以帶上緩存里的原值,驗證資源是否是最新的。如果資源沒有變,伺服器就回應一個「304 Not Modified」,表示緩存依然有效,瀏覽器就可以更新一下有效期,然後放心大膽地使用緩存了。

代理緩存

代理伺服器

代理伺服器就是客戶端和服務端之間的中間商,在中間的位置轉發上游的請求和下游的響應。代理伺服器在計算機領域有非常重要的功能

  • 負載均衡:面向客戶端時屏蔽原伺服器,代理伺服器可以通過輪詢、哈希等算法將流量分發,提高整體的性能。
  • 健康檢查:使用『心跳』等機制監控伺服器,保證伺服器的可用性。
  • 安全防護:保護被代理服務端的IP和流量,防止網絡攻擊或負載問題。
  • 加密卸載:對外和對內使用不同的加密策略,節省加密成本
  • 內容緩存:暫存/復位伺服器的響應。

緩存代理

HTTP的服務端緩存主要由代理伺服器來實現,代理伺服器收到源伺服器的響應之後將報文轉發給客戶端的同時也存入自己的cache里,下次再有相同的請求就可以直接發送304或者緩存數據,節省源伺服器的成本。

因為代理伺服器既是服務端,又是客戶端的特性,有一些特殊的cache-control屬性:

  • 服務端

private: 表示只能客戶端緩存,不允許代理伺服器上緩存。punlic:表示完全公開,客戶端和代理伺服器都可以緩存。proxy-revalidate:要求代理伺服器緩存過期後必須回源驗證。s-maxage: 代理伺服器緩存的有效期no-transform: 不允許代理伺服器轉換數據格式。

  • 客戶端

max-stale: 如果代理上的緩存過期了也可以接受,但不能過期太多,超過 x 秒也會不要。min-flash: 表示緩存少於x有效期就不要了。only-if-cached:表示只接受代理緩存的數據,不接受源伺服器的響應。如果代理上沒有緩存或者緩存過期,就應該給客戶端返回一個 504。

8. HTTPS

由於 HTTP 天生「明文」的特點,整個傳輸過程完全透明,任何人都能夠在鏈路中截獲、修改或者偽造請求 / 響應報文,數據不具有可信性。只有具有機密性、完整性、身份認證和不可否認性,我們才認為這個請求是安全的。HTTPS為HTTP增加了以上四個特性。

HTTPS實際上就指的是HTTP over TLS/SSl。是在原本的HTTP協議上加了一層TLS/SSL協議。

SSL/TLS

SSL 即安全套接層(Secure Sockets Layer),在 OSI 模型中處於第 5 層(會話層),由網景公司於 1994 年發明。SSL 發展到 v3 時已經證明了它自身是一個非常好的安全通信協議,於是在 1999 年它改名為 TLS(傳輸層安全, Transport Layer Security),目前應用的最廣泛的 TLS 是 1.2,而之前的協議(TLS1.1/1.0、SSLv3/v2)都已經被認為是不安全的。

機密性(基於TLS1.2)

SSL/TLS通過加密(encrypt)來傳輸密文(cipher text)保證數據傳輸的安全性,只有擁有密鑰(key)的人才能夠通過解密(decrypt)獲得明文(plain text/clear text),加密解密的操作過程就是加密算法。所以「密鑰」是一長串的數字,約定俗成的度量單位是「位」(bit)。比如,說密鑰長度是 128,就是 16 字節的二進位串,密鑰長度 1024,就是 128 字節的二進位串。按照密鑰的使用方式,加密可以分為兩大類:對稱加密非對稱加密

對稱加密

顧名思義,加密解密都使用相同的密鑰就叫做對稱加密。TLS里目前常用的有 AES 和 ChaCha20。

AES 的意思是「高級加密標準」(Advanced Encryption Standard),密鑰長度可以是 128、192 或 256。它是 DES 算法的替代者,安全強度很高,性能也很好,而且有的硬體還會做特殊優化,所以非常流行,是應用最廣泛的對稱加密算法。

ChaCha20 是 Google 設計的另一種加密算法,密鑰長度固定為 256 位,純軟體運行性能要超過 AES,曾經在移動客戶端上比較流行,但 ARMv8 之後也加入了 AES 硬體優化,所以現在不再具有明顯的優勢。

非對稱加密

對稱加密看上去很好的實現了機密性,但是還有一個問題就是如何安全的傳輸密鑰。因為在加密算法中,只要擁有密鑰就可以解密,如果密鑰在傳輸過程中被竊取,也就無機密性可言。為了解決這個問題,又有了非對稱加密算法。他擁有兩個密鑰,分別是公鑰(public key)私鑰(private key),公鑰是公開的,而私鑰是嚴格保密的。公鑰私鑰有個特別的「單向」性,雖然都可以用來加密解密,但公鑰加密後只能用私鑰解密,反過來,私鑰加密後也只能用公鑰解密。非對稱加密可以解決密鑰交換的問題。網站秘密保管私鑰,在網上任意分發公鑰,你想要登錄網站只要用公鑰加密就行了,密文只能由私鑰持有者才能解密。而黑客因為沒有私鑰,所以就無法破解密文。

非對稱加密算法的設計要比對稱算法難得多,在 TLS 里只有很少的幾種,比如 DH、DSA、RSA、ECC 等。RSA 可能是其中最著名的一個,幾乎可以說是非對稱加密的代名詞,它的安全性基於「整數分解」的數學難題,使用兩個超大素數的乘積作為生成密鑰的材料,想要從公鑰推算出私鑰是非常困難的。

ECC是非對稱加密里的「後起之秀」,它基於「橢圓曲線離散對數」的數學難題,使用特定的曲線方程和基點生成公鑰和私鑰,子算法 ECDHE 用於密鑰交換,ECDSA 用於數字簽名。相對RSA,ECC在安全和性能上都有更明顯的優勢,160位的ECC相當於1024位的RSA,260位的ECC相當於2048位的RSA。

混合加密

雖然非對稱加密沒有密鑰交換的難題,但因為它們都是基於複雜的數學難題,運算速度很慢,即使是 ECC 也要比 AES 差上好幾個數量級。所以目前TLS使用混合加密,使二者取長補短,既能高效加密解密,又能安全的進行數據傳輸。

在建立連接之初先使用非對稱加密的形式傳遞密鑰,然後用隨機數產生對稱算法使用的「會話密鑰」(session key),再用公鑰加密。因為會話密鑰很短,通常只有 16 字節或 32 字節,所以慢一點也無所謂。對方拿到密文後用私鑰解密,取出會話密鑰。這樣,雙方就實現了對稱密鑰的安全交換,後續就不再使用非對稱加密,全都使用對稱加密。

完整性

摘要算法

實現完整性的手段主要是摘要算法(Digest Algorithm),也就是常說的散列函數、哈希函數(Hash Function)。可以把摘要算法近似地理解成一種特殊的加密算法,它能夠把任意長度的數據加密成固定長度、而且獨一無二的「摘要」字符串,且不能從壓縮後的密文中推導出原文。MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1就是最常用的兩個摘要算法,能夠生成 16 字節和 20 字節長度的數字摘要。但這兩個算法的安全強度比較低,不夠安全,在 TLS 里已經被禁止使用了。目前TLS使用的是SLA-2。摘要算法保證了「數字摘要」和原文是完全等價的。所以,我們只要在原文後附上它的摘要,就能夠保證數據的完整性。不過摘要算法不具有機密性,所以真正的完整性還是需要建立在機密性之上。

身份認證&不可否認

數字簽名

數字簽名的原理其實很簡單,就是把公鑰私鑰的用法反過來,之前是公鑰加密、私鑰解密,現在是私鑰加密、公鑰解密。但又因為非對稱加密效率太低,所以私鑰只加密原文的摘要,這樣運算量就小的多,而且得到的數字簽名也很小,方便保管和傳輸。

數字證書和CA

因為公鑰是任何人都可以發布的,所以我們需要引入第三方來保證公鑰的可信度,這個「第三方」就是我們常說的 CA(Certificate Authority,證書認證機構),CA 對公鑰的簽名認證也是有格式的,要包含公鑰的序列號、用途、頒發者、有效時間等等,把這些打成一個包再簽名,完整地證明公鑰關聯的各種信息,形成「數字證書」(Certificate)。小一點的 CA 可以讓大 CA 簽名認證,但鏈條的最後,也就是 Root CA,就只能自己證明自己了,這個就叫「自簽名證書」(Self-Signed Certificate)或者「根證書」(Root Certificate)。你必須相信,否則整個證書信任鏈就走不下去了。

TLS1.2建立連接的過程

TLS協議的組成

記錄協議(Record Protocol): 規定了 TLS 收發數據的基本單位:記錄(record)。所有的其他子協議都需要通過記錄協議發出,但多個記錄數據可以在一個 TCP 包里一次性發出。

警報協議(Alert Protocol): 的職責是向對方發出警報信息,有點像是 HTTP 協議里的狀態碼。比如,protocol_version 就是不支持舊版本,bad_certificate 就是證書有問題,收到警報後另一方可以選擇繼續,也可以立即終止連接。

握手協議(Handshake Protocol): 是 TLS 里最複雜的子協議,要比 TCP 的 SYN/ACK 複雜的多,瀏覽器和伺服器會在握手過程中協商 TLS 版本號、隨機數、密碼套件等信息,然後交換證書和密鑰參數,最終雙方協商得到會話密鑰,用於後續的混合加密系統。變更密碼規範協議(Change Cipher Spec Protocol): 是一個「通知」,告訴對方,後續的數據都將使用加密保護。那麼反過來,在它之前,數據都是明文的。

基於ECDHE 的 TLS1.2 握手

TLS1.3

9. HTTP/2

HTTP1.X引入了Cookie解決了無狀態的問題、通過引入TLS/SSL解決了明文傳輸不安全的問題。那接下來HTTP2的發力點就放在性能層面了。Google首先發明了SPDY協議,隨後網際網路標準化組織IETF以SPDY 為基礎發布了HTTP2。HTTP2對於性能上的優化主要由以下幾點出發:1. 包頭過大 2. 隊頭阻塞 。

性能優化

包頭過大(頭部壓縮)

在HTTP1.x時期,很多請求請求體和響應體的大小遠遠小於頭部欄位的大小,比如GET請求,301/302/204響應。而且很多頭部欄位是重複的,HTTP/1.x浪費了大量的帶寬在傳輸重複的頭欄位上,所以,HTTP/2 把「頭部壓縮」作為性能改進的一個重點。

HPACK算法是專門為壓縮 HTTP 頭部定製的算法,與 gzip、zlib 等壓縮算法不同,它是一個「有狀態」的算法,需要客戶端和伺服器各自維護一份「索引表」,壓縮和解壓縮就是查表和更新表的操作。為了方便管理和壓縮,HTTP/2 廢除了原有的起始行概念,把起始行裡面的請求方法、URI、狀態碼等統一轉換成了頭欄位的形式, 為了與「真頭欄位」區分開來,這些「偽頭欄位」會在名字前加一個「:」,比如「:authority」 「:method」 「:status」,分別表示的是域名、請求方法和狀態碼。廢除了起始行里的版本號和錯誤原因短語。用索引號表示重複的字符串,還釆用哈夫曼編碼來壓縮整數和字符串,可以達到 50%~90% 的高壓縮率。

下面的這個表格列出了「靜態表」的一部分,這樣只要查表就可以知道欄位名和對應的值,比如數字「2」代表「GET」,數字「8」代表狀態碼 200。

新增的頭欄位或者值保存在動態表(Dynamic Table)里,它添加在靜態表後面,結構相同,但會在編碼解碼的時候隨時更新。比如說,第一次發送請求時的「user-agent」欄位長是一百多個字節,用哈夫曼壓縮編碼發送之後,客戶端和伺服器都更新自己的動態表,添加一個新的索引號「65」。那麼下一次發送的時候就不用再重複發那麼多字節了,只要用一個字節發送編號就好。

隊頭阻塞(二進位分幀、流式傳輸)

基於請求-應答模式的http協議存在隊頭阻塞的問題,前面提到的並發連接域名分片都是犧牲數量解決質量的思路。而HTTP2採用了二進位分幀➕流式傳輸的方式來解決這個問題。

二進位分幀

HTTP/2把原來的Header+Body的消息「打散」為數個小片的二進位「幀」(Frame),用HEADER幀存放頭數據、DATA幀存放實體數據。

流式傳輸

HTTP/2還定義了一個「流」(Stream)的概念,它是二進位幀的雙向傳輸序列,同一個消息往返的幀會分配一個唯一的流 ID。你可以把它想像成是一個虛擬的「數據流」,在裡面流動的是一串有先後順序的數據幀,這些數據幀按照次序組裝起來就是HTTP/1里的請求報文和響應報文。HTTP/2 可以在一個 TCP 連接上用「流」同時發送多個「碎片化」的消息,這就是常說的「多路復用」( Multiplexing),多個往返通信都復用一個連接來處理。在「流」的層面上看,消息是一些有序的「幀」序列,而在「連接」的層面上看,消息卻是亂序收發的「幀」。多個請求 / 響應之間沒有了順序關係,不需要排隊等待,也就不會再出現「隊頭阻塞」問題,降低了延遲,大幅度提高了連接的利用率。

幀開頭是幀長度(不包含報文頭的9個字節),默認上限是2^14,最大是2^24,也就是說 HTTP/2的幀通常不超過16K,最大是 16M。

後面的一個字節是幀類型,大致可以分成數據幀控制幀兩類,HEADERS幀DATA幀屬於數據幀,存放的是 HTTP 報文,而 SETTINGS、PING、PRIORITY 等則是用來管理流的控制幀。

第 5 個字節是非常重要的幀標誌信息,可以保存 8 個標誌位,攜帶簡單的控制信息。常用的標誌位有 END_HEADERS 表示頭數據結束,END_STREAM 表示單方向數據發送結束(即 EOS,End of Stream)。

報文頭裡最後 4 個字節是流標識符,也就是幀所屬的「流」,接收方使用它就可以從亂序的幀里識別出具有相同流 ID 的幀序列(在 HTTP/2 連接上,雖然幀是亂序收發的,但只要它們都擁有相同的流 ID,就都屬於一個流,而且在這個流里幀不是無序的,而是有著嚴格的先後順序。),按順序組裝起來就實現了虛擬的「流」。流標識符雖然有 4 個字節,但最高位被保留不用,所以只有 31 位可以使用,也就是說,流標識符的上限是 2^31,大約是 21 億。

流的特點

  • 流是可並發的,一個 HTTP/2 連接上可以同時發出多個流傳輸數據,也就是並發多請求,實現「多路復用」;
  • 客戶端和伺服器都可以創建流,雙方互不干擾;
  • 流是雙向的,一個流裡面客戶端和伺服器都可以發送或接收數據幀,也就是一個「請求 - 應答」來回;
  • 流之間沒有固定關係,彼此獨立,但流內部的幀是有嚴格順序的;
  • 流可以設置優先級,讓伺服器優先處理,比如先傳 HTML/CSS,後傳圖片,優化用戶體驗;
  • 流 ID 不能重用,只能順序遞增,客戶端發起的 ID 是奇數,伺服器端發起的 ID 是偶數;
  • 在流上發送「RST_STREAM」幀可以隨時終止流,取消接收或發送;
  • 第 0 號流比較特殊,不能關閉,也不能發送數據幀,只能發送控制幀,用於流量控制。

協議棧

10.HTTP/3

HTTP/2 雖然使用「幀」「流」「多路復用」,沒有了「隊頭阻塞」,但這些手段都是在應用層里,而在 TCP 協議里,還是會發生「隊頭阻塞」。Google 在推 SPDY 的時候就已經意識到了這個問題,於是就又發明了一個新的QUIC協議,讓 HTTP 跑在 QUIC 上而不是 TCP 上。而這個HTTP over QUIC就是 HTTP 協議的下一個大版本,HTTP/3。它在 HTTP/2 的基礎上又實現了質的飛躍,真正完美地解決了隊頭阻塞問題。

QUICK

QUIC 基於 UDP,而 UDP 是「無連接」的,不需要「握手」和「揮手」,所以天生就要比 TCP 快。QUIC 全面採用加密通信,它使用自己的幀「接管」了 TLS 里的「記錄」,握手消息、警報消息都不使用 TLS 記錄,直接封裝成 QUIC 的幀發送,省掉了一次開銷。QUIC 的基本數據傳輸單位是包(packet)和幀(frame),一個包由多個幀組成,包面向的是「連接」,幀面向的是「流」。

QUIC 使用不透明的「連接 ID」來標記通信的兩個端點,客戶端和伺服器可以自行選擇一組 ID 來標記自己,這樣就解除了 TCP 里連接對「IP 地址 + 埠」(即常說的四元組)的強綁定,支持「連接遷移」(Connection Migration)。

HTTP/3

因為 QUIC 本身就已經支持了加密、流和多路復用,所以HTTP/3不需要定義流,而是直接使用 QUIC 的流。由於流管理被「下放」到了 QUIC,所以 HTTP/3 里幀的結構也變簡單了。幀頭只有兩個欄位:類型和長度,而且同樣都採用變長編碼,最小只需要兩個字節。

來自公眾號:騰訊技術工程作者:yutingbai,騰訊 PCG 前端開發工程師

關鍵字: