跳至主要內容
Skip to content

HTTP/2 進階:Server Push 與 HPACK

上一篇介紹了 HTTP/2 的核心概念,本篇將深入兩個進階特性:伺服器推送(Server Push)和標頭壓縮(HPACK)。這些特性進一步優化了網路效能。


一、 伺服器推送(Server Push)

1.1 問題背景

傳統流程中,瀏覽器需要多次往返才能獲得所有資源:

問題:必須等 HTML 下載並解析後,才知道需要哪些資源。

1.2 Server Push 的解決方案

伺服器可以主動推送它知道客戶端會需要的資源:

1.3 PUSH_PROMISE 幀

伺服器通過 PUSH_PROMISE 幀「預告」即將推送的資源:

PUSH_PROMISE Frame:
┌─────────────────────────────────────┐
│ Pad Length? (8 bits)               │
├─────────────────────────────────────┤
│ R │ Promised Stream ID (31 bits)   │
├─────────────────────────────────────┤
│ Header Block Fragment (*)          │
├─────────────────────────────────────┤
│ Padding (*)                        │
└─────────────────────────────────────┘

重要欄位

欄位說明
Promised Stream ID推送資源使用的串流 ID
Header Block Fragment推送請求的標頭(壓縮後)

1.4 推送流程詳解

1.5 使用限制

限制說明
只能推送可快取資源推送的資源會進入快取
客戶端可拒絕發送 RST_STREAM 取消
必須同源不能推送跨域資源
串流 ID 規則伺服器使用偶數 ID

1.6 客戶端取消推送

如果客戶端不需要(已有快取),可以取消:

1.7 Server Push 的問題

雖然設計精巧,但 Server Push 在實際應用中遇到許多問題:

問題說明
快取判斷困難伺服器不知道客戶端有沒有快取
浪費頻寬推送已有快取的資源
優先級衝突推送可能搶佔更重要資源的頻寬
配置複雜需要精確預測客戶端需求

1.8 Chrome 移除 Server Push

IMPORTANT

Chrome 106 (2022) 已移除 HTTP/2 Server Push 支援。

Google 發現實際效益有限,且 103 Early Hints 是更好的替代方案。

替代方案:103 Early Hints

http
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style

HTTP/1.1 200 OK
Content-Type: text/html
...

二、 HPACK 標頭壓縮

2.1 為什麼需要壓縮標頭?

HTTP/1.1 的標頭有嚴重的冗餘問題:

http
# 請求 1
GET /page1 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
Accept: text/html,application/xhtml+xml,...
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; user=john...

# 請求 2(幾乎完全相同!)
GET /page2 HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)...
Accept: text/html,application/xhtml+xml,...
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Cookie: session=abc123; user=john...

問題

  • 每次請求都傳送完整標頭
  • Cookie 可能非常大(數 KB)
  • 純文字無法有效壓縮

2.2 HPACK 的設計目標

  1. 高壓縮率:減少標頭大小
  2. 安全:抵抗 CRIME 攻擊
  3. 簡單:易於實作

2.3 核心機制

HPACK 使用三種技術:

1. 靜態表(Static Table)

預定義 61 個常見標頭:

索引標頭名稱標頭值
1:authority
2:methodGET
3:methodPOST
4:path/
5:path/index.html
.........
61www-authenticate

2. 動態表(Dynamic Table)

連線期間維護的可變表,儲存最近使用的標頭:

動態表(FIFO):
┌─────────────────────────────────────┐
│ Index 62: x-custom: value1          │ ← 最新
├─────────────────────────────────────┤
│ Index 63: cookie: session=abc       │
├─────────────────────────────────────┤
│ Index 64: :path: /api/users         │ ← 較舊
└─────────────────────────────────────┘

3. Huffman 編碼

對字面值使用 Huffman 編碼:

原始: "www.example.com"
Huffman: 更短的位元序列

2.4 編碼方式

HPACK 定義了幾種編碼格式:

1. 索引標頭欄位

完全使用索引(最高效):

  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 1 |        Index (7+)         |
+---+---------------------------+

範例::method: GET 可以只用一個位元組 0x82(索引 2)

2. 帶索引的字面標頭

名稱用索引,值是字面:

  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 1 |      Index (6+)       |
+---+---+-----------------------+
| H |     Value Length (7+)     |
+---+---------------------------+
|  Value String (Length octets) |
+-------------------------------+

3. 不索引的字面標頭

敏感資料,不加入動態表:

  0   1   2   3   4   5   6   7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 |  Index (4+)   |
+---+---+-----------------------+

2.5 壓縮範例

第一個請求:
:method: GET         → 索引 2      → 0x82
:path: /index.html   → 索引 5      → 0x85
host: example.com    → 字面 + 加入動態表

第二個請求:
:method: GET         → 索引 2      → 0x82
:path: /style.css    → 字面 + 加入動態表
host: example.com    → 索引 62     → 0xBE(從動態表)

2.6 壓縮效果

典型壓縮率:85-90%

2.7 安全考慮

HPACK 設計時考慮了 CRIME 攻擊:

保護措施說明
不使用 gzip避免壓縮側信道攻擊
靜態 Huffman固定編碼表
never-indexed敏感標頭不索引

三、 優先級(Priority)

3.1 為什麼需要優先級?

不同資源的重要性不同:

HTML > CSS > JS > 圖片

HTTP/2 允許指定串流優先級。

3.2 優先級模型

HTTP/2 使用依賴樹模型:

3.3 權重計算

頻寬按權重比例分配:

Stream 5 (CSS): 256
Stream 7 (JS):  128
────────────────
總計:           384

CSS 獲得: 256/384 = 66.7% 頻寬
JS 獲得:  128/384 = 33.3% 頻寬

3.4 PRIORITY 幀

設定串流優先級:

PRIORITY Frame:
┌─────────────────────────────────────┐
│ E │ Stream Dependency (31 bits)     │
├─────────────────────────────────────┤
│ Weight (8 bits)                     │
└─────────────────────────────────────┘

E: Exclusive(獨佔)位
Weight: 1-256(實際值 +1)

3.5 優先級問題

WARNING

HTTP/2 的優先級系統過於複雜,實際實作效果不佳。

HTTP/3 改用更簡單的 Priority Hints。


四、 流量控制

4.1 為什麼需要流量控制?

防止快速發送方壓垮慢速接收方。

4.2 雙層控制

HTTP/2 有兩層流量控制:

  1. 連線級別:整個連線的流量
  2. 串流級別:單個串流的流量

4.3 WINDOW_UPDATE 幀

WINDOW_UPDATE Frame:
┌─────────────────────────────────────┐
│ R │ Window Size Increment (31 bits) │
└─────────────────────────────────────┘

4.4 流量控制流程


總結

特性說明現狀
Server Push主動推送資源Chrome 已移除,建議用 103 Early Hints
HPACK標頭壓縮核心特性,壓縮率 85-90%
優先級資源重要性排序實作效果不佳,HTTP/3 簡化
流量控制防止壓垮接收方雙層控制

> **實務建議**:

  • 不要依賴 Server Push,使用 103 Early Hints 或 Resource Hints
  • HPACK 自動運作,確保連線重用以發揮最大效益
  • 流量控制通常不需要調整預設值

進階挑戰

  1. 使用 nghttp 觀察 HPACK 壓縮效果,比較多次請求的標頭大小變化。
  2. 研究為什麼 Chrome 決定移除 Server Push,並了解 103 Early Hints 如何解決同樣的問題。
  3. 實作一個簡單的 HPACK 編碼器,理解靜態表和動態表的運作。

延伸閱讀與資源