HTTP 訊息

HTTP 訊息是 HTTP 協議中用於在伺服器和客戶端之間交換資料的機制。訊息有兩種型別:客戶端傳送的請求,用於觸發伺服器上的操作;以及伺服器為響應請求而傳送的響應

開發者很少(如果不是從不)從零開始構建 HTTP 訊息。瀏覽器、代理或 Web 伺服器等應用程式使用專門設計的軟體以可靠且高效的方式建立 HTTP 訊息。訊息的建立或轉換透過瀏覽器中的 API、代理或伺服器的配置檔案或其他介面進行控制。

在 HTTP/2 及更早的 HTTP 協議版本中,訊息是基於文字的,熟悉格式後相對容易閱讀和理解。在 HTTP/2 中,訊息被封裝在二進位制幀中,這使得它們稍微難以閱讀。然而,協議的底層語義是相同的,因此您可以根據 HTTP/1.x 訊息的文字格式來學習 HTTP 訊息的結構和含義,並將這種理解應用於 HTTP/2 及更高版本。

本指南使用 HTTP/1.1 訊息以增強可讀性,並使用 HTTP/1.1 格式解釋 HTTP 訊息的結構。我們將在最後一節重點介紹您可能需要描述 HTTP/2 的一些差異。

注意:您可以在瀏覽器的開發者工具的網路選項卡中檢視 HTTP 訊息,或者在使用諸如 curl 等 CLI 工具將 HTTP 訊息列印到控制檯時檢視。

HTTP 訊息的組成

為了理解 HTTP 訊息如何工作,我們將研究 HTTP/1.1 訊息並檢查其結構。以下插圖顯示了 HTTP/1.1 中的訊息是什麼樣的

Requests and responses share a common structure in HTTP

請求和響應都具有相似的結構

  1. 一條起始行是一行,描述了 HTTP 版本以及請求方法或請求結果。
  2. 一組可選的HTTP 標頭,包含描述訊息的元資料。例如,對資源的請求可能包含該資源允許的格式,而響應可能包含指示實際返回格式的標頭。
  3. 一個空行,表示訊息的元資料已完成。
  4. 一個可選的正文,包含與訊息關聯的資料。這可能是請求中傳送到伺服器的 POST 資料,或者是響應中返回給客戶端的某些資源。訊息是否包含正文由起始行和 HTTP 標頭決定。

HTTP 訊息的起始行和標頭統稱為請求的頭部,而其後包含其內容的部分稱為正文

HTTP 請求

讓我們看一個使用者提交網頁表單後傳送的以下 HTTP POST 請求示例

http
POST /users HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49

name=FirstName+LastName&email=bsmth%40example.com

HTTP/1.x 請求中的起始行(上例中的POST /users HTTP/1.1)稱為“請求行”,由三部分組成

http
<method> <request-target> <protocol>
<方法>

HTTP 方法(也稱為HTTP 動詞)是一組定義的單詞之一,描述了請求的含義和期望的結果。例如,GET 表示客戶端希望接收一個資源作為返回,而 POST 表示客戶端正在向伺服器傳送資料。

<請求目標>

請求目標通常是絕對或相對的 URL,並由請求的上下文來描述。請求目標的格式取決於所使用的 HTTP 方法和請求上下文。下面 請求目標 部分對此進行了更詳細的描述。

<協議>

HTTP 版本,它定義了剩餘訊息的結構,作為響應預期使用的版本的指示器。這幾乎總是 HTTP/1.1,因為 HTTP/0.9HTTP/1.0 已過時。在 HTTP/2 及更高版本中,協議版本不包含在訊息中,因為它從連線設定中即可得知。

請求目標

描述請求目標有幾種方法,但最常見的是“源形式”。以下是目標型別及其使用場景的列表

  1. 源形式中,接收方將絕對路徑與 Host 標頭中的資訊結合起來。查詢字串可以附加到路徑中以獲取額外資訊(通常採用 key=value 格式)。這與 GETPOSTHEADOPTIONS 方法一起使用

    http
    GET /en-US/docs/Web/HTTP/Guides/Messages HTTP/1.1
    
  2. 絕對形式是一個完整的 URL,包括許可權,與 GET 一起使用,當連線到代理時

    http
    GET https://mdn.club.tw/en-US/docs/Web/HTTP/Guides/Messages HTTP/1.1
    
  3. 權威形式是權威和埠用冒號 (:) 分隔。它僅與 CONNECT 方法一起使用,用於建立 HTTP 隧道時

    http
    CONNECT developer.mozilla.org:443 HTTP/1.1
    
  4. 星號形式僅與 OPTIONS 一起使用,當您想要表示整個伺服器 (*) 而不是命名資源時

    http
    OPTIONS * HTTP/1.1
    

請求頭

頭是與請求一起傳送的元資料,位於起始行之後和正文之前。在上面的表單提交示例中,它們是訊息的以下幾行

http
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 49

