Fluent Bit 依赖的 nghttp2:nghttp2_submit_extension 扩展帧提交 API 深度解析
2026/9/17 9:20:04 网站建设 项目流程

Fluent Bit 依赖的 nghttp2:nghttp2_submit_extension 扩展帧提交 API 深度解析

【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit

本文基于 Fluent Bit 仓库内置的 nghttp2 1.65.0 第三方库(lib/nghttp2-1.65.0),围绕其 API 参考文档 nghttp2_submit_extension.rst 展开。HTTP/2 标准帧之外,协议允许在 type > 0x9 的区间定义"非关键扩展帧"(non-critical extension frames),nghttp2_submit_extension正是 nghttp2 为应用层暴露的、用于提交这类自定义扩展帧的入口。读完本文,你将掌握该函数的签名、参数语义、前置回调要求、内存生命周期约束、错误码含义,并能结合源码实现(lib/nghttp2_submit.c)与单元测试(tests/nghttp2_session_test.c)完整复现一次扩展帧的"提交—打包—发送"全过程。

函数签名与原型

文档给出的原型(需包含<nghttp2/nghttp2.h>):

int nghttp2_submit_extension(nghttp2_session *session, uint8_t type, uint8_t flags, int32_t stream_id, void *payload);

各参数语义如下:

  • session:目标 HTTP/2 会话,可以是客户端或服务器端会话对象;
  • type:扩展帧类型,必须严格大于 0x9,即不能使用标准 HTTP/2 帧类型区间 [0x0, 0x9](DATA、HEADERS、PRIORITY、RST_STREAM、GOAWAY、WINDOW_UPDATE、PING、CONTINUATION);
  • flags:帧标志位,应用可任意指定;
  • stream_id:流 ID,应用可任意指定(扩展帧既可以是连接级的也可以是流级的,由扩展协议自身约定);
  • payload:不透明指针(opaque pointer)。它不会被库解析,也不会被库持有所有权("The library will not own passedpayloadpointer"),后续在打包回调中通过frame->ext.payload取回。

返回值为0(成功)或下列负错误码之一:

错误码触发条件
NGHTTP2_ERR_INVALID_STATE尚未通过nghttp2_session_callbacks_set_pack_extension_callback2()设置 pack extension 回调
NGHTTP2_ERR_INVALID_ARGUMENTtype落入标准帧类型区间 [0x0, 0x9]
NGHTTP2_ERR_NOMEM内存分配失败

源码实现:提交路径与校验顺序

nghttp2_submit_extension的实现在 lib/nghttp2_submit.c#L827-L863,完整逻辑为:

int nghttp2_submit_extension(nghttp2_session *session, uint8_t type, uint8_t flags, int32_t stream_id, void *payload) { int rv; nghttp2_outbound_item *item; nghttp2_frame *frame; nghttp2_mem *mem; mem = &session->mem; if (type <= NGHTTP2_CONTINUATION) { return NGHTTP2_ERR_INVALID_ARGUMENT; } if (!session->callbacks.pack_extension_callback2 && !session->callbacks.pack_extension_callback) { return NGHTTP2_ERR_INVALID_STATE; } item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); if (item == NULL) { return NGHTTP2_ERR_NOMEM; } nghttp2_outbound_item_init(item); frame = &item->frame; nghttp2_frame_extension_init(&frame->ext, type, flags, stream_id, payload); rv = nghttp2_session_add_item(session, item); if (rv != 0) { nghttp2_frame_extension_free(&frame->ext); nghttp2_mem_free(mem, item); return rv; } return 0; }

