go 的 http.server 默认不支持 server push,需手动断言 http.pusher 接口并在 http/2 且客户端支持时调用 push,但现代浏览器已弃用该功能,推荐使用 103 early hints 替代。

Go 的 http.Server 默认不支持 Server Push
Go 标准库的 http.Server 在 1.8 引入了对 HTTP/2 的支持,但只实现了「被动响应」——它能处理客户端发起的 PUSH_PROMISE 吗?不能。net/http 没暴露任何接口让你主动调用 Push 方法。这是最常被误解的一点:很多人以为启用 HTTP/2 就自动有了 Server Push 能力,其实没有。
根本原因在于 Go 的设计哲学:标准库不封装底层协议细节。HTTP/2 的 Server Push 需要直接操作 http.ResponseWriter 底层的 http2.Pusher 接口,而这个接口只在特定条件下可用,且仅限于 handler 内部显式断言使用。
- 必须用
http2.Transport(客户端)或确保 TLS + ALPN 正确协商出 h2(服务端) - 只有当请求是通过 HTTP/2 进来、且客户端声明支持 PUSH(绝大多数现代浏览器已禁用),
Pusher才非 nil - 调用
Push必须早于任何WriteHeader或Write,否则 panic:push: http: invalid push after headers sent
Push 调用前必须做类型断言和判空
不能直接对 http.ResponseWriter 调 Push,它不是接口方法。你得先尝试把它转成 http.Pusher,再检查是否为 nil —— 这步漏掉,上线后可能静默失败,也可能 panic。
示例片段:
立即学习“go语言免费学习笔记(深入)”;
func handler(w http.ResponseWriter, r *http.Request) {
if p, ok := w.(http.Pusher); ok {
if err := p.Push("/style.css", &http.PushOptions{
Method: "GET",
}); err != nil {
// log but don't fail the main response
log.Printf("push failed: %v", err)
}
}
// ... rest of handler: write status, headers, body
}
-
http.Pusher是一个未导出实现的接口,只在net/http内部满足,所以断言是唯一途径 -
PushOptions.Method必须是"GET"或"HEAD",其他值会返回http: invalid push method - 路径必须是绝对路径(如
"/script.js"),相对路径或带协议的 URL 会报错
现代浏览器基本已弃用 Server Push
Chrome 96+、Firefox 97+、Edge 96+ 全面禁用了 HTTP/2 Server Push。原因是实测收益低、易引发资源竞争、难以控制优先级,反而拖慢首屏。这意味着:即使你的 Go 服务正确调用了 Push,绝大部分真实用户根本收不到推送。
替代方案更实际:
- 用
<link rel="preload">让浏览器主动提前拉关键资源(CSS/JS/font) - 合并小资源、减少请求数,比 Push 更可控
- 考虑 HTTP/3 + QPACK 优化头压缩,而非依赖 Push
如果你还在压测环境看到 Push 生效,别高兴太早——那只是 curl 或旧版工具的行为,不代表线上效果。
想真做资源预加载,用 http.ResponseController(Go 1.22+)
Go 1.22 新增了 http.ResponseController,提供更底层的连接控制能力,包括发送 103 Early Hints。这比 Server Push 更兼容、更轻量,且被 Chrome/Firefox 支持。
用法示例:
func handler(w http.ResponseWriter, r *http.Request) {
if rc, err := http.NewResponseController(w); err == nil {
_ = rc.WriteHeader(103, map[string][]string{
"Link": {`<https://example.com/style.css>; rel=preload; as=style`},
})
}
w.WriteHeader(200)
w.Write([]byte("<html>...</html>"))
}
-
103 Early Hints不触发渲染,只让浏览器提前发起资源请求 - 不需要 HTTP/2,HTTP/1.1 也能用(只要客户端支持)
- 不依赖客户端 PUSH 能力,规避了 Push 的协商失败问题
真正要落地资源预加载,现在该盯的是 103 和 Link 头,而不是花力气绕过 Go 标准库去硬推 Push。很多团队卡在“为什么 Push 不生效”,其实答案早就写在浏览器更新日志里了。










