HTTP/3 in Go
Posted 相思的雨
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HTTP/3 in Go相关的知识,希望对你有一定的参考价值。
写在前面,目前 HTTP/3 协议仍在实验阶段,RFC 进行到了第 29 号版本,将于 2020/12/11 日失效,本文所讨论的均为已经达成一致的部分;前半部分介绍了一些简单的理论,后半部分进行代码实现,希望通过代码实现能够让你对 HTTP/3 有个大概的概念,在后续的概念普及中有所印象。
概念部分
本篇的概念部分主要介绍协议的升级方式,由于在协议普及过程中,互联网中存在着大量的旧服务应用,必须提供一种从低版本协商升级到新版本协议的方式。
我们首先复习一下 HTTP1.1 升级到 HTTP2的方式,分为 h2c
和 h2
;然后我们会聊到 HTTP Alternative Services(HTTP 替代服务),这是一个很有趣的协议,然后举例 HTTP/3 是如何通过 ALTSVC
协议进行自举的。
0x01 H2C
如果你在浏览器中输入 HTTP 的域名,未进入 HTTPS 协议,如果浏览器启用 HTTP/2,那么会在发起时设置 Upgrade
和 HTTP2-Settings
:
GET / HTTP/1. 1
Host: server. example. com
Connection: Upgrade, HTTP2-Settings
Upgrade: h2c
HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
服务端如果支持 HTTP/2,则响应:
HTTP/1. 1 101 Switching Protocols
Connection: Upgrade
Upgrade: h2c
客户端在接收到 101
响应后,就会发起 HTTP/2 的第一个帧(SETTINGS
帧),即开始 HTTP/2。
0x02 H2
如果你在浏览器输入的是 HTTPS 域名或 URL,则浏览器会在启用 HTTP/2 时,进行 H2 的升级协商。
H2 协商建立在 TLS 之上,采用 ALPN 扩展协议,采用 h2
作为协议标识符,由于通过 TLS 所以称之为 安全版本
。
流程如下:
客户端和服务端 TCP 握手
客户端和服务端 TLS 层协商
客户端发送链接序言(
SETTINGS
帧)服务端响应链接序言
双方各自确认序言
其他帧传输
下面的一张图提供了一个简单对比,可以参考:
图片来自:《HTTP/2 笔记之连接建立》
H2C 协商升级,是为了 HTTP/1.1 的广泛存在,在未启用 TLS 时提供一种协议切换的升级,HTTPS 则是一个强制的协商,在 TLS 的 ALPN 扩展中直接完成协议的交换。
HTTP/1.1 升级到 HTTP/2 用了近十六年的时间,中间经过了很多变迁,网际中存在着对 HTTP/1.1 和 TCP 大量优化和应用,由于 HTTP/1.1 是文本协议,HTTP/2 是二进制协议,两者直接跨度很大,所以设计了两种对应的协议迁移方式;又由于此,HTTP/2 并非全部基于安全协议。
0x03 HTTP Alternative Services
HTTP Alternative Services(HTTP 替代服务)是发布于 2016 年上半年的一份 RFC7838,协议约定当前请求的资源,可以通过备用主机,端口或者协议获取。
在当前的架构中,我们常用的分流方式一般分为两种:
DNS 分区域或供应商解析不同 IP
SLB 进行负载均衡,大多数有专有机器提供服务
不同于使用 30x 状态码进行重定向分流,HTTP Alternative Services 只改变浏览器获取资源的网络方式,上层应用不会感觉到任何变化。
HTTP Alternative Services 协议
定义 HTTP
头部如下:
Alt-Svc: h2="alt.example.com:8000", h2=":443"; ma=2592000; persist=1
h2
代表着 HTTP/2,ma
是 max-age 的缩写,persist
表示在遇到网络变更时依然有效。
在 HTTP/1 中,Alt-Svc
必须在首次响应的头部返回,只有在第二次请求时,浏览器才会使用备用服务,在 HTTP2 中新增 ALTSVC
帧,可以单独发送,浏览器可以在首次请求就切换为备用服务。
0x04 H3
由于互联网中存在大量不可预知的情况,所以正如 HTTP/1 到 HTTP/2 一样,需要有一种协议协商的手段,让浏览器知道什么时候可以使用 HTTP/3,所以 H3 也一样规定了协议协商的方式。
HTTP/3 通过 ALTSVC
协议实现自举,告知浏览器其已支持 HTTP/3,浏览器可切换至 UDP 的方式进行通讯。
Alt-Svc: h3-29=":4443"; ma=2592000
根据 draft-ietf-quic-http-29#section-3.1 《Draft Version Identification》章节所述:
HTTP/3 uses the token "h3" to identify itself in ALPN and Alt-Svc.
Only implementations of the final, published RFC can identify themselves as "h3". Until such an RFC exists, implementations MUST NOT identify themselves using this string.
在 HTTP/3 正式发布之前,必须不能使用 h3 作为相关标识,所以做为第 29 版实验支持版本,上面协议部分标识为 h3-29
;自举样例表示含义,本服务支持 h3 的第 29 号实验版本,通过与响应相同的 IP 的 4443
端口进行通信,有效期 2592000
秒。
基于此,浏览器可以在 HTTP/2 甚至是 HTTP/1 中完成自举,告知浏览器自举已经支持 HTTP/3,同上文中提到的一样,在 HTTP/2 中,可以实现在首次请求时切换协议,而在 HTTP/1 中,只能在第二次请求中切换到新的协议(在实验中,大多是在第二次请求才切换 HTTP/3)。
实验部分
由于 HTTP/3 通过 QUIC 实现通讯,QUIC 使用 TLS 1.3 传输层安全协议(RFC 8446),且不存在非加密版本;所以在本实验中,你必须提前准备秘钥和证书,你可以申请正式的秘钥和证书,也可以根据《》 生成自签名证书和秘钥。
上篇中,我们通过自签名在 Go 语言下实现了 HTTPS Service;本节我们通过实验的方式,一步步来了解 HTTP3;如果你没有读过,那么建议你阅读一下,当然如果你对自签名的原理和用法十分清晰,那么直接进行本节也是没有任何难度的。
0x05 HTTPS
我们先实现一个最简单的 HTTPS Service:
package main
import (
"fmt"
"net/http"
)
func main() {
// 请注意匹配正确的路径
certFile := "/Users/Shared/go/quic/tls/server.crt"
keyFile := "/Users/Shared/go/quic/tls/server.key"
handle := func(w http.ResponseWriter, req *http.Request) {
_, _ = fmt.Fprintf(w, "<html><body>hello world</body></html>")
}
mux := http.NewServeMux()
mux.HandleFunc("/", handle)
err := http.ListenAndServeTLS(":443", certFile, keyFile, mux)
fmt.Println(err)
}
请替换上面代码中的证书和私钥,你可以通过《》章节的内容进行自签名,并将 CA 证书导入对应的浏览器或系统证书,并选择信任;或者你可以去申请正式的证书,并通过本地 HOST 指向去测试。
你可以直接通过 IDE 直接运行或者在对应的目录下执行 go run main.go
,执行后控制台没有任何输出,但是请注意由于 443
端口,可能需要 sudo
权限,这取决于你运行的系统和用户。
如果你正确的导入了信任证书,或者使用了正式的可信证书,那么此时你用浏览器访问 https://localhost
应该能够正确的看到 hello world
,或者通过 curl https://localhost/ -i -k
(-k 用户忽略证书验证,否则你需要通过 --cacert 指定 CA 证书位置),那么你可以看到以下输出:
HTTP/2 200
content-type: text/html; charset=utf-8
content-length: 37
date: Mon, 10 Aug 2020 10:05:36 GMT
<html><body>hello world</body></html>
通过 CURL 的 -i 参数,我们看到了,在配置证书和私钥文件后,Go http Server 会优先为我们启用 HTTP2
。
请注意,到这一步我们应该已经成功的实现了 HTTP2
(这里我们暂不考虑 H2C)。
0x06 Quic-go
quic-go is an implementation of the QUIC protocol in Go. It roughly implements the IETF QUIC draft, although we don't fully support any of the draft versions at the moment.
go get github.com/lucas-clemente/quic-go
Quic-go 是 Go 语言实现的 QUIC 协议,知名 Caddy 服务器软件就是基于此包实现的 HTTP/3 协议的支持,接下来让我们通过样例,来将 HTTP/3 跑起来,让 HTTP/3 看起来离我们并不远。
package main
import (
"fmt"
"net/http"
"github.com/lucas-clemente/quic-go/http3"
)
func main() {
certFile := "/Users/Shared/go/quic/tls/server.crt"
keyFile := "/Users/Shared/go/quic/tls/server.key"
handle := func(w http.ResponseWriter, req *http.Request) {
_, _ = fmt.Fprintf(w, "<html><body>hello world</body></html>")
}
mux := http.NewServeMux()
mux.HandleFunc("/", handle)
err := http3.ListenAndServe(":443", certFile, keyFile, mux)
fmt.Println(err)
}
什么?这与上面的 HTTPS Service 有什么区别?
是的,只有 http3.ListenAndServe
从 http
变为 http3
并引入了 github.com/lucas-clemente/quic-go/http3
包,但是我却用了很长时间才将样例代码跑起来。
接下来我简单的介绍下 quic-go
包的实现:
首先 提供了
ListenAndServe
,ListenAndServeQUIC
,以及Service
去自己实现Service.ListenAndServe
http3.ListenAndServe
加载了证书和秘钥,并同时监听了 Addr 的 TCP 和 UDP 连接并且为 TCP 的
Handle
注册一个全局SetQuicHeaders
实现ALTSVC
自举TCP 连接则有
http
包实现Service
,UDP 有quic-go
实现Service
quic-go
的Service
有着与http
的Service
类似的接口,监听端口后进行处理
0x07 验证
上面的代码,你可能已经直接 Run
起来了,可是却不知道怎么验证,是否成功?
我在准备文章之前曾尝试几种方式:
新版本的 CURL,有依赖问题
Curl + Quiche,编译麻烦
http3-client,同样需要编译
谷歌浏览器,需要导入 CA 到系统证书
https://www.mozilla.org/zh-CN/firefox/developer/
安装完成后,需要进行下面的配置:
在弹出的
三思而后行
提示页面,点击接受风险并继续
在
搜索首选项
输入框中输入:http3
双击
network.http.http3.enabled
所在行,启用HTTP3
引用
RFC7838: HTTP Alternative Services
HTTP Alternative Services 介绍
HTTP/2 笔记之连接建立
HTTPS 深入浅出 - 什么是 ALPN?
Hypertext Transfer Protocol Version 3 (HTTP/3)[draft-ietf-quic-http-29]
Get a head start with QUIC
一文看完 HTTP3 的演化历程
如何玩转 HTTP 3?
科普:QUIC 协议原理分析
以上是关于HTTP/3 in Go的主要内容,如果未能解决你的问题,请参考以下文章
golang 片段7 for https://medium.com/@francesc/why-are-there-nil-channels-in-go-9877cc0b2308
GO 智能合约cannot use transactionRecordId + strconv.Itoa(id) (type string) as type byte in append(示例代码(代
[Go] 通过 17 个简短代码片段,切底弄懂 channel 基础
一旦单击带有 in 片段的回收器列表项,如何将片段意向活动,以及如何获取回收器项目值?
What's the difference between @Component, @Repository & @Service annotations in Spring?(代码片段