跳至主要內容
Skip to content

HTTP 協定結構解剖

上一篇我們了解了一個網路請求的完整生命週期。現在,讓我們把焦點放在 HTTP 協定本身,深入解剖它的報文結構。

HTTP(HyperText Transfer Protocol)是一個純文字協定,這意味著你可以用肉眼直接閱讀它的內容。理解報文結構是掌握 HTTP 的基礎中的基礎。


一、 HTTP 報文總覽

HTTP 通訊基於「請求-回應」模式:客戶端發送請求報文(Request Message),伺服器回傳回應報文(Response Message)

兩種報文的結構非常相似:

┌─────────────────────────────────────┐
│           起始行 (Start Line)        │
├─────────────────────────────────────┤
│           標頭 (Headers)             │
│           Header: Value              │
│           Header: Value              │
│           ...                        │
├─────────────────────────────────────┤
│           空行 (CRLF)                │
├─────────────────────────────────────┤
│           主體 (Body)                │
│           (可選)                     │
└─────────────────────────────────────┘
組成部分說明
起始行描述請求或回應的基本資訊
標頭鍵值對形式的元資料(Metadata)
空行標頭與主體之間的分隔符(CRLF)
主體實際傳輸的資料(可選)

二、 請求報文結構

完整範例

讓我們看一個實際的 HTTP 請求:

http
POST /api/users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Content-Length: 42
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)

{"name": "John", "email": "john@example.com"}

請求行(Request Line)

請求報文的第一行稱為請求行,由三個部分組成:

POST /api/users HTTP/1.1
 │       │         │
 │       │         └─ HTTP 版本(HTTP-Version)
 │       └─ 請求目標(Request-Target / URI)
 └─ 請求方法(Method)

請求方法(Method)

HTTP 定義了多種請求方法,每種有不同的語義:

方法語義有 Body冪等性安全性
GET獲取資源
POST建立資源
PUT完整更新資源
PATCH部分更新資源
DELETE刪除資源
HEAD獲取標頭(無 Body)
OPTIONS查詢支援的方法

> **冪等性**:多次執行相同請求,結果相同。**安全性**:不會改變伺服器狀態。後續文章會深入探討。

請求目標(Request-Target)

請求目標通常是資源的路徑,有四種形式:

形式範例使用場景
origin-form/api/users?page=1最常見,一般請求
absolute-formhttp://example.com/api代理請求
authority-formexample.com:443CONNECT 方法(隧道)
asterisk-form*OPTIONS 方法

HTTP 版本

常見的版本:

版本說明
HTTP/1.0每次請求新建連線
HTTP/1.1持久連線,最廣泛使用
HTTP/2二進位協定,多路複用
HTTP/3基於 QUIC,使用 UDP

三、 回應報文結構

完整範例

http
HTTP/1.1 200 OK
Date: Wed, 08 Jan 2025 10:30:00 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 85
Cache-Control: no-cache
X-Request-Id: abc123

{"id": 1, "name": "John", "email": "john@example.com", "createdAt": "2025-01-08T10:30:00Z"}

狀態行(Status Line)

回應報文的第一行稱為狀態行,由三個部分組成:

HTTP/1.1 200 OK
   │      │   │
   │      │   └─ 原因短語(Reason Phrase)
   │      └─ 狀態碼(Status Code)
   └─ HTTP 版本

狀態碼(Status Code)

狀態碼是三位數字,分為五大類:

範圍類別意義範例
1xxInformational資訊性,處理中100 Continue
2xxSuccess成功200 OK, 201 Created
3xxRedirection重導向301 Moved, 304 Not Modified
4xxClient Error客戶端錯誤400 Bad Request, 404 Not Found
5xxServer Error伺服器錯誤500 Internal Error, 503 Unavailable

原因短語(Reason Phrase)

原因短語是狀態碼的人類可讀描述。在 HTTP/1.1 中是必要的,但在 HTTP/2 和 HTTP/3 中已被移除(因為是二進位協定)。

IMPORTANT

不要依賴原因短語來判斷結果,永遠以狀態碼為準。不同伺服器可能回傳不同的原因短語。


四、 標頭(Headers)

標頭是 HTTP 報文的核心,攜帶了大量的元資料。

標頭格式

每個標頭由名稱組成,以冒號分隔:

Header-Name: Header-Value

