对 nginx 的 listen 指令中 HTTP 版本指定的一点研究
最近在公司用 nginx 代理 grpc 时发现,公司的 nginx 竟然在不启用 HTTP/2 的配置下也能支持对 grpc 的代理。 这引起了我的好奇心,因为 grpc 是基于 HTTP/2 的。难道是我记错了?于是找到相关人员询问了一下是如何做到的。 答曰:公司的 nginx 是经过改造的。OK,fine,不是我的问题。
不过,鉴于我对 HTTP/2 一直不太了解,我还是打算稍微研究一下:
- HTTP/2 可以在非 TLS/SSL 下工作吗?
- HTTP/2 兼容 HTTP/1.1 吗?
HTTP/2 可以在非 TLS/SSL 下工作吗?
nginx 说它是支持的:
The http2 parameter (1.9.5) configures the port to accept HTTP/2 connections. Normally, for this to work the ssl parameter should be specified as well, but nginx can also be configured to accept HTTP/2 connections without SSL.
nginx 配置如下:
server {
listen 8080 http2;
location / {
return 200 "it's ok!";
}
}
尝试用 curl 直接请求一下:
$ curl localhost:8080
curl: (1) Received HTTP/0.9 when not allowed
看样子是不行的。
如果指定使用 HTTP/2 的版本呢:
$ curl --http2 localhost:8080
curl: (1) Received HTTP/0.9 when not allowed
嗯?什么情况这是?
看 curl 的文档发现,还有一个叫作 --http2-prior-knowledge
的选项:
$ curl --http2-prior-knowledge localhost:8080
it's ok!
终于可以了!
文档对这两个选项的说明:
--http2-prior-knowledge
(HTTP) Tells curl to issue its non-TLS HTTP requests using HTTP/2 without HTTP/1.1
Upgrade. It requires prior knowledge that the server supports HTTP/2 straight away.
HTTPS requests will still do HTTP/2 the standard way with negotiated protocol ver‐
sion in the TLS handshake.
--http2-prior-knowledge requires that the underlying libcurl was built to support
HTTP/2. This option overrides --http1.1 and -0, --http1.0 and --http2. Added in
7.49.0.
--http2
(HTTP) Tells curl to use HTTP version 2.
See also --http1.1 and --http3. --http2 requires that the underlying libcurl was
built to support HTTP/2. This option overrides --http1.1 and -0, --http1.0 and
--http2-prior-knowledge. Added in 7.33.0.
所以,--http2-prior-knowledge
是直接发 HTTP/2 请求,而推理出 --http2
是通过 HTTP/1.1 的 Upgrade 协议升级到 HTTP/2 (h2c)。
由于我们的 nginx 配置的是 HTTP/2。从实验结果看,不支持 HTTP/1.1。
综上,HTTP/2 可以在非 TLS/SSL 下工作。
HTTP/2 兼容 HTTP/1.1 吗?
所以 HTTP/2 真的兼容 HTTP/1.1 吗?
先说结论:是,也不是。 说“不是”是因为,从 curl 的请求结果来看,确实请求失败。 说“是”是因为:当请求一个配置了 TLS 的 HTTP/2 网站会发现,请求成功,因为借助了应用层协议协商(Application Layer Protocol Negotiation, ALPN),它来自于 TLS 的握手协议。
我尝试发了一个请求:
$ curl -i https://blog.twofei.com
HTTP/2 200
...
发现我博客确实是支持 HTTP/2 的。 然后我再尝试抓了一下包发现,curl 在不确定对方是否支持 HTTP/2 的情况下,在 TLS 的 Extension 里面提供了自己可以支持的协议:
然后服商端在紧接着的回包中,应答了双方应采用的协议:
所以,在进行正式的 HTTP 协议通信之前,客户端和服务端就已经协商好了要使用的 HTTP 协议版本。 严格地说,HTTP/2 确实不兼容 HTTP/1.1 协议(二进制层面)。
“二进制层面”是指数据在 TCP 连接上的流动。
总结
简单总结一下:
- HTTP/2 可以在 非 TLS/SSL 下工作;
- HTTP/2 不兼容 HTTP/1.1 (二进制层面);
- 如果配置了 TLS(证书),则 nginx 可以在 HTTP/2 模式下处理 HTTP/1.1 协议。