在chrome浏览器中有一个机制“Connection Coalescing”,它将发往不同域名(Host)的请求复用到同一条 TCP 连接上,判断条件如下:
IP 地址必须相同
证书必须覆盖所有请求域名(证书的Subject Alternative Name或者common name)
同类的域名请求都必须是https访问
例:现有业务域名 a.silenceweb.cn b.silenceweb.cn,他们部署在各自的后端服务上,但他们使用相同的ssl证书,同时支持http/2(或先被访问的域名支持http/2)。
当chrome发起a域名的http/2连接时,会产生一个socket、http2_session。
当chrome的另一个页签向b域名发起https连接时,会复用a域名的socket、http2_session。
但如果是在a域名的页签html代码中调用b域名,则不会复用,会新起一个连接。(忽略协议 http/1.1,为了测试我停用了app server b的h2,实际无论是哪种协议都会这样)

没错就是这样反直觉,将在后面给出实验记录。
这个机制可能会引发什么问题呢?
当我们在网络链路中插入4层代理时可能会导致域名请求无法转发到对应的服务上。
链路如下:client -> proxy server -> app server
当首次访问域名a时:
client上的代理配置使域名a的dns解析结果指向代理服务器;(通常将所有需要四层代理的域名都指向 proxy server)
client与proxy server建立tcp连接;
cilent向proxy server发送client hello报文
proxy server抽取client hello报文中的SNI用于解析app server ip;
proxy server与app server建立tcp连接,开始转发4层以上的报文,包括tls握手报文。
后续该条连接进来的所有数据都转发给这个app server a,即使请求的http.heders.host已经发生变化,由于是四层代理无法感知。
如果域名a以外的业务域名没有在这个app server a上部署,将无法正常获取到响应。
典型的nginx stream代理配置如下:
stream {
resolver 8.8.8.8 valid=300s ipv6=off;
resolver_timeout 5s;
server {
listen 443;
# 开启 SNI 预读,获取客户端 TLS 握手中的域名
ssl_preread on;
# 使用 SNI 域名和当前监听端口作为转发目标
# 因为变量参与,nginx 会通过 resolver 实时解析该域名
proxy_pass $ssl_preread_server_name:$server_port;
# 可选:超时等参数
proxy_connect_timeout 10s;
}
}实验信息记录
证书信息
自签的证书需要安装到操作系统的受信证书,否则浏览器不会使用该机制

net-export-log
使用浏览器的工具 net-export 导出网络日志后再使用 https://netlog-viewer.appspot.com/ 工具格式化文件。
HTTP/2 session统计
可见域名a、b在同一个会话中,点击ID可以进入Events详情

Events详情
HTTP2_SESSION ID: 4587 它使用的 SOCKET ID 4581

会话中对域名a的请求

会话中对域名b的请求

http业务异常
由于域名b的业务部署在app server b,但域名b的请求被proxy server转发给了app server a,所有无法得到正确响应。

代理服务器tcpdump包
握手过程
