ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

Fluent Bit 依赖的 nghttp2:nghttp2_submit_extension 扩展帧提交 API 深度解析 Fluent Bit 依赖的 nghttp2nghttp2_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 framesnghttp2_submit_extension正是 nghttp2 为应用层暴露的、用于提交这类自定义扩展帧的入口。读完本文你将掌握该函数的签名、参数语义、前置回调要求、内存生命周期约束、错误码含义并能结合源码实现lib/nghttp2_submit.c与单元测试tests/nghttp2_session_test.c完整复现一次扩展帧的提交—打包—发送全过程。函数签名与原型文档给出的原型需包含nghttp2/nghttp2.hint 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、CONTINUATIONflags帧标志位应用可任意指定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; }从源码结构看可以归纳出三点与文档一一对应的实现事实校验顺序先做type NGHTTP2_CONTINUATION即type 0x9的参数校验再做回调是否存在检查最后才是内存分配。这与文档中错误码列表的顺序无关但意味着如果同时犯了type 非法和回调未设置两个错误库会优先报告NGHTTP2_ERR_INVALID_ARGUMENT。回调的兼容检查源码中同时检查pack_extension_callback2和旧版已废弃的pack_extension_callback二者其一即可文档以新版nghttp2_pack_extension_callback2为准这是 v1.22 引入的基于nghttp2_ssize的 64 位安全版本见 nghttp2.h#L2360-L2390 中对旧版的 Deprecated 警告。提交 ≠ 发送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-L667NGHTTP2_SETTINGS 0x04、NGHTTP2_GOAWAY 0x07、NGHTTP2_CONTINUATION 0x09紧随其后的NGHTTP2_ALTSVC 0x0a正是第一个合法的扩展帧类型示例。前置条件pack_extension_callback2文档明确要求The application must setnghttp2_pack_extension_callback2usingnghttp2_session_callbacks_set_pack_extension_callback2()。该回调类型定义在 nghttp2.h#L2388-L2390typedef 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由库负责打包应用只需打包 payloadframe-ext.payload就是提交时传入的payload指针打包缓冲区buf容量至少 16KiBlen即容量成功时返回写入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_extensiontests/nghttp2_session_test.c#L6511-L6570是验证本文所有说法的最佳证据。其流程为定义一个极简的 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); }创建客户端会话设置callbacks.pack_extension_callback2 pack_extension_callback与send_callback2累积实际写入的字节以type2110xd3远大于 0x9、flags0x01、stream_id3、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);对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_GOAWAY0x07调用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 扩展帧type0xa, 连接级stream_id0 */ 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 fieldorigin 长度按 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_extensionpack_extension_callback2本文主题接收自定义扩展帧→unpack_extension_callbackon_extension_chunk_recv_callbacknghttp2_option_set_user_recv_extension_typeALTSVC 这类官方帧→ 可用内建 handler也可用上述通用扩展框架。实践清单与常见陷阱结合文档、实现和测试使用nghttp2_submit_extension时的检查清单type 取值严格大于 0x9提交 [0x0, 0x9] 区间任何值都会得到NGHTTP2_ERR_INVALID_ARGUMENT源码中即type NGHTTP2_CONTINUATION判断。回调必须先注册未注册pack_extension_callback2或旧版回调时返回NGHTTP2_ERR_INVALID_STATE建议统一使用nghttp2_session_callbacks_set_pack_extension_callback2的 64 位安全变体。提交是异步入队submit成功不代表帧已发出帧会在后续nghttp2_session_send()轮到时才经 pack 回调编码、经send_callback2写出。payload 生命周期默认持有到on_frame_send/on_frame_not_send回调只有确认打包后不再读取时才可在 pack 回调内释放。pack 回调返回值的边界返回 len或任意未定义值都会被归一为NGHTTP2_ERR_CALLBACK_FAILURE缓冲区容量至少 16KiB超长 payload 需评估是否一次装得下。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),仅供参考
返回列表