
fluent-bit 内置 nghttp2 的 HPACK 头字段压缩nghttp2_hd_deflate_hd 函数深入解析【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读HTTP/2 协议通过 HPACKRFC 7541对头部字段进行压缩而本仓库内置的 nghttp2 库lib/nghttp2-1.65.0/是这一压缩机制的权威 C 实现。本文以 nghttp2 官方 API 文档中的 nghttp2_hd_deflate_hd 参考页为核心骨架完整讲解该函数的签名、语义、缓冲区管理、错误处理与生命周期配套函数并结合仓库内的 头字段压缩实现 和 完整可运行示例帮助读者掌握在 fluent-bit 相关 HTTP/2 组件如 out_http、out_opentelemetry 等基于 nghttp2 的插件中正确、安全地进行头部字段压缩。函数总览Synopsis 与签名nghttp2_hd_deflate_hd用于将一组 HTTP/2 头字段name/value 对压缩Deflate为符合 HPACK 规范的二进制头块Header Block。其官方声明位于 nghttp2.h#include nghttp2/nghttp2.h ssize_t nghttp2_hd_deflate_hd(nghttp2_hd_deflater *deflater, uint8_t *buf, size_t buflen, const nghttp2_nv *nva, size_t nvlen);参数含义如下参数说明deflater由nghttp2_hd_deflate_new()初始化得到的nghttp2_hd_deflater对象持有 HPACK 编码器状态含动态表buf输出缓冲区用于存放压缩后的头块字节序列buflenbuf的长度字节nva指向nghttp2_nv数组的指针每一项描述一个头字段的 name/valuenvlennva中头字段的个数重要提示来自官方文档该函数已被标记为Deprecated废弃官方建议改用nghttp2_hd_deflate_hd2()。两者语义完全一致唯一区别是返回值类型旧接口返回ssize_t新接口返回nghttp2_ssize。在库内实现中旧接口只是对新接口的一层薄封装见下文源码分析。核心语义把 nva 压缩进 buf根据 nghttp2_hd_deflate_hd.rst 的定义该函数完成的工作是将nva中全部nvlen个 name/value 头字段作为输入依据 HPACK 编码规则静态表、动态表、Huffman 编码、增量索引等将它们压缩把压缩结果写入长度为buflen的buf成功时返回实际写入buf的字节数失败时返回负的错误码。值得注意的两个行为约定失败后进入“不可恢复”状态一旦本次调用失败之后对该deflater的每一次调用都会固定返回NGHTTP2_ERR_HEADER_COMP。这意味着压缩失败是致命的不能在同一 deflater 上重试只能重新创建 deflater 对象。调用返回后nva可安全释放函数是同步压缩返回时头字段数据已全部被消费调用方可以立即 free 这些 name/value 存储。从 HPACK 原理看这一 API 的压缩收益来自状态保持连续多次调用同一个deflater时动态表会不断累积后续头块通过索引引用先前出现过的字段实现差分编码。仓库的 HPACK 教程 明确指出Subsequent calls tonghttp2_hd_deflate_hd2()will use the current encoder state and perform differential encoding, which yields HPACKs fundamental compression gain.缓冲区规划先用 nghttp2_hd_deflate_bound 求上界文档反复强调一个关键问题如何确保buf足够大官方给出的答案是使用配套函数 nghttp2_hd_deflate_boundsize_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen);它返回对给定nva长度nvlen压缩后大小的一个上界upper bound。调用方应当把与nghttp2_hd_deflate_hd完全相同的三个参数deflater、nva、nvlen传给nghttp2_hd_deflate_bound据此分配缓冲区从而保证压缩必然成功除内存不足等系统级错误外。从 实现源码 可以精确看到这个上界是如何计算的size_t nghttp2_hd_deflate_bound(nghttp2_hd_deflater *deflater, const nghttp2_nv *nva, size_t nvlen) { size_t n 0; size_t i; (void)deflater; /* Possible Maximum Header Table Size Change. Encoding (1u 31) - 1 using 4 bit prefix requires 6 bytes. We may emit this at most twice. */ n 12; /* Use Literal Header Field without indexing - New Name, since it is most space consuming format. Also we choose the less one between non-huffman and huffman, so using literal byte count is sufficient for upper bound. */ n 6 * 2 * nvlen; for (i 0; i nvlen; i) { n nva[i].namelen nva[i].valuelen; } return n; }这个上界由三部分构成体现了“最坏情况”的保守估算策略动态表大小变更指令预留 12 字节HPACK 允许在头块中插入 Table Size Update 指令4 位前缀整数编码(1u31)-1最多需要 6 字节该指令最多可能出现两次故预留6 × 2 12字节每个头字段的表示开销预留6 × 2 12字节最耗空间的编码形态是 “Literal Header Field without Indexing – New Name”7 位前缀整数单个字段最多需要 6 字节的长度前缀且 Huffman 编码只可能更省因此每个字段按 6 字节长度前缀、且 name/value 都可能出现该前缀来估算字段原始字节数namelen valuelen累加——因为“不索引的新名字面量字段”至少要原样输出 name 和 value 的字节。也就是说nghttp2_hd_deflate_bound给出的上界始终大于等于实际压缩输出但通常远大于实际值尤其是当大量字段命中动态表索引时。示例中压缩比通常能达到 0.20.5 甚至更低。错误码详解与失败语义成功时返回写入buf的字节数失败时返回以下负错误码之一见 nghttp2.h 中的文档注释错误码含义处理建议NGHTTP2_ERR_NOMEM内存不足Out of memory属系统级失败释放资源后考虑降级或退出NGHTTP2_ERR_HEADER_COMP头字段压缩过程失败致命错误该 deflater 已进入坏状态后续调用恒失败必须重建 deflaterNGHTTP2_ERR_INSUFF_BUFSIZE提供的buflen太小装不下输出用nghttp2_hd_deflate_bound重新计算上界并分配更大的缓冲区需要特别强调NGHTTP2_ERR_INSUFF_BUFSIZE与NGHTTP2_ERR_HEADER_COMP的级联关系当缓冲区不足时内部压缩在写入中途失败导致 deflater 状态被标记为bad。因此一次缓冲区不足不仅让本次调用失败还会毒化整个 deflater 对象——这也是文档与教程反复强调务必先用nghttp2_hd_deflate_bound()求出上界再分配缓冲区的根本原因。从源码看实现调用链与缓冲区包装nghttp2_hd_deflate_hd的真实实现位于 lib/nghttp2_hd.c其内部调用链如下nghttp2_hd_deflate_hd() -- 废弃 API 薄封装 └─ nghttp2_hd_deflate_hd2() -- 推荐 API ├─ nghttp2_bufs_wrap_init() -- 把调用方的 buf/buflen 包装为内部 bufs ├─ nghttp2_hd_deflate_hd_bufs() -- 核心压缩逻辑 │ ├─ emit_table_size() -- 必要时输出 Table Size Update │ └─ deflate_nv() × nvlen -- 逐字段 HPACK 编码 └─ nghttp2_bufs_wrap_free()核心逻辑nghttp2_hd_deflate_hd_bufs()源码位置体现了两个关键设计坏状态检查先行函数一进来就检查deflater-ctx.bad若为真直接返回NGHTTP2_ERR_HEADER_COMP这就是“失败后所有后续调用恒失败”这一语义的实现位置任何中间失败都会置坏状态无论是emit_table_size还是逐字段的deflate_nv返回非零代码都会跳转到fail标签执行deflater-ctx.bad 1后返回错误——保证 deflater 状态的严格一致性。另外值得注意nghttp2_hd_deflate_hd2()会把内部NGHTTP2_ERR_BUFFER_ERROR底层 bufs 包装层写满返回的错误统一转换为对外暴露的NGHTTP2_ERR_INSUFF_BUFSIZE这正是文档所述错误码的由来。对于需要把输出写入多个不连续内存段iov的场景仓库还提供了nghttp2_hd_deflate_hd_vec2()变体实现思路与hd2完全一致只是改用nghttp2_vec数组包装输出空间。配套生命周期 API完整使用闭环nghttp2_hd_deflate_hd只是 HPACK 压缩流程的中间一环一个完整的压缩器生命周期需要以下配套函数协同1. 创建nghttp2_hd_deflate_newint nghttp2_hd_deflate_new(nghttp2_hd_deflater **deflater_ptr, size_t max_deflate_dynamic_table_size);按 nghttp2_hd_deflate_new 的定义该函数分配并初始化一个nghttp2_hd_deflater对象把指针写入*deflater_ptr。第二个参数max_deflate_dynamic_table_size是压缩器允许使用的动态表大小上限直接决定编码器的内存占用。HPACK 规范规定的动态表默认上限为 4096 字节拿不准时直接传 4096 即可官方教程建议。失败时*deflater_ptr保持原值不被触碰仅返回NGHTTP2_ERR_NOMEM。从源码看它内部会转调nghttp2_hd_deflate_new2()支持自定义内存分配器nghttp2_mem的变体。2. 压缩nghttp2_hd_deflate_hd本文主角推荐用hd23. 销毁nghttp2_hd_deflate_delvoid nghttp2_hd_deflate_del(nghttp2_hd_deflater *deflater);按 nghttp2_hd_deflate_del 的定义该函数释放 deflater 对象占用的所有资源包括动态表内存与内部缓冲区。4. 动态表控制nghttp2_hd_deflate_change_table_sizeint nghttp2_hd_deflate_change_table_size(nghttp2_hd_deflater *deflater, size_t settings_max_dynamic_table_size);用于根据对端 SETTINGS 帧中的SETTINGS_HEADER_TABLE_SIZE动态调整表大小。注意文档说明如果传入值大于创建时的max_deflate_dynamic_table_size最终生效值会被钳制cap到创建时的上限即min(settings_max_dynamic_table_size, max_deflate_dynamic_table_size)。5. 状态查询仓库还提供了nghttp2_hd_deflate_get_dynamic_table_size()、nghttp2_hd_deflate_get_max_dynamic_table_size()、nghttp2_hd_deflate_get_num_table_entries()、nghttp2_hd_deflate_get_table_entry()等查询接口用于调试与监控动态表状态。完整可运行示例deflate.c仓库在 examples/deflate.c 中提供了完整的演示程序它同时驱动 deflater 与 inflater展示了头字段压缩-解压的完整闭环。其核心流程/* 1. 创建 deflater 与 inflater */ rv nghttp2_hd_deflate_new(deflater, 4096); rv nghttp2_hd_inflate_new(inflater); /* 2. 第一组头字段类似 HTTP 请求 */ nghttp2_nv nva1[] { MAKE_NV(:scheme, https), MAKE_NV(:authority, example.org), MAKE_NV(:path, /), MAKE_NV(user-agent, libnghttp2), MAKE_NV(accept-encoding, gzip, deflate)}; /* 3. 第二组头字段与第一组高度重复用于展示差分编码 */ nghttp2_nv nva2[] { MAKE_NV(:scheme, https), MAKE_NV(:authority, example.org), MAKE_NV(:path, /stylesheet/style.css), MAKE_NV(user-agent, libnghttp2), MAKE_NV(accept-encoding, gzip, deflate), MAKE_NV(referer, https://example.org)}; /* 4. 先压缩第一组再基于相同 deflater 压缩第二组 */ deflate(deflater, inflater, nva1, sizeof(nva1) / sizeof(nva1[0])); deflate(deflater, inflater, nva2, sizeof(nva2) / sizeof(nva2[0])); /* 5. 销毁 */ nghttp2_hd_inflate_del(inflater); nghttp2_hd_deflate_del(deflater);其中MAKE_NV宏展示了nghttp2_nv结构的初始化要点——必须完整初始化name、namelen、value、valuelen、flags五个成员#define MAKE_NV(K, V) \ { \ (uint8_t *)K, (uint8_t *)V, sizeof(K) - 1, \ sizeof(V) - 1, NGHTTP2_NV_FLAG_NONE, \ }deflate()函数则完整展示了“先 bound、再分配、再压缩”的推荐姿势buflen nghttp2_hd_deflate_bound(deflater, nva, nvlen); buf malloc(buflen); rv nghttp2_hd_deflate_hd2(deflater, buf, buflen, nva, nvlen); if (rv 0) { fprintf(stderr, nghttp2_hd_deflate_hd2() failed with error: %s\n, nghttp2_strerror((int)rv)); ... } outlen (size_t)rv;该示例还会打印压缩前后字节数与压缩比ratio并调用nghttp2_hd_inflate_hd3()将压缩结果解压回读以验证正确性是理解整个 HPACK 编码器 API 的最佳起点。敏感字段与安全实践HPACK 压缩存在著名的 CRIME 类压缩侧信道攻击风险若攻击者能控制待压缩内容并观察输出大小变化就可能逐步恢复敏感数据。因此 nghttp2 提供NGHTTP2_NV_FLAG_NO_INDEX标志位见 HPACK 教程其语义是该头字段不插入动态表从而不参与后续差分编码从根源上杜绝了针对动态表的恢复攻击。官方明确建议For security sensitive header fields (such as cookies), set theNGHTTP2_NV_FLAG_NO_INDEXflag innghttp2_nv.flags. Setting this flag prevents recovery of sensitive header fields by compression based attacks.正确用法示例nghttp2_nv nv; nv.name (uint8_t *)cookie; nv.namelen strlen(cookie); nv.value (uint8_t *)sessionsecret; nv.valuelen strlen(sessionsecret); nv.flags NGHTTP2_NV_FLAG_NO_INDEX; /* 阻止敏感字段进入动态表 */在 fluent-bit 项目中的关联位置作为 HTTP/2 能力的基础依赖nghttp2 被仓库以lib/nghttp2-1.65.0/目录整体内置并通过 cmake/nghttp2.cmake 参与构建。在 fluent-bit 中凡是需要发起 HTTP/2 通信的插件例如 out_http、out_opentelemetry 等输出插件在启用 HTTP/2 时都会经由 nghttp2 的会话层lib/nghttp2-1.65.0/lib/nghttp2_session.c间接使用到本文介绍的 HPACK 压缩路径——nghttp2_hd_deflate_hd系列函数正是 HEADERS 帧头部编码的底层执行者。对库使用者而言实践要点可归纳为四条铁律缓冲区先 bound 后分配每次压缩前用nghttp2_hd_deflate_bound()求上界并据此分配buf避免NGHTTP2_ERR_INSUFF_BUFSIZE失败即废弃任何错误返回后该 deflater 已不可用必须重建nghttp2_hd_deflate_delnghttp2_hd_deflate_new敏感字段标记NGHTTP2_NV_FLAG_NO_INDEXcookie、token 等字段绝不进入动态表优先使用新 API新代码直接使用nghttp2_hd_deflate_hd2()及hd_vec2()nghttp2_hd_deflate_hd仅作为向后兼容的别名存在。参考文档与源码索引API 参考nghttp2_hd_deflate_hd本文主体、nghttp2_hd_deflate_hd2、nghttp2_hd_deflate_bound、nghttp2_hd_deflate_new、nghttp2_hd_deflate_del教程HPACK API Tutorial示例examples/deflate.c实现lib/nghttp2_hd.cnghttp2_hd_deflate_hd_bufs/hd2/bound实现、lib/includes/nghttp2/nghttp2.h公开 API 声明与文档注释【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考