Go 的 http.Server 默认不支持 HTTP/2 服务端推送,需显式调用 http2.ConfigureServer 启用且仅限 TLS(h2)环境;Pusher 接口已弃用,推荐使用 preload 替代。

Go 的 http.Server 默认不支持 HTTP/2 服务端推送
HTTP/2 推送(Pusher)在 Go 标准库中被刻意限制:仅当使用 http.ServeTLS 启动、且 TLS 配置满足 ALPN 协议协商(h2)时,ResponseWriter 才可能实现 http.Pusher 接口。但即使满足条件,Go 1.22 之前版本也**默认禁用推送**——因为底层 net/http 在 TLS 握手后未主动调用 http2.ConfigureServer 注入推送逻辑。
常见错误现象:panic: interface conversion: http.ResponseWriter is *http.response not http.Pusher 或静默忽略 Push 调用(无报错但浏览器收不到推送流)。
- 必须显式启用 HTTP/2:用
http2.ConfigureServer包装http.Server,否则Pusher接口永远为nil - 必须使用 TLS:HTTP/2 推送在明文 HTTP/2(h2c)中被 RFC 明确禁止,Go 也不实现 h2c 的
Push - 证书不能是自签名且未加到系统信任链:某些客户端(如 Chrome)会拒绝推送,即使连接成功
http.Pusher.Push 的路径必须是绝对路径且可被当前 handler 处理
调用 Push 时传入的路径不是相对 URL,而是从根开始的绝对路径(如 /static/app.js),且该路径需能被当前 http.ServeMux 或路由中间件捕获并响应 200。否则推送会失败,但 Go 不报错,只悄悄丢弃。
典型误用场景:前端用 Webpack 打包后资源带 hash,后端硬编码 Push("/static/app.a1b2c3.js"),但实际文件名已变;或推送路径指向一个需要登录态的接口,而推送发生在未鉴权的初始 HTML 响应阶段。
立即学习“go语言免费学习笔记(深入)”;
- 路径必须以
/开头,不能是./或static/ - 确保对应 handler 已注册,且不依赖请求上下文中的动态状态(如 session、header)
- 避免推送大文件:浏览器对单次推送有大小和并发流数限制,超限会被中断
Go 1.22+ 中 http.Pusher 已被标记为 deprecated
官方明确表示 Pusher 是遗留接口,未来可能移除。根本原因是现代前端构建工具(Vite、Webpack)、CDN 和浏览器自身优化(preload、preconnect)已让服务端推送收益大幅下降,反而增加服务器负担和连接复杂度。
如果你正在做性能优化,优先检查是否真的需要推送:用 Chrome DevTools 的 Network → Protocol 列确认资源是否真走 HTTP/2,再看 Initiator 是否为 Push;更大概率你看到的是 preload link 标签触发的 fetch,它更可控、兼容性更好。
- 替代方案:在 HTML 中写
<link rel="preload" href="/static/app.js" as="script"> - 若仍坚持用 Push,必须用
if pusher, ok := w.(http.Pusher); ok { pusher.Push(...) }做运行时判断,不能假设存在 - 注意:
Push是异步发起的,不阻塞主响应,但错误不会抛出,只能靠日志或抓包验证
用 curl -v --http2 和 Chrome 的 net-internals 验证推送是否生效
光看 Go 日志没用,Push 成功与否取决于客户端是否接收并处理了 PUSH_PROMISE 帧。本地开发最靠谱的方式是抓包 + 客户端日志双验证。
常见假阳性:服务端调用了 Push,但浏览器因缓存、跨域策略或资源已存在而直接复用本地副本,不触发新流。
- 用
curl -v --http2 https://localhost:8080/看不到推送,因为 curl 不处理 PUSH_PROMISE,只显示主响应 - Chrome 访问
chrome://net-internals/#events,过滤HTTP2_SESSION_PUSH_PROMISE_RECEIVED事件 - Wireshark 抓包时需启用 TLS 解密(配置
SSLKEYLOGFILE),否则只能看到加密帧
真正难调试的点不在代码怎么写,而在“推送发出去了,但没人认领”——这往往卡在 TLS 握手细节、客户端策略或资源路径匹配上。别急着改 Go 代码,先确认那条 PUSH_PROMISE 帧到底有没有进浏览器。