从源码结构看,可以归纳出三点与文档一一对应的实现事实:

  1. 校验顺序:先做type <= NGHTTP2_CONTINUATION(即type <= 0x9)的参数校验,再做回调是否存在检查,最后才是内存分配。这与文档中错误码列表的顺序无关,但意味着:如果同时犯了"type 非法"和"回调未设置"两个错误,库会优先报告NGHTTP2_ERR_INVALID_ARGUMENT
  2. 回调的兼容检查:源码中同时检查pack_extension_callback2和旧版(已废弃的)pack_extension_callback二者其一即可;文档以新版nghttp2_pack_extension_callback2为准,这是 v1.22+ 引入的基于nghttp2_ssize的 64 位安全版本(见 nghttp2.h#L2360-L2390 中对旧版的 Deprecated 警告)。
  3. 提交 ≠ 发送nghttp2_submit_extension只是把扩展帧包装为一个nghttp2_outbound_item,通过nghttp2_frame_extension_init初始化帧结构后插入会话的出站队列(nghttp2_session_add_item)。真正的字节流生成发生在后续nghttp2_session_send()/nghttp2_session_mem_send2()被调用、轮到该 item 处理时——此时库才会回调你的 pack 函数把payload编码进 wire format。

标准帧类型常量的定义位于 lib/includes/nghttp2/nghttp2.h#L640-L667:NGHTTP2_SETTINGS = 0x04NGHTTP2_GOAWAY = 0x07NGHTTP2_CONTINUATION = 0x09,紧随其后的NGHTTP2_ALTSVC = 0x0a正是第一个合法的扩展帧类型示例。

前置条件:pack_extension_callback2

文档明确要求:"The application must setnghttp2_pack_extension_callback2usingnghttp2_session_callbacks_set_pack_extension_callback2()"。该回调类型定义在 nghttp2.h#L2388-L2390:

typedef nghttp2_ssize (*nghttp2_pack_extension_callback2)( nghttp2_session *session, uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data);

回调契约(来自头文件注释):

  • 帧头(9 字节:length、type、flags、stream_id)由负责打包,应用只需打包 payload
  • frame->ext.payload就是提交时传入的payload指针;
  • 打包缓冲区buf容量至少 16KiB(len即容量);
  • 成功时返回写入buf的字节数;返回值严格大于len会被视为NGHTTP2_ERR_CALLBACK_FAILURE
  • 返回NGHTTP2_ERR_CANCEL可放弃该帧,随后触发nghttp2_on_frame_not_send_callback
  • 发生致命错误时返回NGHTTP2_ERR_CALLBACK_FAILUREnghttp2_session_send()/nghttp2_session_mem_send2()将立即以该错误返回。

Fluent Bit 本身作为 HTTP/2 客户端使用 nghttp2 时走的是标准帧路径,并不直接调用nghttp2_submit_extension;但在阅读 Fluent Bit 内置的 nghttp2 源码树(如 lib/flb_http_client_http2.c 所依赖的这套库)时,理解这条扩展帧通路有助于理解 nghttp2 出站队列(outbound queue)与回调驱动的整体架构。

payload 的内存生命周期

这是文档中最容易被忽视的约束,直接决定应用侧能否正确释放内存:

The application should retain the memory pointed bypayloaduntil the transmission of extension frame is done (which is indicated bynghttp2_on_frame_send_callback), or transmission fails (which is indicated bynghttp2_on_frame_not_send_callback). If application does not touch this memory region after packing it into a wire format, application can free it insidenghttp2_pack_extension_callback2.

可以归纳为两条策略:

  • 保守策略:持有payload直到nghttp2_on_frame_send_callback(发送成功)或nghttp2_on_frame_not_send_callback(发送失败)被触发后再释放。适用于 payload 在打包后还可能被读取的场景;
  • 激进策略:如果应用保证在 pack 回调内完成 wire format 编码之后不再触碰该内存,则可以直接在nghttp2_pack_extension_callback2内部释放,减少内存驻留时间。

注意库自身永远不释放该指针——所有权完全在应用侧。

单元测试印证:一次完整的扩展帧发送

单元测试test_nghttp2_submit_extension(tests/nghttp2_session_test.c#L6511-L6570)是验证本文所有说法的最佳证据。其流程为:

  1. 定义一个极简的 pack 回调(同文件 L738-L750):从frame->ext.payload(一个nghttp2_buf)中memcpy数据到buf并返回长度:
static nghttp2_ssize pack_extension_callback(nghttp2_session *session, uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data) { nghttp2_buf *p = frame->ext.payload; (void)session; (void)len; (void)user_data; memcpy(buf, p->pos, nghttp2_buf_len(p)); return (nghttp2_ssize)nghttp2_buf_len(p); }
  1. 创建客户端会话,设置callbacks.pack_extension_callback2 = pack_extension_callbacksend_callback2(累积实际写入的字节);

  2. type=211(0xd3,远大于 0x9)、flags=0x01stream_id=3、payload 指向 scratch buffer 提交:

rv = nghttp2_submit_extension(session, 211, 0x01, 3, &ud.scratchbuf); assert_int(0, ==, rv); rv = nghttp2_session_send(session); assert_int(0, ==, rv);
  1. send_callback2实际收到的字节流做逐字段断言,验证 wire format 正确性:
assert_size(NGHTTP2_FRAME_HDLEN + sizeof(data), ==, acc.length); len = nghttp2_get_uint32(acc.buf) >> 8; /* 帧头 24-bit length */ assert_size(sizeof(data), ==, len); assert_uint8(211, ==, acc.buf[3]); /* type 字段 */ assert_uint8(0x01, ==, acc.buf[4]); /* flags 字段 */ stream_id = (int32_t)nghttp2_get_uint32(acc.buf + 5); assert_int32(3, ==, stream_id); /* stream_id 字段(低 1 位清零后编码) */ assert_memory_equal(sizeof(data), data, &acc.buf[NGHTTP2_FRAME_HDLEN]); /* payload */

测试最后还验证了错误路径:在服务器端会话上以标准帧类型NGHTTP2_GOAWAY(0x07)调用nghttp2_submit_extension,断言返回NGHTTP2_ERR_INVALID_ARGUMENT——与文档错误码表完全一致:

rv = nghttp2_submit_extension(session, NGHTTP2_GOAWAY, NGHTTP2_FLAG_NONE, 0, NULL); assert_int(NGHTTP2_ERR_INVALID_ARGUMENT, ==, rv);

这组断言还揭示了一个实现细节:扩展帧的 9 字节帧头(NGHTTP2_FRAME_HDLEN)确实由库统一打包(length/type/flags/stream_id 均由库填充),应用回调只负责 payload 部分,与头文件注释中的分工描述吻合。

官方扩展框架中的定位:从 ALTSVC 示例看典型用法

nghttp2 官方开发者指南(doc/programmers-guide.rst "Implement user defined HTTP/2 non-critical extensions" 一节,自 v1.8.0 起引入扩展框架)以 RFC 7838 的 ALTSVC 帧(type =0xa)为例,展示了nghttp2_submit_extension的标准用法。完整最小示例如下:

typedef struct { const char *origin; const char *field; } alt_svc; /* pack 回调:只编码 payload,帧头交给库 */ nghttp2_ssize pack_extension_callback(nghttp2_session *session, uint8_t *buf, size_t len, const nghttp2_frame *frame, void *user_data) { const alt_svc *altsvc = (const alt_svc *)frame->ext.payload; size_t originlen = strlen(altsvc->origin); size_t fieldlen = strlen(altsvc->field); uint8_t *p; if (len < 2 + originlen + fieldlen || originlen > 0xffff) { return NGHTTP2_ERR_CANCEL; } p = buf; *p++ = originlen >> 8; /* origin 长度(16-bit big-endian) */ *p++ = originlen & 0xff; memcpy(p, altsvc->origin, originlen); p += originlen; memcpy(p, altsvc->field, fieldlen); p += fieldlen; return p - buf; } /* 注册回调(新版 API 用 ..._callback2 变体) */ nghttp2_session_callbacks_set_pack_extension_callback2( callbacks, pack_extension_callback); /* 提交 ALTSVC 扩展帧:type=0xa, 连接级(stream_id=0) */ static const alt_svc altsvc = {"example.com", "h2=\":8000\""}; nghttp2_submit_extension(session, 0xa, NGHTTP2_FLAG_NONE, 0, (void *)&altsvc);

要点回顾:

  • ALTSVC 的 wire format 为origin_length(2B) + origin + field,origin 长度按 16-bit 上限做了originlen > 0xffff的防御检查,不满足时返回NGHTTP2_ERR_CANCEL主动放弃发送(此时会触发nghttp2_on_frame_not_send_callback);
  • 提交参数(0xa, NGHTTP2_FLAG_NONE, 0, ...)表示:扩展帧类型 0xa、无标志位、连接级(stream_id = 0)——这三个参数正是nghttp2_submit_extension允许应用"任意指定"的灵活性所在。

指南还指出,nghttp2 对官方扩展帧(目前内置 ALTSVC)另有一套内建处理器:发送 ALTSVC 也可以走内建路径(见 lib/nghttp2_frame.c#L205 中 ALTSVC 帧的内建构造),接收侧则可通过nghttp2_option_set_builtin_recv_extension_type(option, NGHTTP2_ALTSVC)注册内建处理,或通过nghttp2_option_set_user_recv_extension_type(option, 0xa)加上nghttp2_unpack_extension_callback/nghttp2_on_extension_chunk_recv_callback两个回调注册用户级接收(接收侧回调定义见 nghttp2.h#L2314-L2317)。也就是说:

  • 发送自定义扩展帧nghttp2_submit_extension+pack_extension_callback2(本文主题);
  • 接收自定义扩展帧unpack_extension_callback+on_extension_chunk_recv_callback+nghttp2_option_set_user_recv_extension_type
  • ALTSVC 这类官方帧→ 可用内建 handler,也可用上述通用扩展框架。

实践清单与常见陷阱

结合文档、实现和测试,使用nghttp2_submit_extension时的检查清单:

  1. type 取值:严格大于 0x9;提交 [0x0, 0x9] 区间任何值都会得到NGHTTP2_ERR_INVALID_ARGUMENT(源码中即type <= NGHTTP2_CONTINUATION判断)。
  2. 回调必须先注册:未注册pack_extension_callback2(或旧版回调)时返回NGHTTP2_ERR_INVALID_STATE;建议统一使用nghttp2_session_callbacks_set_pack_extension_callback2的 64 位安全变体。
  3. 提交是异步入队submit成功不代表帧已发出;帧会在后续nghttp2_session_send()轮到时才经 pack 回调编码、经send_callback2写出。
  4. payload 生命周期:默认持有到on_frame_send/on_frame_not_send回调;只有确认打包后不再读取时才可在 pack 回调内释放。
  5. pack 回调返回值的边界:返回 >len或任意未定义值都会被归一为NGHTTP2_ERR_CALLBACK_FAILURE;缓冲区容量至少 16KiB,超长 payload 需评估是否一次装得下。
  6. stream_id 低 1 位:从测试断言nghttp2_get_uint32(acc.buf + 5)直接还原出 3 可以推断,库在编码帧头时对 stream_id 按 HTTP/2 规范处理(连接级为 0,流 ID 低 1 位语义由协议层保证);应用传入时应遵守 HTTP/2 流 ID 的奇偶约定。

小结

nghttp2_submit_extension是 nghttp2 非关键扩展帧框架的发送端 API:它把type/flags/stream_id/payload四元组封装为扩展帧并入站,校验"type > 0x9"与"pack 回调已注册"两个前提,随后由应用注册的nghttp2_pack_extension_callback2在真正发送时完成 payload 的 wire 编码。本文以 Fluent Bit 仓库内 lib/nghttp2-1.65.0/doc/nghttp2_submit_extension.rst 为纲,用 lib/nghttp2_submit.c 的实现、tests/nghttp2_session_test.c 的字节级断言和 doc/programmers-guide.rst 的 ALTSVC 示例逐条印证了文档中每一处关键描述——这套"文档 + 源码 + 测试"三方对照的阅读方式,同样适用于理解 nghttp2 乃至 Fluent Bit HTTP/2 客户端栈中的其他 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询