对 nginx 的 listen 指令中 HTTP 版本指定的一点研究

陪她去流浪 桃子 2022年10月03日 编辑 微信上查看 阅读次数:2007

最近在公司用 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 配置如下:

1
2
3
4
5
6
7
server {
	listen 8080 http2;

	location / {
		return 200 "it's ok!";
	}
}

尝试用 curl 直接请求一下:

1
2
$ curl localhost:8080
curl: (1) Received HTTP/0.9 when not allowed

看样子是不行的。

如果指定使用 HTTP/2 的版本呢:

1
2
$ curl --http2 localhost:8080
curl: (1) Received HTTP/0.9 when not allowed

嗯?什么情况这是? 看 curl 的文档发现,还有一个叫作 --http2-prior-knowledge 的选项:

1
2
$ curl --http2-prior-knowledge localhost:8080
it's ok!

终于可以了!

文档对这两个选项的说明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
--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 的握手协议。

我尝试发了一个请求:

1
2
3
$ curl -i https://blog.twofei.com
HTTP/2 200
...

发现我博客确实是支持 HTTP/2 的。 然后我再尝试抓了一下包发现,curl 在不确定对方是否支持 HTTP/2 的情况下,在 TLS 的 Extension 里面提供了自己可以支持的协议:

client hello

然后服商端在紧接着的回包中,应答了双方应采用的协议:

server hello

所以,在进行正式的 HTTP 协议通信之前,客户端和服务端就已经协商好了要使用的 HTTP 协议版本。 严格地说,HTTP/2 确实不兼容 HTTP/1.1 协议(二进制层面)。

“二进制层面”是指数据在 TCP 连接上的流动。

总结

简单总结一下:

  • HTTP/2 可以在 非 TLS/SSL 下工作;
  • HTTP/2 不兼容 HTTP/1.1 (二进制层面);
  • 如果配置了 TLS(证书),则 nginx 可以在 HTTP/2 模式下处理 HTTP/1.1 协议。

参考

标签:nginx · HTTPS · TLS · gRPC · ALPN