規則:

  • 名稱不區分大小寫(Content-Type 等同 content-type
  • 名稱與冒號之間不能有空格
  • 值前可以有一個空格(慣例)
  • 一行一個標頭
  • 以 CRLF(\r\n)結尾

標頭分類

HTTP 標頭可以分為四大類:

通用標頭(General Headers)

請求和回應都可使用:

標頭說明範例
Date報文建立時間Wed, 08 Jan 2025 10:30:00 GMT
Cache-Control快取指令max-age=3600, public
Connection連線管理keep-alive, close
Transfer-Encoding傳輸編碼chunked

請求標頭(Request Headers)

僅用於請求:

標頭說明範例
Host目標主機(必填)api.example.com
User-Agent客戶端資訊Mozilla/5.0 (...)
Accept可接受的回應類型application/json
Accept-Language偏好語言zh-TW, en;q=0.9
Accept-Encoding可接受的壓縮方式gzip, deflate, br
Authorization認證資訊Bearer token123
Cookie客戶端 Cookiesession=abc123
Referer來源頁面https://example.com/page
Origin請求來源(CORS)https://example.com

回應標頭(Response Headers)

僅用於回應:

標頭說明範例
Server伺服器軟體nginx/1.18.0
Set-Cookie設定 Cookiesession=abc; Path=/; HttpOnly
Location重導向目標https://example.com/new
WWW-Authenticate認證要求Bearer realm="api"
Access-Control-*CORS 相關Access-Control-Allow-Origin: *

實體標頭(Entity Headers)

描述 Body 的屬性:

標頭說明範例
Content-Type內容的 MIME 類型application/json; charset=utf-8
Content-Length內容長度(bytes)1234
Content-Encoding內容壓縮方式gzip
Content-Language內容語言zh-TW
Content-Disposition處理方式(下載等)attachment; filename="file.pdf"

自訂標頭

你可以定義自己的標頭。歷史上常用 X- 前綴,但 RFC 6648 已廢棄此慣例:

http
# 舊式(已不建議)
X-Request-Id: abc123

# 現代做法
Request-Id: abc123

五、 主體(Body)

Body 是 HTTP 報文中實際傳輸的資料。

何時有 Body?

報文類型有 Body 的情況
請求POST、PUT、PATCH 通常有;GET、DELETE 通常沒有
回應大多數有;204、304 沒有

Content-Type 常見值

Content-Type 告訴接收方如何解讀 Body:

MIME 類型說明用途
text/htmlHTML 文件網頁
text/plain純文字文字檔
application/jsonJSON 格式API 資料交換
application/xmlXML 格式舊式 API
application/x-www-form-urlencoded表單編碼HTML 表單
multipart/form-data多部分表單檔案上傳
application/octet-stream二進位資料檔案下載

表單編碼格式比較

HTML 表單有兩種編碼方式:

application/x-www-form-urlencoded

http
POST /login HTTP/1.1
Content-Type: application/x-www-form-urlencoded

username=john&password=secret123

特點:

  • 資料以 key=value&key=value 格式編碼
  • 特殊字元會被 URL 編碼(如空格變 %20
  • 適合簡單表單

multipart/form-data

http
POST /upload HTTP/1.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxk

------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="title"

My Document
------WebKitFormBoundary7MA4YWxk
Content-Disposition: form-data; name="file"; filename="doc.pdf"
Content-Type: application/pdf

(二進位內容)
------WebKitFormBoundary7MA4YWxk--

特點:

  • 每個欄位用 boundary 分隔
  • 可傳輸二進位檔案
  • 適合檔案上傳

六、 行結束符:CRLF

HTTP/1.x 使用 CRLF(Carriage Return + Line Feed)作為行結束符:

CR = \r = 0x0D (Carriage Return)
LF = \n = 0x0A (Line Feed)
CRLF = \r\n

報文結構圖解

POST /api HTTP/1.1\r\n          ← 請求行 + CRLF
Host: example.com\r\n           ← 標頭 + CRLF
Content-Type: application/json\r\n
Content-Length: 13\r\n
\r\n                             ← 空行(只有 CRLF)
{"id": 1}                       ← Body(無 CRLF)

為什麼是 CRLF?

這是歷史遺留問題:

  • CR:打字機時代,將打印頭移回行首
  • LF:換到下一行
  • Windows 使用 \r\n,Unix/Linux 使用 \n,HTTP 選擇了 Windows 風格

TIP

雖然規範要求 CRLF,但大多數伺服器也能容忍只有 LF 的情況。


七、 用 cURL 觀察報文

查看完整請求與回應

bash
# -v 顯示詳細資訊(包含標頭)
curl -v https://httpbin.org/get

# 輸出範例:
# > GET /get HTTP/2
# > Host: httpbin.org
# > User-Agent: curl/8.1.2
# > Accept: */*
# >
# < HTTP/2 200
# < content-type: application/json
# < content-length: 256
# <
# { "args": {}, "headers": { ... } }

發送 POST 請求

bash
# -d 指定 body,-H 指定標頭
curl -v -X POST https://httpbin.org/post \
  -H "Content-Type: application/json" \
  -d '{"name": "John"}'

只查看標頭

bash
# -I 只取標頭(發送 HEAD 請求)
curl -I https://example.com

# -i 顯示標頭 + body
curl -i https://example.com

八、 HTTP/1.1 vs HTTP/2 報文差異

HTTP/2 將文字協定改為二進位協定:

特性HTTP/1.1HTTP/2
格式純文字二進位
讀取方式行導向(CRLF 分隔)幀導向(Frame)
標頭原樣傳輸HPACK 壓縮
多路複用

總結

HTTP 報文結構是協定的基礎,無論是請求還是回應,都遵循相似的格式:

組成部分請求報文回應報文
起始行請求行(Method + URI + Version)狀態行(Version + Status + Reason)
標頭Host、User-Agent、Accept 等Server、Set-Cookie、Content-Type 等
空行CRLFCRLF
Body請求資料(可選)回應資料(可選)

掌握這個結構後,你就能:

  • 閱讀和理解任何 HTTP 通訊
  • 使用 cURL 等工具進行除錯
  • 理解各種 HTTP 工具的運作原理

進階挑戰

  1. 實作挑戰:使用 nc(netcat)手動發送一個 HTTP 請求到 httpbin.org

    bash
    echo -e "GET /get HTTP/1.1\r\nHost: httpbin.org\r\n\r\n" | nc httpbin.org 80

    觀察回應並理解每個部分。

  2. 深度思考:為什麼 HTTP/2 要將協定改為二進位?純文字協定有什麼缺點?(提示:解析效率、標頭冗餘)

  3. 探索實驗:使用瀏覽器的 DevTools Network 面板,找一個 POST 請求,觀察它的 Request Headers 和 Request Payload。


延伸閱讀與資源