开启了http2,但是http2_max_field_size 还在用默认值吗?
排查1个异常接口,学到一个降本和接口提速的新思路。另外找到1个charles的"bug"
现象
测试同学反馈在iOS13设备上请求接口报错,将请求复制为 curl
命令。分别导入 apifox 和 在终端执行:
- 在 apifox 请求正常 ✅
- 在终端请求失败 ❌
curl: (56) Failure when receiving data from the peer
测试同学又反馈另一个手机支持请求接口返回正常。
定位
有了正常和错误的请求curl,那直接对比二者差异就很简单了。对比发现,在终端执行失败的请求中多了一个较大的Cookie: app_token。按历史经验基本能确定是因为 Cookie 这个 header 条目太大,超过服务器的限制。 找云平台确定相关配置:
ELB
http1: header头限制 128KB,body 限制一个10G
http2: 单个 header 限制 4KB
curl 加 -v
参数再次请求,发现终端发起的请求使用的是 http2。再使用 --http1.1
参数强制使用 http1.1,接口正常返回。佐证了问题出在 http2,遵循优先快速恢复原则,我们先关闭了 http2,请求恢复正常。但还是有 3 个疑问困扰?
- 为什么有问题的curl请求在 apifox 始终能成功?
- 测试同学发现,iOS 14 和 iOS 17 的请求都能的成功,Cookie 大小也和 iOS 13的一致,但是却始终返回正常。
- 通过手动修改 Cookie 长度,找到了成功和失败的临界点。但是这个值5000多,为什么大于 4KB?
根因排查
第1个问题:apifox 无法在界面指定 http 协议,于是在启动 Charles 抓包,apifox 默认就是使用的 http1.1,所以没有问题。第2个问题:👍 测试同学又抓包发现,iOS 13 和 iOS 14 的请求对 cookie 的处理方式不同:
- iOS 13 所有的 cookie 合并到一起
- iOS 14 则逐个分开
我们现在知道了拆分 Cookie 可以跳过 nginx 的 http2_max_field_size,但这就是 iOS 14 为什么要拆分 Cookie 的原因吗?
我们在rfc文档里找到了答案https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.5http2 拆分 Cookie 可以获得更高的压缩率,新版系统应该对此有优化。因为 http2 的设计对 header 的优化其中一点就是:减少冗余数据。通过索引和动态表,减少了重复传输相同头部字段和值所需的字节数。
cookie-pairs (or "crumbs"). This header field doesn't follow the
list construction rules in HTTP (see [RFC7230], Section 3.2.2), which
prevents cookie-pairs from being separated into different name-value
pairs. This can significantly reduce compression efficiency as
individual cookie-pairs are updated.
To allow for better compression efficiency, the Cookie header field
MAY be split into separate header fields, each with one or more
cookie-pairs. If there are multiple Cookie header fields after
decompression, these MUST be concatenated into a single octet string
using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
before being passed into a non-HTTP/2 context, such as an HTTP/1.1
connection, or a generic HTTP server application.
Therefore, the following two lists of Cookie header fields are
semantically equivalent.
cookie: a=b; c=d; e=f
cookie: a=b
cookie: c=d
cookie: e=f
HTTP/2 支持 HEADER 压缩,开发了 HPACK 算法,在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,可以达到 50%~90% 的高压缩率。
回到第2个问题,“iOS 14 和 iOS 17 的请求都能的成功,Cookie 大小也和 iOS 13的一致,但是却始终返回正常”。因为 iOS 14 对 Cookie 的处理方式进行了优化。原因不是为了绕过 http2_max_field_size 的限制,而是为了获取更高的压缩率。
第3个问题:http2_max_field_size
针对单个 HeaderField 的限制,如果进行 Huffman(哈夫曼编码)压缩,那么此限制针对的是对压缩后的值进行比较;
注:此指令自版本 1.19.7 起已过时。应改用 large_client_header_buffers
。
总结
要让 HPACK 的“字典”持续使用,必须确保Key-Value不会发生变更。所以尽量拆分 HEADER 内容,不常变更的和常变更的要分开。少了 HEADER 的传输,带宽和请求时延都有提升,这是不是一个降本的好思路呢,快来检查你们接口的请求和响应 HEADER,看看有没有优化的可能性。
- HTTP/2 支持将 Cookie 字段可以拆分为单独的 HEADER 字段,每个标头字段包含一个或多个 cookie 对。
- 是否将 Cookie 拆分是请求方逻辑,部分旧客户端虽然支持 HTTP/2,但是不一定会进行此优化。
最后也发现 Charles 的小 Bug 🪳:测试反馈接口问题,服务端可能让提供 Charles 请求复制成 Curl Request。但把拆分 Cookie 请求,复制成 curl, Cookie 会合并到一起🌝。
参考: