TCP|你真的懂 HTTP 嗎?

samzhangjy發表於2022-11-23

前言

Hello 大家好,我是 Sam Zhang。

HTTP 相信是每個 Web 開發者都耳熟能詳的名詞了。但是,新手開發者想要完全理解 HTTP 協議卻需要時間。這期影片,我就來帶大家入門 HTTP 協議。

話不多說,我們直接進入正題!

TCP / IP 模型

在真正講解 HTTP 協議之前,我們先來看一下更為底層的 TCP / IP 模型。

TCP / IP 模型的四個層次

TCP / IP 模型

正如大家所看到的,TCP / IP 模型分為四層,由下到上依次是:

  1. 鏈路層(Link Layer)
  2. 網路層(Internet Layer)
  3. 傳輸層(Transport Layer)
  4. 應用層(Application Layer)

鏈路層

鏈路層處於 TCP / IP 模型的最底層。它負責管理資料如何與物理媒介進行互動,例如 Wi-Fi 定義了資料如何進行無線傳輸。

網路層

鏈路層只能在區域網中工作,而網路層定義了 IP 協議,主要負責定址操作。

傳輸層

傳輸層主要負責提供應用程式介面,為應用程式提供接入網路的途徑。除此以外,這一層還出現了埠,也就是客戶端與伺服器連線的頻道。

最主要的是,這一層有可靠傳輸的 TCP 協議。透過 TCP 協議傳輸的資料都能保證按照順序抵達,內容正確且沒有重複。大多數網路請求都是透過 TCP 傳輸的。

應用層

這一層包括了主要為應用軟體使用的協議,例如負責檔案傳輸的 FTP 協議,負責電子郵件的 SMTP 協議和負責域名系統的 DNS 等。最經常出現的 HTTP / HTTPS 協議也是在這一層下的,在瀏覽器中使用非常廣泛。WebSocket 協議也同屬於這一層。

HTTP / HTTPS 協議

基礎概念

A Web document is the composition of different resources

HTTP(HyperText Transfer Protocol)是全球資訊網(World Wide Web)的基礎協議。自 Tim Berners-Lee 博士和他的團隊在1989-1991年間創造出它以來,HTTP已經發生了太多的變化,在保持協議簡單性的同時,不斷擴充套件其靈活性。如今,HTTP已經從一個只在實驗室之間交換檔案的早期協議進化到了可以傳輸圖片,高解析度影片和3D效果的現代複雜網際網路協議。

HTTP 從 HTTP / 0.9 的單行協議演變到現在 HTTP / 2 的資料幀,HTTP 協議在不斷地變化並發展著。這個最初從 CERN 實驗室中走出來的小東西,最終被幾乎所有人每天每刻地使用著。這,就是我們今天的主角 —— HTTP 協議。

HTTP 是一個 client-server 協議。也就是說,請求由客戶端發出,被伺服器處理後返回一個響應。在這之間,可能存在著許多代理 - 例如閘道器。這些代理是建立在應用層上的。代理的作用包括快取,過濾,負載均衡等功能。

HTTP 設計理念

HTTP 是簡單的

HTTP 的設計理念是簡單易讀 —— 這就使得 HTTP 報文能被人類讀懂,而不只是機器。

HTTP 是可擴充套件的

請求頭的存在使得擴充套件 HTTP 變得更簡單了。只要服務端和客戶端對某一個 header 達成一致的語義,那麼這個新 header 的功能就可以很輕鬆被接入進來。

HTTP 是無狀態,有會話的

HTTP 本身是無狀態的。即使在同一連線中,兩個請求之間也是沒有關係的。但是,透過 Cookies 建立一個會話,就能夠讓請求共享同樣的上下文資訊。

也就是說,HTTP 本身是無狀態的,但因為 Cookies ,你可以在 HTTP 中建立有狀態的會話。

HTTP 和連線

HTTP 在底層使用 TCP 傳輸協議,而不是 UDP 協議 —— 因為 TCP 是可靠的,不丟失訊息的。

資料 URL

資料 URL 是一種特殊的 URL 。他使用 data: 協議,允許嵌入小檔案。

它的定義形式如下:

data:[<mediatype>][;base64],<data>

其中 mediatype 是一個 MIME 型別的字串,用於判斷資料的型別。預設為 text/plain;charset=US-ASCII ,即 ASCII 編碼的純文字文件。

除此以外,你還可以選擇使用 base64 編碼來儲存資料。這在儲存二進位制檔案(例如影片和圖片)時非常有用。加入 ;base64 識別符號即可儲存 base64 資料。

例如,data:,Hello%20World! 就是一個純文字型別資料,而 data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D 則是剛剛 Hello World 的 base64 形式。

