一次 HTTP/2 通信失败的问题分析

云原生实验室

共 1675字,需浏览 4分钟

 ·

2022-03-16 06:52

背景

某业务上线 HTTP/2 以后,通过 curl 访问某接口一直失败。

开发人员怀疑可能是运维的 HTTP/2 配置不当导致访问失败,但是同样是配置 HTTP/2 的其它域名却是正常的,于是来一起看了一下这个问题。

排查

排查第一步:遇事不决先抓包,在没有任何先决信息的情况下,先抓包,看看传输了一些啥。因为 HTTP/2 要求通过 HTTPS 通信,所以这里抓包,还需要用到 wireshark 抓取 HTTPS 包的一些技巧。至于这么做,我在之前的 B 站分享有讲过,大家如果感兴趣可以看看。地址在这里:Wireshark 抓取 HTTPS 流量的 N 种方法「 https://www.bilibili.com/video/BV1ur4y1Y7NB 」

抓取 HTTPS 的包

简单来说,就是通过导出 premaster-secret 来帮助 wireshark 解密数据。wireshark ssl keylog 格式长啥样,具体的定义在 https://github.com/boundary/wireshark/blob/master/epan/dissectors/packet-ssl-utils.c#L4183

wireshark ssl keylog 格式

curl 要做的就是把 key 打印到文件里,这部分的源码在:https://github.com/curl/ lib/vtls/keylog.c ,如下图所示。

curl 源码

对于 curl 而言,我们只需要指定一个环境变量就可以了,抓取的包我们就可以解密出来了。

export SSLKEYLOGFILE=/Users/arthur/keylog.txt

wireshark 解密出来的结果如下。

wireshark 解密结果

看起来就是 HTTP2 服务端的问题发了一个错误的包导致客户端回了 rst 帧。

接下来继续看 HTTP/2 服务端回复了什么。通过查看包,果然发现了一些有意思的。

wireshark 包结果

expires 头部后面多了一个空格,其它的 header 都没有。怀疑是这个导致的,同时发现通过增加一个 Cache-Control 请求头,返回结果里 expires 头部就没有返回了,请求就成功了,因此更加确认是这个问题。

curl -H 'Cache-Control: max-age=0'  -v  https://license.bytello.com/licenseAdmin/test

返回结果如图

通过跟业务确认,确实如此,expires 后面多了一个空格,去掉以后马上访问正常了。

Expires 头

在 HTTP/1.1 时代,curl 是合法的,没有问题,在 HTTP/2 中,这里就有问题了。

当然这依然不能直接证明就是这个原因,除非 curl 亲自告诉我。

进一步分析

为什么有空格会出现问题呢?当然要从 curl 的底层去分析,curl 的 HTTP/2 底层是用 nghttp 这个库来实现的,nghttp 本来也可以通过命令行直接发起请求。

使用 nghttp 访问一下,印证了我们的想法。

nghttp 访问结果

探究源码

nghttp 是一个开源项目,可以很方面的把源码 clone 下来编译本地调试,发现他在处理 header 的时候会判定 header 是否合法

合法非法的 ASCII 字符在这里定义

可以看到空格,也就是下图中的 SPC,ASCII 码值是:32(0x20),对应的 VALID 为 0,表示空格是非法的 header 字符。

通过 GDB 同步确认这一点

GDB 调试

至此,我们就知道了为什么 curl 在处理带有空格头部时的问题,chrome、safari 也有类似的问题,大家感兴趣可以看看。

浏览 50
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报