HTTP/2 在 Go 里是自动启用的,但“自动”有一堆前提:要有 TLS、客户端没有自定义 TLS 配置、服务端没有关闭它。多路复用也不意味着一个连接扛所有请求。这篇用自签名证书的本地服务做实验,把启用条件、一个共享 tls.Config 的坑、h2c 和并发流的实际表现都跑出来。
HTTPS 服务器(默认配置)+ DefaultTransport 的副本(只改 TLSClientConfig 跳过证书校验):客户端看到 resp.Proto=HTTP/2.0;服务端看到 proto=HTTP/2.0
自己写 &http.Transport{TLSClientConfig: 新建的配置}(没有 ForceAttemptHTTP2):客户端看到 resp.Proto=HTTP/1.1;服务端看到 proto=HTTP/1.1
同上并设 ForceAttemptHTTP2: true:客户端看到 resp.Proto=HTTP/2.0;服务端看到 proto=HTTP/2.0
服务器把 TLSNextProto 设为空 map(关闭 HTTP/2)+ 默认配置的客户端:客户端看到 resp.Proto=HTTP/1.1;服务端看到 proto=HTTP/1.1
明文 HTTP(没有 TLS)+ 默认客户端:客户端看到 resp.Proto=HTTP/1.1;服务端看到 proto=HTTP/1.1默认配置的服务端和 DefaultTransport 的副本,跑 TLS 就是 HTTP/2;自己新建 &http.Transport{TLSClientConfig: …} 就退回 HTTP/1.1,要加 ForceAttemptHTTP2: true(源码注释说这是为了不意外地改动用户的 tls.Config);服务端把 TLSNextProto 设成空 map 也能关掉它;明文 HTTP 默认是 HTTP/1.1。
共享的 tls.Config 在使用前 NextProtos = [] 第一个 Transport(ForceAttemptHTTP2)发一个请求:客户端看到 resp.Proto=HTTP/2.0;服务端看到 proto=HTTP/2.0 之后同一个 tls.Config 的 NextProtos = [h2 http/1.1](被 Transport 改过了) 第二个 Transport 用同一个 tls.Config、不设 ForceAttemptHTTP2:错误:Get "https://127.0.0.1:52929": net/http: HTTP/1.x transport connection broken: malformed HTTP response "\x00\x00$\x04\x00\x00\x00\x00\x00\x00\x05\x00\x10\x00\x00\x00\x03\x00\x00\x00\xfa\x00\x06\x00\x10\x01@\x00\x01\x00\x00\x10\x00\x00\x04\x00\x10\x00\x00\x00\t\x00\x00\x00\x01"
开启了 HTTP/2 的 Transport 会把 NextProtos 改成 [h2 http/1.1]。如果另一个没开 h2 的 Transport 共用同一个 tls.Config,它会向服务器宣告支持 h2,服务器选了 h2,而它自己只会说 HTTP/1.x,于是出现 “malformed HTTP response” 之类难懂的错误。每个 Transport 用自己的 tls.Config。
明文 TCP 上的 HTTP/2(h2c):Server.Protocols 设 HTTP1 + UnencryptedHTTP2;客户端 Transport.Protocols 只设 UnencryptedHTTP2:客户端看到 resp.Proto=HTTP/2.0;服务端看到 proto=HTTP/2.0
Go 1.24 起可以用 Protocols 在明文 TCP 上跑 HTTP/2:服务端启用 UnencryptedHTTP2,客户端的 Transport 也只启用它,实验里两边都看到了 HTTP/2.0。
HTTP/2,服务端默认 MaxConcurrentStreams:TCP 连接 50–50 个;同时在跑的 handler 最多 300–300 个;总用时 212–222 ms HTTP/2,Server.HTTP2.MaxConcurrentStreams = 10:TCP 连接 227–383 个;同时在跑的 handler 最多 96–160 个;总用时 1248–3264 ms HTTP/1.1(服务器关闭 h2):TCP 连接 196–299 个;同时在跑的 handler 最多 196–300 个;总用时 233–258 ms HTTP/2,服务端默认(冷启动):TCP 连接 153–370 个;总用时 237–267 ms 服务端限 10 个流 + 客户端 StrictMaxConcurrentRequests = true:TCP 连接 0–0 个;同时在跑的 handler 最多 1–10 个;成功 1–10 个、失败 20–29 个
300 个并发请求,服务端默认设置、预热过一个连接后:新增了 50 个 TCP 连接,300 个 handler 同时在跑,约 215 ms 完成——单个连接的并发流有上限,超过后客户端会再开连接。把服务端的 MaxConcurrentStreams 设成 10,连接数涨到 227–383 个,用时 1.2–3.3 秒,比 HTTP/1.1(233–258 ms)还慢。冷启动时(没有现成连接)并发请求会各自拨号,TCP 连接 153–370 个。客户端设 StrictMaxConcurrentRequests 的那组实验 30 个请求在 5 秒内最多成功 10 个,原因我没有查清,所以不下结论。
MaxConcurrentStreams optionally specifies the number of concurrent streams that a client may have open at a time. If zero, MaxConcurrentStreams defaults to at least 100. // This parameter only applies to Servers. StrictMaxConcurrentRequests controls whether an HTTP/2 server's concurrency limit should be respected across all connections to that server. If true, new requests sent when a connection's concurrency limit has been exceeded will block until an existing request completes. If false, an additional connection will be opened if all existing connections are at their limit. // - HTTP1 is the HTTP/1.0 and HTTP/1.1 protocols. HTTP1 is supported on both unsecured TCP and secured TLS connections. - HTTP2 is the HTTP/2 protocol over a TLS connection. - UnencryptedHTTP2 is the HTTP/2 protocol over an unsecured TCP connection.
MaxConcurrentStreams 默认至少 100,Protocols 的三种取值把 “TLS 上的 HTTP/2” 和 “明文上的 HTTP/2” 分开。