一文帶你深入瞭解 HTTP

http 的發展史

在學習網絡之前,瞭解它的歷史能夠幫助我明白爲何它會發展爲如今這個樣子,能讓我有探究它的興趣。下面的這張圖片就展示了 “互聯網” 誕生至今的發展歷程

http 是什麼?

HyperTextTransferProtocol 直譯爲 “超文本傳輸協議”。

  1. 超文本:指文字、圖片、視頻、音頻等的混合體,比如最熟悉的 html。

  2. 傳輸:http 是一個 “雙向協議”,傳輸的是請求方和響應方之間的數據,不限制請求方和響應方之間的角色,傳遞的過程中可以存在任意 “中間人”。

  3. 協議:協是兩個或多個參與者之間的交流,議是指對參與者之間的約定和規範。所以,http 協議可以理解爲作用在計算機之間,使用計算機能夠理解的語言確立計算機之間交流通信的規範,以及相關的各種控制和錯誤處理方式。

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

與 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 主要有三個基本的部分構成:

  1. 協議名:即訪問該資源應當使用的協議

  2. 主機名:即互聯網上主機的標記,可以是域名或 IP 地址

  3. 路徑:即資源在主機上的位置,使用 “/” 分隔多級目錄

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

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

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

  1. 匿名代理:完全 “隱匿” 了被代理的機器,外界看到的只是代理服務器;

  2. 透明代理:顧名思義,它在傳輸過程中是 “透明開放” 的,外界既知道代理,也知道客戶端;

  3. 正向代理:靠近客戶端,代表客戶端向服務器發送請求;

  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 協議族進行網絡通信時,會通過分層順序與對方進行通信(發送端從應用層往下走,接收端從應用層往上走)。

域名

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

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

DNS

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

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

  1. 根域名服務器(Root DNS Server):管理頂級域名服務器,返回 "com","net","cn" 等頂級域名服務器的 IP 地址。

  2. 頂級域名服務器(Top-level DNS Server):管理各自域名下的權威域名服務器,比如 cn 頂級域名服務器可以返回 123.cn 域名服務器的 IP 地址。

  3. 權威域名服務器(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 端把請求分發到不同的服務器,實現負載均衡。

HTTP/1.X

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

(一)HTTP 報文

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

  1. 起始行(start line):描述請求或響應的基本信息;

  2. 頭部字段集合(header):使用 key-value 形式更詳細地說明報文;

  3. 消息正文(entity):實際傳輸的數據,它不一定是純文本,可以是圖片、視頻等二進制數據。

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

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

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

  1. 字段名不區分大小寫,例如 “Host” 也可以寫成“host”,但首字母大寫的可讀性更好。

  2. 字段名裏不允許出現空格,可以使用連字符 “-”,但不能使用下劃線“_”。例如,“test-name” 是合法的字段名,而 “test name”“test_name” 是不正確的字段名。

  3. 字段名後面必須緊接着 “:”,不能有空格,而“:” 後的字段值前可以有多個空格。

  4. 字段的順序是沒有意義的,可以任意排列不影響語義。

  5. 字段原則上不能重複,除非這個字段本身的語義允許,例如 Set-Cookie。

(二)HTTP 請求方法

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

1.GET:獲取資源,可以理解爲讀取或者下載數據。

2.HEAD:獲取資源的元信息。

3.POST:向資源提交數據,相當於寫入或上傳數據。

4.PUT:類似 POST。

5.DELETE:刪除資源。

6.CONNECT:建立特殊的連接隧道。

7.OPTIONS:列出可對資源實行的方法。

8.TRACE:追蹤請求 - 響應的傳輸路徑。

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

GET 和 HEAD

  1. GET 適用於向服務器請求資源,一般將數據攜帶於 url 上。

  2. HEAD 類似於簡化版的 GET 請求,服務端收到 HEAD 請求時只返回響應頭並且響應頭與 GET 完全一致。

POST 和 PUT

  1. POST 適用於向服務端發送數據,將數據攜帶在 body 當中,通常表示的是 “create” 的含義。

  2. 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××

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 如何傳輸大文件

1. 數據壓縮

前面提到的 accept-encoding 請求頭可以算是是一種傳輸大文件的解決方式,服務器可以選擇一種瀏覽器支持的數據壓縮方式放進 content-encoding 響應頭裏,再把原數據壓縮後返回給客戶端。缺點是這種方式只對文本有較好地壓縮率,對於圖片音頻等本身就已經高度壓縮的多媒體數據束手無策。

2. 分塊傳輸
在 HTTP 頭部表示爲 Transfer-Encoding: chunked, 指報文裏的 body 部分不是一次性發過來的,而是分爲許多 chunked 分塊發送。Transfer-Encoding: chunked 和 Content-Length 這兩個字段是互斥的,也就是說響應報文裏這兩個字段不能同時出現,一個響應報文的傳輸要麼是長度已知,要麼是長度未知(chunked),這一點你一定要記住。

3. 範圍請求
如果想獲取某個大文件其中的片段,分塊傳輸就沒辦法滿足這樣的需求。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%。

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 協議特別規定,瀏覽器必須具有檢測 “循環跳轉” 的能力,在發現這種情況時應當停止發送請求並給出錯誤提示。

HTTP 是 “無狀態” 的,這既是優點也是缺點。優點是服務器沒有狀態差異,可以很容易地組成集羣,而缺點就是無法支持需要記錄狀態的事務操作。好在 HTTP 協議是可擴展的,後來發明的 Cookie 技術,給 HTTP 增加了“記憶能力”。

cookie 同樣存在於 HTTP 頭部字段裏。服務端可以使用 set-cookie 標識客戶端身份,客戶端則在請求時攜帶 cookie 告訴服務端自己的信息。cookie 字段以 key=value 的格式保存,瀏覽器在一個 cookie 字段裏可以存放多對數據,用; 分割。

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

  1. 會話狀態管理(如用戶登錄狀態、購物車、遊戲分數或其它需要記錄的信息)

  2. 個性化設置(如用戶自定義設置、主題等)

  3. 瀏覽器行爲跟蹤(如跟蹤分析用戶行爲等)

生存週期

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

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

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

作用域

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

安全性

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

SameSite 可以防範 “跨站請求僞造”(XSRF)攻擊,SameSite = strict 表示禁止 cookie 在跳轉鏈接時跨域傳輸。SameSite = lax 稍微寬鬆一點,允許在 GET、HEAD 等安全請求方式中跨域攜帶。默認值爲 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-Since 和 If-None-Match 這兩個。需要第一次的響應報文預先提供 Last-modified(最後修改時間)和 ETag(資源唯一標識),然後第二次請求時就可以帶上緩存裏的原值,驗證資源是否是最新的。如果資源沒有變,服務器就回應一個 “304 Not Modified”,表示緩存依然有效,瀏覽器就可以更新一下有效期,然後放心大膽地使用緩存了。

代理服務器
代理服務器就是客戶端和服務端之間的中間商,在中間的位置轉發上游的請求和下游的響應。代理服務器在計算機領域有非常重要的功能:
  1. 負載均衡:面向客戶端時屏蔽原服務器,代理服務器可以通過輪詢、哈希等算法將流量分發,提高整體的性能。

  2. 健康檢查:使用‘心跳’等機制監控服務器,保證服務器的可用性。

  3. 安全防護:保護被代理服務端的 IP 和流量,防止網絡攻擊或負載問題。

  4. 加密卸載:對外和對內使用不同的加密策略,節省加密成本

  5. 內容緩存:暫存 / 復位服務器的響應。

緩存代理

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

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

  1. 服務端

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

  1. 客戶端

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

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)都已經被認爲是不安全的。

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 建立連接的過程

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

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

