登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go问答

Go h2c 明文升级失败的网络层排查清单

来源:17golang原创

时间:2026-09-28 23:22:33 238浏览 收藏

Go 的 h2c 明文升级失败,优先检查网络层,不要先改业务 Handler。把请求从客户端到 Go 服务端拆成四段:客户端是否发起 h2c、代理是否保留升级字段、服务端是否接受明文 HTTP/2、响应是否已经进入 HTTP/2。只要其中一段把 h2c 当成普通 HTTP/1.1、HTTPS 或未知协议,常见结果就是 400、426、502、连接重置,甚至看到 HTTP/2 前言错误。

要点速览
  • h2c 是不带 TLS 的 HTTP/2;“http://”只说明地址明文,不代表请求已经使用 HTTP/2。
  • Upgrade 模式要同时保留 Connection、Upgrade 和 HTTP2-Settings,代理剥离任意一项都会让升级失效。
  • Go 1.27+ 可用 Server.Protocols 与 Transport.Protocols 配置明文 HTTP/2;旧项目再考虑 h2c.NewHandler。

先确认失败发生在哪一层

h2c 有两种建立方式。没有先验信息时,客户端先发 HTTP/1.1 Upgrade 请求;已经知道对端支持时,也可能直接发送 HTTP/2 connection preface。前者需要升级头,后者开头通常是 PRI * HTTP/2.0。因此只看最终的业务状态码不够,必须确认到达 Go 监听端口的第一段字节和请求头。

GET /health HTTP/1.1
Host: service.internal
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: AAMAAABkAAQCAAAAAAIAAAAA

如果客户端声称使用 h2c,但服务端日志只有普通 HTTP/1.1,先查 Transport 或 SDK;如果直连成功、经过网关失败,优先查网关的 hop-by-hop 头处理;如果直连也失败,再查服务端是否真的启用了明文 HTTP/2。下面这张图用于区分这些静态边界。

h2c 客户端握手、反向代理与 Go Handler 的静态关系说明图
图1:h2c 握手边界说明图,展示客户端字段、中间代理与 Go Handler 的静态关系,不是抓包截图。

沿客户端到服务端逐跳检查升级字段

HTTP/1.1 的 Connection 是逐跳字段,反向代理通常会重新生成它。升级请求至少要让下一跳看到 Upgrade: h2c,并保留与之配套的 HTTP2-Settings。代理若把请求转成普通 HTTP/1.1,后端会正常收到一个“没有升级意图”的请求;这不是 Go Handler 失效,而是协议协商在到达 Handler 前已经结束。

观察结果更可能的边界先查什么
直连成功,经过代理返回 400/426升级字段未逐跳保留代理路由、Connection/Upgrade 重写和 HTTP/2 上游模式
服务端收到 PRI 后立即断开监听端口不接受先验明文 HTTP/2Server.Protocols 或旧版 h2c handler
出现 TLS handshake errorh2 与 h2c 混用URL、端口、TLS 终止位置和客户端 Transport
502 且后端没有访问日志代理到后端的连接或协议失败代理 upstream 日志和后端监听地址

排查时固定做一次“直连”和一次“经代理”对照,并记录每一跳的 http/https、是否 TLS、是 Upgrade 还是先验连接。不要把代理层已经返回的 502 当成业务程序的错误响应。

把 Go 服务端与客户端协议对齐

当前 Go 的 net/http 已提供原生明文 HTTP/2 配置。需要同时兼容 HTTP/1.1 的服务端可以这样声明协议集合:

package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
        // 返回固定结果,便于用直连和代理两条路径比较协议状态。
        w.WriteHeader(http.StatusNoContent)
    })

    protocols := new(http.Protocols)
    protocols.SetHTTP1(true)              // 保留 HTTP/1.1 回退能力。
    protocols.SetUnencryptedHTTP2(true)  // 接受明文 HTTP/2,包括 h2c 场景。

    srv := &http.Server{
        Addr:     ":8080",
        Handler:  mux,
        Protocols: protocols,
    }
    log.Fatal(srv.ListenAndServe()) // 明文监听;TLS 终止应放在明确的边界上。
}

客户端也必须显式选择明文 HTTP/2。若只打开服务端而客户端仍使用默认 HTTP/1.1,服务端“支持 h2c”并不能自动改变客户端协议。

protocols := new(http.Protocols)
protocols.SetUnencryptedHTTP2(true) // 只允许 http:// 请求使用明文 HTTP/2。

transport := &http.Transport{Protocols: protocols}
client := &http.Client{Transport: transport}
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/health", nil)
if err != nil {
    // 请求构造失败时立即返回,避免把本地参数错误误判成网络协商失败。
    log.Fatal(err)
}
resp, err := client.Do(req)
if err != nil {
    // 这里的错误仍需结合代理日志和服务端首字节判断协议层原因。
    log.Fatal(err)
}
defer resp.Body.Close() // 释放连接资源,避免排查时混入连接池残留状态。

旧项目如果仍使用 golang.org/x/net/http2/h2c,应把它看作兼容方案,确认依赖版本和 Go 工具链后再维护;当前官方包文档已经将该包标记为 deprecated,并建议改用 net/http 的明文 HTTP/2 能力。使用旧写法时,核心不是给 Handler 增加重试,而是确保监听器的 Handler 确实由 h2c.NewHandler 包裹,并限制首个升级请求的读取大小。

Go Server Protocols、Transport Protocols 与 h2c 兼容边界结构图
图2:Go h2c 配置关系结构图,展示服务端与客户端协议能力的对应边界,不是运行界面。

用最小证据完成反向验证

修复后不要只看“请求返回 204”。先用一个无业务副作用的健康路径,分别验证直连和代理;再在服务端临时记录 r.Proto、r.TLS 是否为空以及代理注入的追踪头。Proto=HTTP/2.0 只能说明请求已进入 HTTP/2,不能证明中间每一跳都使用了 h2c;还要对照网关的 upstream 协议日志。

上线前可以按这份清单收口:监听端口是否明确为明文;客户端是否启用 UnencryptedHTTP2;代理是否保留 Upgrade 相关字段或改用明确的 HTTP/2 upstream;TLS 是否只在约定的位置终止;HTTP/1.1 回退是否仍有意义;超时、连接复用和最大首个请求大小是否有边界。h2c 传输本身不提供 TLS 加密,适合受控的可信网络链路,不应因为“已经是 HTTP/2”就把它当成公网安全通道。

常见边界问题

为什么客户端用了 http://,服务端仍然不是 HTTP/2

http://只代表没有 TLS。客户端还需要选择 Upgrade 或明文 HTTP/2 先验模式,服务端也要接受对应协议。

代理必须透传 h2c 头吗

取决于代理到后端采用的协议。如果代理终止客户端连接并以另一种协议连后端,重点是两端协议能力匹配,不是机械复制所有头;若要做端到端 Upgrade,则必须检查逐跳字段是否被保留。

看到 connection preface 错误先改 Handler 吗

不建议。先判断对端发送的是 HTTP/2 前言还是 TLS ClientHello,再确认监听器协议配置。首字节错配时,业务 Handler 通常还没有机会执行。

官方资料:https://www.rfc-editor.org/rfc/rfc7540/、https://go.dev/src/net/http/doc.go、https://pkg.go.dev/net/http、https://pkg.go.dev/golang.org/x/net@v0.59.0/http2/h2c。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>