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 的設計目標
- 高壓縮率:減少標頭大小
- 安全:抵抗 CRIME 攻擊
- 簡單:易於實作
2.3 核心機制
HPACK 使用三種技術:
1. 靜態表(Static Table)
預定義 61 個常見標頭:
| 索引 | 標頭名稱 | 標頭值 |
|---|---|---|
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| ... | ... | ... |
| 61 | www-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 有兩層流量控制:
- 連線級別:整個連線的流量
- 串流級別:單個串流的流量
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 自動運作,確保連線重用以發揮最大效益
- 流量控制通常不需要調整預設值
進階挑戰
- 使用
nghttp觀察 HPACK 壓縮效果,比較多次請求的標頭大小變化。 - 研究為什麼 Chrome 決定移除 Server Push,並了解 103 Early Hints 如何解決同樣的問題。
- 實作一個簡單的 HPACK 編碼器,理解靜態表和動態表的運作。