HPACK 头部压缩与服务器推送
基于 HTTP 现代标准 · 核于 2026-06
速查
- HTTP/1.1 头部以明文逐请求重发,单次开销约 500-800 字节,带 Cookie 时可达数 KB,且无压缩。
- HPACK(RFC 7541)= 静态表 + 动态表 + Huffman 编码,三者叠加可削减约 85%-88% 的头部体积。
- 静态表:61 条预定义常见头(
:method、:path、content-type等),双端硬编码,开箱即用。 - 动态表:连接内增量维护、FIFO 淘汰,默认上限 4096 字节,重复头第二次起只发一个索引号。
- Huffman 编码:对头部字符串用针对 HTTP 频率优化的静态码表压缩。
- 不直接用 gzip 压缩头部:通用压缩会引入 CRIME/BREACH 侧信道攻击;HPACK 以「整值匹配 + 永不索引字面量」抗探测。
- 服务器推送(Server Push):服务器用
PUSH_PROMISE帧主动推 CSS/JS,曾设想免去客户端二次请求。 - 服务器推送已被废弃:Chrome 106(2022)默认移除,采用率仅 0.7%-1.25%,收益低且常引发性能回退、缓存难处理、易推送浪费。
- 替代方案 103 Early Hints(RFC 8297):在最终响应前先发
Link: rel=preload/preconnect提示,由浏览器自主决定是否抓取。 - HTTP/3 改用 QPACK 替代 HPACK(适配 QUIC 乱序,细节见下一页)。
为什么头部需要压缩
HTTP/1.1 时代,请求体可以 gzip,但头部始终是明文且不压缩。问题在于头部高度冗余:
- 同一站点的每个请求都重复携带
User-Agent、Accept、Accept-Encoding、Referer等几乎一致的字段。 - Cookie 是重灾区:一旦写入,之后每个请求(包括图片、字体、API)都把整串 Cookie 原样再发一遍。
- 一个典型页面动辄几十上百个子资源请求,头部开销线性累加。
经验数据:每次传输头部带来约 500-800 字节开销,带 Cookie 时常额外多出数 KB。在移动网络等高延迟、上行受限的链路上,这部分纯冗余会明显拖慢首屏。
HTTP/2 的多路复用让一条连接并发跑很多请求,头部冗余被进一步放大,因此必须有专门的头部压缩方案——这就是 HPACK。
HPACK 机制
HPACK(RFC 7541)是 HTTP/2 专用的头部压缩格式,由三部分协同:
静态表(Static Table)
一张双端预先约定、不可变的表,收录 61 条最常见的头部条目,索引 1-61。包含 HTTP/2 伪首部(:method GET、:scheme https、:path / 等)以及 content-type、accept-encoding 这类高频字段。命中静态表的头,直接用一个小整数索引代替整行文本。
动态表(Dynamic Table)
连接建立后从空开始、随交互增量填充的表,紧接静态表编号往后排:
- 每方向独立:请求方向与响应方向各自维护一张动态表。
- FIFO 淘汰:默认上限 4096 字节(受
SETTINGS_HEADER_TABLE_SIZE控制),超限时从最旧条目开始驱逐。每条占用 = 名长 + 值长 + 32 字节固定开销。 - 增量收益:第一次出现的头(如
cookie: abc...)发完整值并存入动态表;之后同一头只发一个索引号,这正是 Cookie 重发问题的根治手段。
Huffman 编码
对需要发送的字符串字面量(头名 / 头值),可用一张针对 HTTP 头部字符频率优化的静态 Huffman 码表压缩。编码方与解码方共用同一码表,用 1 个标志位指示该字符串是否经过 Huffman 编码。
三者如何叠加
静态表消掉「人人都发的通用头」,动态表消掉「本连接重复发的头」(Cookie/自定义头),Huffman 再压缩剩余的字面量字节。SPDY/HTTP2 早期实测头部数据可缩减 85%-88%,慢速链路上单页加载可因此快 45-1142 ms。
为何不直接用 gzip 压缩头部
最直觉的做法是「把头部也丢进 gzip/DEFLATE」——HTTP/2 的前身 SPDY 起初正是这么干的,但很快被推翻,原因是安全而非性能。
CRIME / BREACH 压缩侧信道攻击
通用压缩的本质是「重复内容压得更短」。攻击者若能往请求里注入可控字符串(如猜测的 Cookie 片段),再观察压缩后头部的长度变化,就能逐字符地猜出同一压缩上下文里的机密值(如会话 Cookie)。这类攻击称为 CRIME(针对请求头压缩)/ BREACH(针对响应体压缩),可把指数级暴力破解降为线性级。
HPACK 的设计针对性地削弱了这条攻击路径:
- 整值匹配,而非逐字符:动态表以「整条头部字段值」为索引单位,攻击者必须一次猜中整个字段值才能观察到长度变化,成本远高于逐字符猜测。
- 永不索引字面量(Never-Indexed Literal):对敏感头(
cookie、authorization)可标记为「绝不存入动态表」,彻底关闭对它们的压缩探测面。
残余风险
HPACK 是「缓解」而非「根除」:低熵值仍可能被攻破,高熵值则基本无法还原。因此规范明确建议对敏感头使用永不索引字面量。这也是「头部不能简单套通用压缩」的根本原因。
服务器推送(Server Push)—— 一个被废弃的特性
高频考点:服务器推送已被主流浏览器废弃
HTTP/2 Server Push 不是推荐使用的优化手段。Chrome 自 106 版(2022 年)默认移除对它的支持,其它 Chromium 系浏览器跟进。下文先讲原理,再讲为何被淘汰——务必记住结论是「已废弃」,不要当成现行最佳实践。
原理与 PUSH_PROMISE 帧
服务器推送允许服务器在客户端仅请求一个 HTML 的情况下,主动把后续大概率要用的 CSS/JS 等资源「连同」推给浏览器,省去浏览器解析 HTML 后再发请求的往返:
- 服务器对将要推送的每个资源先发一个
PUSH_PROMISE帧,声明「我要推这个 URL」,且该帧须在父响应数据之前到达。 - 随后在新建的服务器发起流上推送资源内容。
- 客户端若不需要(如本地缓存已有),可用
RST_STREAM帧拒收。
设想的用途:首屏请求 index.html 时,顺手把关键 app.css、app.js 一并推下,理论上比「等浏览器发现 <link> 再请求」更快。
为何被废弃
实践中推送的「设想收益」几乎无法兑现,反而带来一堆麻烦:
- 采用率极低:统计显示仅约 1.25% 的 HTTP/2 站点用了推送,后续进一步降到 0.7%。
- 收益低且常致性能回退:实测「结果参差,无明确净收益,很多场景反而是性能回退」——服务器往往推了浏览器已缓存或当前页面用不上的资源,白白挤占带宽与连接(推送浪费)。
- 缓存难处理:推送在浏览器决定查缓存之前就已发生,服务器无从得知客户端缓存状态,极易重复推送。
- HTTP/3 未跟进:许多 HTTP/3 服务器与客户端根本没实现推送,等于在新协议上已被淘汰。
- 复杂度高:正确配置推送(推什么、推多少、何时停)门槛高,收益却不稳定。
替代方案:103 Early Hints
官方推荐用 103 Early Hints(RFC 8297)替代推送——它保留了「让浏览器尽早拿到资源」的好处,却几乎没有推送的坏处:服务器不主动塞资源,而是先发一个信息性响应,用 Link 头提示浏览器有哪些资源值得 preload 或对哪些源 preconnect,由浏览器自主决定是否抓取(自然会查缓存、不浪费)。
服务器在准备最终响应(如等数据库查询)期间,可先回 103:
GET / HTTP/1.1
Host: example.com
HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </script.js>; rel=preload; as=script
Link: <https://cdn.example.com>; rel=preconnect
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Link: </style.css>; rel=preload; as=style
<!doctype html>
...使用要点
- 浏览器只处理第一个 103 响应;跨域重定向时会丢弃早期提示。
- 出于兼容与安全,建议仅在 HTTP/2 及以上发送 103。
- 与推送的本质区别:提示(hint)让浏览器去拉,推送(push)是服务器硬塞——前者保留了缓存感知与客户端控制权。
HTTP/3 的 QPACK(简述)
HTTP/3 跑在 QUIC 上,而 QUIC 的多条流是乱序独立到达的。HPACK 的动态表依赖「头块按序到达」的假设,在乱序环境会造成队头阻塞,因此 HTTP/3 改用 QPACK ——它沿用静态表 + 动态表 + Huffman 的思路,但重新设计了动态表的同步方式以适配乱序传输。具体机制见下一页。
小结
HPACK 用「静态表 + 动态表 + Huffman」三招把 HTTP/1.1 时代逐请求重发的明文头部(尤其是 Cookie)压缩约 85%-88%,并刻意以「整值匹配 + 永不索引字面量」规避通用压缩才有的 CRIME/BREACH 侧信道攻击——这正是头部不能简单套 gzip 的原因。服务器推送曾被寄望提前下推 CSS/JS,但因采用率低、收益不稳、缓存难处理、易造成推送浪费,已被 Chrome 106(2022)默认移除,业界改用更克制的 103 Early Hints(Link: rel=preload/preconnect)让浏览器自主预取。前端记住一句话:「头部压缩留下了 HPACK/QPACK,服务器推送则进了历史」。
- 上一页:HTTP/2 二进制分帧与多路复用
- 下一页:HTTP/3 与 QUIC