在 HTTP/1.x 中,每個標頭都是一個不區分大小寫的字串,後跟一個冒號 (:) 和一個值,其格式取決於標頭。整個標頭,包括值,由一行組成。在某些情況下,例如 Cookie 標頭,此行可能非常長。

Example of headers in an HTTP request

有些標頭專門用於請求,而其他標頭可以在請求和響應中傳送,或者可能具有更具體的分類

  • 請求頭為請求提供額外的上下文,或向伺服器新增額外的邏輯來處理請求(例如,條件請求)。
  • 如果訊息有正文,則會發送表示頭,它們描述訊息資料的原始形式以及應用的任何編碼。這允許接收方理解如何在資源透過網路傳輸之前重新構建它。

請求體

請求正文是請求中向伺服器傳遞資訊的部分。只有 PATCHPOSTPUT 請求才具有正文。在表單提交示例中,這部分是正文

http
name=FirstName+LastName&email=bsmth%40example.com

表單提交請求中的正文包含相對少量的資訊,以 key=value 對的形式,但請求正文可能包含伺服器期望的其他型別的資料

json
{
  "firstName": "Brian",
  "lastName": "Smith",
  "email": "bsmth@example.com",
  "more": "data"
}

或多部分資料

http
--delimiter123
Content-Disposition: form-data; name="field1"

value1
--delimiter123
Content-Disposition: form-data; name="field2"; filename="example.txt"

Text file contents
--delimiter123--

HTTP 響應

響應是伺服器為回覆請求而傳送回的 HTTP 訊息。響應讓客戶端知道請求的結果是什麼。這是一個 HTTP/1.1 響應 POST 請求的示例,該請求建立了一個新使用者

http
HTTP/1.1 201 Created
Content-Type: application/json
Location: http://example.com/users/123

{
  "message": "New user created",
  "user": {
    "id": 123,
    "firstName": "Example",
    "lastName": "Person",
    "email": "bsmth@example.com"
  }
}

起始行(上面是 HTTP/1.1 201 Created)在響應中稱為“狀態行”,包含三部分

http
<protocol> <status-code> <reason-phrase>
<協議>

訊息的HTTP 版本

<狀態碼>

一個數字狀態碼,指示請求是成功還是失敗。常見的狀態碼有200404302

<原因短語> 可選

狀態碼後面的可選文字是對狀態的簡短、純粹資訊性的文字描述,以幫助人們理解請求的結果。原因短語偶爾會用括號括起來(例如,“201 (Created)”),表示它是可選的。

響應頭

響應頭是與響應一起傳送的元資料。在 HTTP/1.x 中,每個頭都是一個不區分大小寫的字串,後跟一個冒號 (:) 和一個值,其格式取決於所使用的頭。

Example of headers in an HTTP response

與請求頭一樣,響應中可能出現許多不同的頭,它們被分類為

  • 響應頭提供有關訊息的額外上下文或新增額外的邏輯來指導客戶端如何進行後續請求。例如,Server 等頭包含有關伺服器軟體的資訊,而 Date 則包含響應生成的時間。還有關於返回資源的資訊,例如其內容型別 (Content-Type) 或如何快取 (Cache-Control)。
  • 如果訊息有正文,表示頭描述訊息資料的形式以及應用的任何編碼。例如,同一個資源可能以特定媒體型別(如 XML 或 JSON)格式化,本地化為特定書面語言或地理區域,和/或壓縮或以其他方式編碼以進行傳輸。這允許接收方理解如何在資源透過網路傳輸之前重新構建它。

響應體

在響應客戶端時,大多數訊息都包含響應體。在成功的請求中,響應體包含客戶端在 GET 請求中請求的資料。如果客戶端請求有問題,響應體通常會描述請求失敗的原因,並暗示是永久性還是暫時性。

響應體可以是

狀態碼回答請求但無需包含訊息內容的響應,例如201 Created204 No Content,不包含正文。

HTTP/2 訊息

HTTP/1.x 使用基於文字的訊息,易於閱讀和構建,但也因此有一些缺點。您可以使用 gzip 或其他壓縮演算法壓縮訊息體,但不能壓縮標頭。在客戶端-伺服器互動中,標頭通常相似或相同,但在連線中的後續訊息中重複。有許多已知的壓縮重複文字的有效方法,這導致大量頻寬節省未被利用。

HTTP/1.x 還有一個問題叫做隊頭阻塞(HOL),即客戶端必須等待伺服器的響應才能傳送下一個請求。HTTP 管道試圖解決這個問題,但由於支援不佳和複雜性,它很少被使用,而且很難正確實現。需要開啟多個連線才能併發傳送請求;並且由於 TCP 慢啟動,已建立的繁忙連線比新的連線更高效。

在 HTTP/1.1 中,如果您想並行發出兩個請求,則必須開啟兩個連線

Making two HTTP requests to a server in parallel

這意味著瀏覽器在可以同時下載和渲染的資源數量上受到限制,通常限制為 6 個並行連線。

