golang 在 http/2 这块做的比较早, 但是因为历史原因导致 API 比较令人迷惑, 网上很多同学在抱怨.
我这里记录一下如何正确的实施 HTTP/2 的客户端与服务端.
HTTP/2 协议
HTTP/2 协议握手分 2 种方式, 一种叫 h2, 一种叫 h2c.
h2 要求必须使用 TLS 加密, 在 TLS 握手期间会顺带完成 HTTPS/2 协议的协商, 如果协商失败 (比如客户端不支持或者服务端不支持), 则会使用 HTTPS/1 继续后续通讯.
h2c 不使用 TLS,而是多了一次基于 HTTP 协议的握手往返来完成向 HTTP/2 协议的升级, 一般不建议使用.
GO 标准库
GO 的 http 库默认支持 HTTP/2 协议, 只要我们使用 TLS 则会默认启动 HTTP/2 特性.
http 库在设计 API 时并没有支持用户使用 h2c, 而是鼓励使用 h2.
只要我们使用 TLS, 则 http 库就会默认进行 HTTPS/2 协商, 协商失败则蜕化为 HTTPS/1.
让很多开发者迷惑的点在于, 当我们希望对 http Client 或者 Server 做一些更加定制化的配置时, 就会覆盖掉 http 库的默认行为, 从而导致无法启用 HTTP/2 协议.
下面我就会告诉大家, 到底怎么保证 HTTP/2 协议的启用.
服务端
我们明确一定要用 h2 模式, 也就是牺牲 TLS 加密的时间, 但是换来的就是 HTTP/2 的使用便捷性.
制作证书
我们首先需要为服务端制作证书和私钥, 其中证书会在收到请求时发回给客户端.
证书是我们自签的, 没有第三方 CA 作验证, 所以客户端需要关闭校验证书有效性的特性.
1, 生成服务端私钥
openssl genrsa -out default.key 2048
2, 生成服务端证书
openssl req -new -x509 -key default.key -out default.pem -days 3650
default.key 是私钥, default.pem 是证书.
校验证书
因为我们是 X509 格式签名的证书, 所以程序做好先做一下有效性校验:
- // TLS 证书解析验证
- if _, err = tls.LoadX509KeyPair(G_config.ServerPem, G_config.ServerKey); err != nil {
- return common.ERR_CERT_INVALID
- }
确认证书有效后, 我们最终通过 serverTLS 传入证书和私钥, 启动一个 HTTPS/2 服务:
- // HTTP/2 TLS 服务
- server = &http.Server{
- ReadTimeout: time.Duration(G_config.ServiceReadTimeout) * time.Millisecond,
- WriteTimeout: time.Duration(G_config.ServiceWriteTimeout) * time.Millisecond,
- Handler: mux,
- }
- // 监听端口
- if listener, err = net.Listen("tcp", ":" + strconv.Itoa(G_config.ServicePort)); err != nil {
- return
- }
- // 拉起服务
- go server.ServeTLS(listener, G_config.ServerPem, G_config.ServerKey)
除了使用 ServeTLS 来启动支持 HTTPS/2 特性的服务端之外, 还可以通过 http2.ConfigureServer https://godoc.org/golang.org/x/net/http2#ConfigureServer 来为 http.Server 启动 HTTPS/2 特性并直接使用 Serve 来启动服务.
客户端
客户端最重要的是配置 Transport, 所谓 Transport 就是底层的连接管理器, 包括了协议的处理能力.
因为我们有很多定制化 Client 配置的需求, 所以我们自己生成了一个 Transport 而不是内置的 Transport:
- transport = &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true,}, // 不校验服务端证书
- MaxIdleConns: G_config.GatewayMaxConnection,
- MaxIdleConnsPerHost: G_config.GatewayMaxConnection,
- IdleConnTimeout: time.Duration(G_config.GatewayIdleTimeout) * time.Second, // 连接空闲超时
- }
其中 TLS 配置声明了 InsecureSkipVerify=true, 表示不向 CA 校验证书的有效性.
类似于服务端, 因为我们没有使用内置的 Client Transport, 所以我们需要使用 http2.ConfigureTransport https://godoc.org/golang.org/x/net/http2#ConfigureTransport 来启动 HTTPS/2 特性:
- // 启动 HTTP/2 协议
- http2.ConfigureTransport(transport)
最后将 Transport 配置给 Client, 负责底层的连接与协议管理:
- // HTTP/2 客户端
- gateConn.client = &http.Client{
- Transport: transport,
- Timeout: time.Duration(G_config.GatewayTimeout) * time.Millisecond, // 请求超时
- }
最后
首先理解 h2 和 h2c 的区别, 然后明确 Go 语言推荐使用 h2.
最后, 当我们没有使用默认的 http 配置时, 我们需要通过 http2.ConfigureXXX 重新配置启用 HTTP2/S 特性.
来源: https://juejin.im/entry/5b3359966fb9a00e4e47c35e