除此以外,還有幾點注意事項:

  1. 雖然 Firefox 支援無限長度的 data URLs,但是標準中並沒有規定瀏覽器必須支援任意長度的 dataURIs。比如,Opera 11瀏覽器限制 URLs 最長為 65535 個字元,這意味著 data URLs 最長為 65529 個字元。
  2. 因為資料 URL 是沒有結束標記的,所以如果你嘗試使用 Query String 新增查詢字串會導致瀏覽器認為加入的查詢字串也是資料的一部分。
  3. data URL 的格式雖然很簡單,但 <data> 前面的逗號 , 卻很容易被人忽略,從而導致錯誤。

MIME 型別

MIME(Multipurpose Internet Mail Extensions)型別,又稱媒體型別,用來表示特定資料的性質和格式。

一個 MIME 型別的結構很簡單:

type/subtype

其中 type 代表類別,subtype 代表子類別。注意,MIME 型別中不允許空格存在。

雖然 MIME 型別不區分大小寫,但傳統寫法都是小寫。

常見的型別有 text , image , audio , video , application 等。每個類別下還有不同的子類別,可謂種類繁多,絕對能找到你想要的。

剛才說的那幾個型別都是獨立型別,還有另外一種 Multipart 型別。它通常在 HTML 表單中出現,型別是 multipart/form-data

更詳細的 MIME 型別表可以在 MDN 中找到。

請求

一個典型的 HTTP 請求應該包括以下幾個部分:

  1. 請求行
  2. 請求頭
  3. 請求體,如果有的話

請求行

HTTP 請求的啟始行叫做請求行。它由下面幾個部分組成:

<命令> <路徑> <版本>

例如:GET /user/register.html HTTP/1.1

  1. <命令> :是 HTTP 請求方式的其中之一,常見的有 GETPOSTPUT 等。
  2. <路徑> :要獲取的資源等路徑,通常是上下文中就很明顯的元素資源的 URL 。需要注意的是,它沒有 protocolhttps://),domainsamzhangjy.github.io),抑或是 TCP 協議的 port80)。
  3. <版本> :HTTP 協議版本號。

請求頭

請求頭是一行不區分大小寫的字串。它遵循如下規範:

<請求頭名稱>: <請求頭值>

舉個例子:

Accept-Language: zh-CN, en-US

這個 header 表示客戶端接受簡體中文和英文。HTTP 協議支援許多不同的請求頭,大致分類有:

  • General headers:同時適用於請求和響應訊息,但與最終訊息主體中傳輸的資料無關的訊息頭。
  • Request headers:包含更多有關要獲取的資源或客戶端本身資訊的訊息頭。
  • Response headers:包含有關響應的補充資訊,如其位置或伺服器本身(名稱和版本等)的訊息頭。
  • Entity headers:包含有關實體主體的更多資訊,比如主體長度 (Content-Length) 或其 MIME 型別。

完整的列表可在 MDN 檢視。

請求體

不是所有的請求都必須有請求體:獲取資料的請求,例如 GET , HEAD , DELETE , OPTIONS 等,通常都不需要請求體。某些請求可能需要上傳資料到伺服器(最典型的例子就是 POST 請求),就需要有請求體來裝載資料,例如 FormData

請求體可以大致分為兩種:

  • Single-resource bodies,由一個單檔案組成。該型別的請求體由兩個 header 定義:Content-TypeContent-Length
  • Multiple-resouce bodies,由多部分 body 組成,每一部分包含不同的資訊位,通常和 HTML 表單資料結合在一起。

響應

HTTP 響應,跟 HTTP 請求一樣,分為三個部分:狀態行,響應頭和響應體。

狀態行

HTTP 響應的第一行就是狀態行(status line)。它的模式是:

<版本> <狀態碼> <狀態文字>

例如:HTTP/1.1 200 OK

  1. <版本> :協議版本,通常情況下是 HTTP/1.1
  2. <狀態碼> :表明請求的狀態,成功還是失敗等。常見的有 200 ,404 ,403 等。
  3. <狀態文字> :一條描述當前狀態碼的簡短明確的文字。

響應頭

跟 HTTP 請求頭的格式完全一樣,HTTP 響應頭也不區分大小寫並使用逗號和冒號分割資料。整個 header(包括其值)表現為單行形式。

有許多響應頭可用,這些響應頭可以分為幾組:

  • General headers,例如 Via,適用於整個報文。
  • Response headers,例如 VaryAccept-Ranges,提供其它不符合狀態行的關於伺服器的資訊。
  • Entity headers,例如 Content-Length,適用於請求的 body。顯然,如果請求中沒有任何 body,則不會傳送這樣的標頭檔案。

