Go 标准库 HTTP2 协议升级细节
客户端行为
首次调用 Transport.RoundTrip()
-
初始化 Transport 的 TLS ALPN 列表,加入 "h2" ALPN (code)。同时设置 "h2" ALPN 对应的协议升级函数 (code)。
-
创建TCP连接,随即启动TLS握手流程 (code)。客户端发送给服务端的 TLS 握手消息,会携带步骤1中设置的 "h2" ALPN (code),与服务端协商。
-
服务端接受 "h2" ALPN,同样在握手消息中携带 "h2" 作为 ALPN 协商的结果,响应给客户端。客户端缓存此结果 (code)。
-
客户端根据 ALPN 协商结果——"h2",找回步骤1中设置的协议升级函数,通过此函数生成新的 HTTP2 Transport ,将其保存到连接的
alt
变量中 (code)。 -
因为 HTTP2 连接可以同时被多个请求复用(通过 HTTP2 Stream),没有被占用的问题,直接把它当成是“空闲”连接,放入到连接池中(code)。
-
由于连接的
alt
变量(HTTP2 Transport )不为空,客户端直接使用此 Transport 发送 http 请求,绕过了 HTTP1 相关的处理 (code)。 -
HTTP2 Transport 创建一个新的 Stream 发送请求 (code)。
后续调用 Transport.RoundTrip()
-
客户端在连接池中发现“空闲”状态的 HTTP2 连接,因为 HTTP2 连接可以同时被多个请求复用(通过 HTTP2 Stream),没有被占用的问题。直接使用这个连接,而且无需将它从连接池中移除(仍旧保持“空闲”状态)。(code)
-
由于连接的
alt
变量(HTTP2 Transport )不为空,客户端直接使用此 Transport 发送 http 请求,绕过了 HTTP1 相关的处理 (code)。 -
HTTP2 Transport 创建一个新的 Stream 发送请求 (code)。
服务端处理
- 服务端启动时,会初始化所支持的 TLS ALPN 列表,加入 "h2" ALPN (code)。同时设置 "h2" ALPN 对应的协议升级函数 (code)。
- 接受TCP连接,开始TLS握手流程(code)。不出意外,客户端会在 TLS 握手消息中提供 "h2" ALPN,服务端根据步骤1提供的所支持的 TLS ALPN 列表,最终 "h2" 会被选择为协商的结果。服务端缓存此结果(code)。
- 服务端基于 ALPN 协商结果——"h2",找回步骤1中设置的协议升级函数,直接通过此函数处理 HTTP 请求,绕过了 HTTP1 相关的处理 (code)。
- 处理 HTTP2 Stream (code)。
结论
- Go 标准库通过 TLS (HTTPS) 的 ALPN 协商 直接识别处理 HTTP2 流量,没有走常规 HTTP1 升级 HTTP2 的流程,缩短了连接建立的时间,性能更好。
- Transport 连接池托管 HTTP2 连接的意义不大,对于同一个域名,连接池内通常只有一个 HTTP2 连接,通过在 HTTP2 连接内 创建多个 Stream 来复用连接。