HTTP/2 允許您使用單個 TCP 連線同時處理多個請求和響應。這是透過將訊息封裝到二進位制幀中,並在連線上的編號中傳送請求和響應來實現的。資料幀和頭幀分開處理,這允許透過 HPACK 演算法壓縮頭。使用同一個 TCP 連線同時處理多個請求稱為多路複用

Multiplexing requests and responses in HTTP/2 using a single TCP connection.

請求不一定是順序的:例如,流 9 不必等待流 7 完成。來自多個流的資料通常在連線上交錯,因此客戶端可以同時接收流 9 和流 7 的資料。協議有一種機制可以為每個流或資源設定優先順序。當低優先順序資源透過不同的流傳送時,它們佔用的頻寬比高優先順序資源少,或者如果存在應首先處理的關鍵資源,它們可以有效地在同一連線上按順序傳送。

總的來說,儘管 HTTP/2 在 HTTP/1.x 的基礎上進行了所有改進和抽象,但開發者用於利用 HTTP/2 而非 HTTP/1.x 的 API 幾乎不需要更改。當瀏覽器和伺服器都支援 HTTP/2 時,它會自動啟用並使用。

偽頭

HTTP/2 訊息的一個顯著變化是使用了偽頭。HTTP/1.x 使用訊息起始行,而 HTTP/2 使用以 : 開頭的特殊偽頭欄位。在請求中,有以下偽頭

  • :method - HTTP 方法。
  • :scheme - 目標 URI 的方案部分,通常是 HTTP(S)。
  • :authority - 目標 URI 的授權部分。
  • :path - 目標 URI 的路徑和查詢部分。

在響應中,只有一個偽頭,那就是 :status,它提供響應的狀態碼。

我們可以使用 nghttp 傳送 HTTP/2 請求來獲取 example.com,它將以更具可讀性的形式打印出請求。您可以使用此命令傳送請求,其中 -n 選項會丟棄下載的資料,-v 用於“詳細”輸出,顯示幀的接收和傳輸

bash
nghttp -nv https://www.example.com

如果您向下瀏覽輸出,您將看到每個傳輸和接收幀的時間

[  0.123] <send|recv> <frame-type> <frame-details>

我們不必深入研究此輸出的太多細節,但請注意格式為 [ 0.123] send HEADERS frame ...HEADERS 幀。在標頭傳輸後的幾行中,您將看到以下行

http
[  0.447] send HEADERS frame ...
          ...
          :method: GET
          :path: /
          :scheme: https
          :authority: www.example.com
          accept: */*
          accept-encoding: gzip, deflate
          user-agent: nghttp2/1.61.0

如果您已經熟悉使用 HTTP/1.x,並且本指南前面部分介紹的概念仍然適用,這應該看起來很熟悉。這是二進位制幀,包含對 example.comGET 請求,由 nghttp 轉換為可讀形式。如果您進一步向下檢視命令的輸出,您將看到伺服器接收到的一個流中的 :status 偽頭

http
[  0.433] recv (stream_id=13) :status: 200
[  0.433] recv (stream_id=13) content-encoding: gzip
[  0.433] recv (stream_id=13) age: 112721
[  0.433] recv (stream_id=13) cache-control: max-age=604800
[  0.433] recv (stream_id=13) content-type: text/html; charset=UTF-8
[  0.433] recv (stream_id=13) date: Fri, 13 Sep 2024 12:56:07 GMT
[  0.433] recv (stream_id=13) etag: "3147526947+gzip"
...

如果您從此訊息中刪除時間和流 ID,它應該會更熟悉

http
:status: 200
content-encoding: gzip
age: 112721

深入研究訊息幀、流 ID 以及連線的管理超出了本指南的範圍,但為了理解和除錯 HTTP/2 訊息,您應該能夠很好地使用本文中的知識和工具。

總結

本指南概述了 HTTP 訊息的結構,並使用 HTTP/1.1 格式進行說明。我們還探討了 HTTP/2 訊息幀,它在 HTTP/1.x 語法和底層傳輸協議之間引入了一個層,而沒有從根本上修改 HTTP 的語義。引入 HTTP/2 是為了透過啟用請求的多路複用來解決 HTTP/1.x 中存在的隊頭阻塞問題。

HTTP/2 中仍然存在一個問題,那就是儘管協議層解決了隊頭阻塞問題,但由於 TCP 內部(傳輸層)的隊頭阻塞,仍然存在效能瓶頸。HTTP/3 透過使用 QUIC(一種基於 UDP 的協議)而不是 TCP 來解決此限制。這一改變提高了效能,減少了連線建立時間,並增強了在降級或不可靠網路上的穩定性。HTTP/3 保留了相同的核心 HTTP 語義,因此請求方法、狀態碼和標頭等功能在所有三個主要 HTTP 版本中保持一致。

如果您理解 HTTP/1.1 的語義,那麼您已經為掌握 HTTP/2 和 HTTP/3 奠定了堅實的基礎。主要區別在於這些語義在傳輸層是如何實現的。透過遵循本指南中的示例和概念,您現在應該能夠熟練地使用 HTTP 並理解訊息的含義,以及應用程式如何使用 HTTP 傳送和接收資料。

另見