更多響應頭可以在 MDN 找到。

響應體

響應體是可選的,例如有著 HTTP 狀態碼 201 或 204 的請求大多數情況下不會有響應體。

常用的 Single-resource bodies 響應體可分為兩類:

  • 由已知長度的單個檔案構成的,由 Content-TypeContent-Length 響應頭定義。
  • 由未知長度的單個檔案構成的,透過將 Transfer-Encoding 設為 chunked 來使用 chunks 編碼傳輸。

屬此以外,還有 Multiple-resource bodies ,由多部分響應體組成,每部分包含不同的資訊段,但十分少見。

HTTP / 2

HTTP / 2 修復了 HTTP / 1.1 的許多效能問題:例如在 HTTP / 2 中,可以壓縮 header 了。如果你想用 HTTP / 2 協議,你完全不需要更改你的任何 API - 當使用者的瀏覽器和你的伺服器都支援 HTTP / 2 時,它會自動取代 HTTP / 1.1 。

如果你想要了解 HTTP / 2 的具體最佳化,請參考 MDN

認證

img

一個初始 HTTP 請求是不包括任何認證資訊的,可以被認為是一個匿名請求。當伺服器接收到此請求且客戶端要訪問的路由是受保護的,伺服器就會返回 401 狀態碼,並使用 WWW-Authenticate 響應頭告知客戶端需要進行相應的認證。下面是它的語法形式:

WWW-Authenticate: <type> realm=<realm>

其中,<type> 表示驗證方案(例如 Basic )。<realm> 是一段用於描述進行保護區域的文字,例如 Access to the staging site ,使使用者能夠知曉他們正在訪問的空間。

這時,當客戶端(瀏覽器)接收到此訊息後,會彈出密碼框,讓使用者輸入密碼。輸入完成後,客戶端將所獲的認證資訊透過 Authorization 請求頭傳回伺服器。

伺服器驗證當前認證資訊後,會根據實際情況返回資料。

一個最基礎的 HTTP 認證方式是 Basic 認證方式(基本驗證方案)。

Basic Authentication

基本驗證方案使用使用者的 ID 或密碼作為認證資訊,並使用 base64 演算法對其進行編碼。

但是,由於 base64 演算法的可逆性,基本驗證方案並不安全。如果在非 HTTPS 的請求中使用會導致資訊洩漏等安全風險。由此,Basic 認證方式不應該用來傳輸敏感的資訊。

快取

HTTP 快取允許使用者複用之前的資源來顯著提高效能。

當快取發現當前請求已經被儲存,就會自動攔截請求並返回儲存的資源。這跳過了請求源伺服器的步驟,大大提升了訪問速度。However,不是所有的快取都是永久不變的:我們需要對快取的截止時間作出相應的調整,使其不快取過期的資源。

快取分為兩種,一種是私有的瀏覽器快取,另一種是共享的代理快取。

瀏覽器快取,顧名思義,就是將快取直接儲存在瀏覽器中。而代理快取,就是在目標伺服器和使用者之間新增一個代理。它將會把經常訪問的資源快取,並以此減少網路擁堵。

雖然 HTTP 快取不是必須的,但重用快取的資源通常是必要的。常見的 HTTP 快取只能儲存 GET 請求的相應,對其他型別的請求稍顯無能為力。

客戶端可以透過設定 Cache-Control 請求頭來控制快取。

Cache-Control 的常見取值由 no-store , no-cache , max-age=<seconds> , must-revalidate , private , public 等。

其中,no-store 代表不進行任何快取,no-cache 代表快取但重新驗證是否過期,max-age 代表快取保持 fresh 狀態的最長時間。

更詳細的快取的資訊可以在 MDN 找到。

Cookie

正如我們一開始所說的,HTTP 是無狀態的。但是我們可以透過 Cookies 建立有狀態的會話。

服務端可以直接使用 Set-Cookie 響應頭來給客戶端傳送 Cookie 資訊。一個最基礎的 Cookie 可以這樣設定:

Set-Cookie: <name>=<value>

客戶端會將伺服器給定的值儲存到本地。Cookies 一旦儲存,每次請求客戶端都會傳送 Cookies 請求頭給伺服器:

Cookie: <name1>=<value1>; <name2>=<value2>; ...

Cookies 的生命週期

Cookie 的生命週期可以透過 ExpiresMax-Age 設定。E.g:

Set-Cookie: fruit=orange; Expires=Wed, 21 Oct 2022 07:28:00 GMT;

更多資訊請參考 MDN

參考資料

文件

BGM

相關文章