1. 引言 (Introduction)
在HTTP/1.1 [RFC7230]中,HTTP头部字段以纯文本形式传输,未经压缩。随着Web应用的发展,HTTP请求和响应中的头部字段数量和大小都在增长,导致不必要的网络流量和延迟。
1.1 动机 (Motivation)
HTTP/2 [RFC7540]通过二进制分帧层提供了多路复用和流优先级等特性,但如果不压缩头部,这些改进的效果会大打折扣。考虑以下场景:
典型的Web请求头部:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: session_id=abc123xyz789; user_pref=dark_mode; ...
Referer: https://www.google.com/search?q=example
Cache-Control: max-age=0
问题:
1. 头部大小通常为500-2000字节
2. 每个请求都重复发送大部分相同的头部
3. Cookie可能非常大(数KB)
4. 对于小文件请求,头部可能大于实际内容
头部冗余问题
在一个典型的Web浏览会话中:
场景: 加载包含100个资源的网页
第1个请求头部: 1200字节
第2个请求头部: 1200字节 (95%重复!)
第3个请求头部: 1200字节 (95%重复!)
...
第100个请求头部: 1200字节 (95%重复!)
总计: 120KB的头部数据
其中: 114KB是重复的
在2G网络(~250kbps)上:
120KB头部 → 3.8秒延迟
如果压缩到12KB → 0.4秒延迟
节省3.4秒!
1.2 SPDY的教训
SPDY协议最初使用DEFLATE压缩算法来压缩HTTP头部。虽然这种方法提供了良好的压缩率,但在2012年,安全研究人员发现了CRIME (Compression Ratio Info-leak Made Easy)攻击。
CRIME攻击原理
CRIME攻击的工作方式:
假设目标: 窃取用户的session cookie
1. 攻击者控制部分请求内容:
Cookie: session=secret123
X-Injected: session=a
压缩后: 500字节
2. 攻击者尝试不同值:
X-Injected: session=b
压缩后: 500字节
3. 直到命中:
X-Injected: session=secret123
压缩后: 480字节 (更小! 压缩找到了重复)
→ 攻击者推断出cookie值
原理:
DEFLATE会查找重复序列并用引用代替。
如果注入内容与敏感数据匹配,压缩率提高。
通过观察压缩后大小,可以推断敏感信息。
这个漏洞使得SPDY无法在安全环境中使用DEFLATE压缩头部。
1.3 HPACK的设计目标
HPACK被设计为HTTP/2的头部压缩格式,需要满足以下目标:
1. 高压缩率 (High Compression Ratio)
目标: 减少70-90%的头部大小
方法:
- 静态表: 预定义常用头部
- 动态表: 缓存连接内的头部
- 霍夫曼编码: 压缩字符串
2. 快速编码/解码 (Fast Encoding/Decoding)
要求:
- 编码器: O(n)时间复杂度
- 解码器: O(n)时间复杂度
- 低CPU开销
实现:
- 简单的表查找
- 无复杂数学运算
- 可高效实现
3. 简单实现 (Simple Implementation)
目标: 降低实现复杂度
方法:
- 明确的状态机
- 简单的编码规则
- 最小的协议开销
4. 安全性 (Security)
核心要求: 抵御CRIME类攻击
解决方案:
1. 不使用通用压缩算法
2. 敏感头部可以不索引
3. 动态表大小可控
4. 无跨请求压缩上下文
5. 低内存占用 (Low Memory Footprint)
目标: 支持资源受限设备
要求:
- 可配置的动态表大小
- 默认4096字节
- 可以设为0 (禁用动态表)
1.4 HPACK的核心思想
HPACK采用了一种全新的压缩方法:
核心概念: 索引表 + 增量编码
1. 静态表 (Static Table)
- 61个预定义常用头部
- 所有连接共享
- 不可修改
示例:
索引2 → :method: GET
索引8 → :status: 200
2. 动态表 (Dynamic Table)
- 连接内维护
- 存储新头部
- FIFO管理
示例:
索引62 → custom-header: custom-value
3. 增量编码
- 首次: 完整编码
- 后续: 索引引用
第1次: custom-header: value → 30字节
第2次: 索引62 → 1字节
4. 霍夫曼编码 (可选)
- 压缩字符串值
- 平均节省20-30%
与DEFLATE的对比
| 特性 | DEFLATE | HPACK |
|---|---|---|
| 压缩率 | 80-90% | 70-85% |
| 安全性 | ❌ CRIME漏洞 | ✅ 安全 |
| 速度 | 慢 (复杂算法) | 快 (简单查表) |
| 内存 | 变化大 | 可控 |
| 实现复杂度 | 高 | 低 |
1.5 文档组织
本规范的其余部分组织如下:
- 第2节: 描述HPACK的压缩过程
- 第3节: 定义原语(整数编码、字符串编码)
- 第4节: 定义动态表的管理
- 第5节: 定义静态表
- 第6节: 定义二进制格式
- 第7节: 讨论安全考虑
- 附录A: 提供静态表定义
- 附录B: 提供霍夫曼编码表
- 附录C: 提供示例
1.6 符号约定
本文档中的关键词"MUST"、"MUST NOT"、"REQUIRED"、"SHALL"、"SHALL NOT"、"SHOULD"、"SHOULD NOT"、"RECOMMENDED"、"MAY"和"OPTIONAL"应按BCP 14 [RFC2119]中的描述进行解释。
所有数值均以十进制表示,除非另有说明。二进制编码使用十六进制表示,前缀为"0x"。
示例:
0x82 = 二进制 10000010 = 十进制 130