握手協議(Handshake Protocol): 是 TLS 裏最複雜的子協議,要比 TCP 的 SYN/ACK 複雜的多,瀏覽器和服務器會在握手過程中協商 TLS 版本號、隨機數、密碼套件等信息,然後交換證書和密鑰參數,最終雙方協商得到會話密鑰,用於後續的混合加密系統。

變更密碼規範協議(Change Cipher Spec Protocol): 是一個 “通知”,告訴對方,後續的數據都將使用加密保護。那麼反過來,在它之前,數據都是明文的。

(三)TLS1.3

HTTP/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 億。

流的特點
  1. 流是可併發的,一個 HTTP/2 連接上可以同時發出多個流傳輸數據,也就是併發多請求,實現 “多路複用”。

  2. 客戶端和服務器都可以創建流,雙方互不干擾。

  3. 流是雙向的,一個流裏面客戶端和服務器都可以發送或接收數據幀,也就是一個 “請求 - 應答” 來回。

  4. 流之間沒有固定關係,彼此獨立,但流內部的幀是有嚴格順序的。

  5. 流可以設置優先級,讓服務器優先處理,比如先傳 HTML/CSS,後傳圖片,優化用戶體驗。

  6. 流 ID 不能重用,只能順序遞增,客戶端發起的 ID 是奇數,服務器端發起的 ID 是偶數。

  7. 在流上發送 “RST_STREAM” 幀可以隨時終止流,取消接收或發送。

  8. 第 0 號流比較特殊,不能關閉,也不能發送數據幀,只能發送控制幀,用於流量控制。

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 裏幀的結構也變簡單了。幀頭只有兩個字段:類型和長度,而且同樣都採用變長編碼,最小隻需要兩個字節。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/pDEkcYN31jNg7LuFXUwn4w