Fluent Bit 内嵌 nghttp2:nghttp2_submit_headers() API 深入解析
【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit
nghttp2_submit_headers()是 nghttp2 库中提交 HEADERS 帧的核心低层 API。Fluent Bit 在输出端启用 HTTP/2(如net.http_version = 2的 HTTP 客户端)时,其内嵌的 nghttp2 1.65.0(位于 lib/nghttp2-1.65.0/)正是通过这一系列 submit 接口把请求头组装成 HTTP/2 帧序列。读完本文,你将掌握该函数的完整签名语义、参数与标志位、伪头字段约束、内存拷贝策略、返回值与错误码体系,以及 Fluent Bit HTTP/2 客户端如何在上层封装调用链。
函数原型
声明位于 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h,实现在 lib/nghttp2-1.65.0/lib/nghttp2_submit.c:
#include <nghttp2/nghttp2.h> int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_priority_spec *pri_spec, const nghttp2_nv *nva, size_t nvlen, void *stream_user_data);它的作用是向会话提交一个 HEADERS 帧。各参数含义如下(对应文档 lib/nghttp2-1.65.0/doc/nghttp2_submit_headers.rst):
| 参数 | 说明 |
|---|---|
session | 已初始化的 nghttp2 会话(客户端或服务端会话) |
flags | 帧标志位,目前仅支持NGHTTP2_FLAG_END_STREAM(值0x01,见 nghttp2.h) |
stream_id | 目标流 ID;传-1表示由库分配新的客户端流(请求 HEADERS,打开新流) |
pri_spec | 优先级规格,当前实现中被忽略(源码中有显式(void)pri_spec;) |
nva/nvlen | 头字段 name/value 数组及其元素个数,类型为nghttp2_nv |
stream_user_data | 与流关联的用户指针,仅当本帧把流从 idle/reserved 状态转为 open 时生效 |
参数语义详解
flags 与 CONTINUATION 的透明处理
应用层只需关心NGHTTP2_FLAG_END_STREAM(0x01):设置后该 HEADERS 帧携带 END_STREAM 标志,表示流在本帧结束。NGHTTP2_FLAG_END_HEADERS(0x04)对应用层不可见——库内部处理 CONTINUATION 帧拆分时,会在 PUSH_PROMISE 或最后一组 CONTINUATION 帧上正确设置 END_HEADERS。
源码印证了这一点:nghttp2_submit.c 中入口处先做flags &= NGHTTP2_FLAG_END_STREAM把非法标志位全部剔除,随后在submit_headers_shared()里强制 OR 上NGHTTP2_FLAG_END_HEADERS(nghttp2_submit.c)。
stream_id 的三种取值
-1:仅客户端会话可用。库把本帧归类为请求(NGHTTP2_HCAT_REQUEST),从session->next_stream_id取 ID 并使后续 ID 加 2(保持客户端流奇数编号),成功时返回新分配的流 ID(见 nghttp2_submit.c)。若next_stream_id已超出INT32_MAX,则无可用 ID。0:非法,返回NGHTTP2_ERR_INVALID_ARGUMENT(HTTP/2 规范中 0 是连接级流)。- 正奇数(具体值):对已存在的流提交 HEADERS(响应头、trailer 等),归类为
NGHTTP2_HCAT_HEADERS,成功时返回 0。
注意文档中的 warning:stream_id = -1成功返回的流 ID 只是预分配,流尚未打开;在nghttp2_before_frame_send_callback对该帧被调用之前,不得向该流 ID 提交其他帧。
nva:伪头字段由应用负责
nghttp2_nv是{flags, name, namelen, value, valuelen}五元组。文档明确要求:
- 必需的伪头字段(
:前缀,如:method、:scheme、:path、:status)必须由应用放入nva,且必须排在普通头字段之前; - 函数会拷贝
nva中所有 name/value,并自动将名称转为小写,元素顺序保持不变; - 若某个
nghttp2_nv设置了NGHTTP2_NV_FLAG_NO_COPY_NAME(0x02)/NGHTTP2_NV_FLAG_NO_COPY_VALUE(0x04)(见 nghttp2.h),对应部分不拷贝;其中NO_COPY_NAME要求应用自行保证名称是小写。对于不拷贝的部分,应用必须保证指针引用在nghttp2_on_frame_send_callback或nghttp2_on_frame_not_send_callback被调用之前持续有效。
低层定位:何时用它,何时用替代品
文档明确把nghttp2_submit_headers()定位为低层函数——它允许应用直接指定 flags。对常规 HTTP 请求应优先用nghttp2_submit_request2(),对 HTTP 响应优先用nghttp2_submit_response2()(两者内部同样最终提交 HEADERS 帧)。此外nghttp2_submit_trailer()等价于flags = NGHTTP2_FLAG_END_STREAM、pri_spec与stream_user_data为 NULL 的nghttp2_submit_headers(),见 nghttp2_submit.c 的实现——它正是直接调用submit_headers_shared_nva()复用同一条路径。
实现剖析:从 submit 到出站队列
完整调用链为nghttp2_submit_headers()→submit_headers_shared_nva()→submit_headers_shared(),源码在 lib/nghttp2-1.65.0/lib/nghttp2_submit.c:
- 参数校验(L149-L155):
stream_id == -1且session->server为真 →NGHTTP2_ERR_PROTO;stream_id <= 0且非 -1 →NGHTTP2_ERR_INVALID_ARGUMENT。 - 深拷贝头数组:
submit_headers_shared_nva()(L112-L130)通过nghttp2_nv_array_copy()复制nva,此后所有权交给nghttp2_frame_headers_init()(源码注释:nghttp2_frame_headers_init() takes ownership of nva_copy,L104),失败路径负责释放。 - 分配出站项并初始化 HEADERS 帧:
nghttp2_mem_malloc()分配nghttp2_outbound_item(内存不足返回NGHTTP2_ERR_NOMEM),nghttp2_outbound_item_init()初始化,stream_user_data存入item->aux_data.headers.stream_user_data。 - 入队:
nghttp2_session_add_item()将项挂入会话的 outbound 队列;此后帧何时真正写包、是否被拆为 CONTINUATION,都由nghttp2_session_send()驱动。 - 返回值:请求类(新流)返回新流 ID,其余情况返回 0。
需要强调:submit 只是入队,并不发送字节。应用随后调用nghttp2_session_send()才会触发nghttp2_before_frame_send_callback并真正序列化帧。
返回值与错误码
成功且stream_id == -1时返回新分配的流 ID;否则成功返回 0。失败返回以下负值错误码(常量取值见 nghttp2.h):
| 错误码 | 常量值 | 触发条件 |
|---|---|---|
NGHTTP2_ERR_NOMEM | -901 | 内存分配失败 |
NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE | -509 | next_stream_id达到INT32_MAX,无可用流 ID |
NGHTTP2_ERR_INVALID_ARGUMENT | -501 | stream_id为 0 |
NGHTTP2_ERR_DATA_EXIST | -529 | 该流的 DATA/HEADERS 已提交且尚未处理完(例如流处于 reserved 状态时重复提交) |
NGHTTP2_ERR_PROTO | -505 | stream_id为 -1 但session是服务端会话(服务端不能"打开"新流) |
判断错误码是否致命时,可配合nghttp2_is_fatal()辅助函数(见 lib/nghttp2-1.65.0/doc/nghttp2_is_fatal.rst)。
Fluent Bit 中的实际使用模式
Fluent Bit 的 HTTP/2 客户端位于 src/flb_http_client_http2.c。从源码结构看,它并没有直接调用裸的nghttp2_submit_headers(),而是按上文建议采用了更高层的封装:
- 在
flb_http2_request_submit()中组装nghttp2_nv头数组(用户头 +Host+Content-length),并绑定data_provider.read_callback(即http2_data_source_read_callback)提供请求体,然后调用nghttp2_submit_request(session->inner_session, NULL, headers, header_count, &data_provider, stream)(src/flb_http_client_http2.c)。最后一个参数stream_user_data正是stream指针——对应本文参数表中"仅当流从 idle 变为 open 时生效"的用户指针语义; - 提交后立刻调用
nghttp2_session_send()驱动帧发送,并把流状态机推进到HTTP_STREAM_STATUS_PROCESSING→HTTP_STREAM_STATUS_RECEIVING_HEADERS; - 会话建立阶段还通过
nghttp2_submit_settings()下发 SETTINGS 帧(src/flb_http_client_http2.c)。
这正体现了文档给出的选型指引:常规 HTTP 请求走nghttp2_submit_request2()系列,nghttp2_submit_headers()则留给需要直接操控 flags 的场景(如服务器推送 trailer、自定义扩展头块等)。理解这条低层 API 的完整语义,有助于阅读 Fluent Bit HTTP/2 路径的调试与二次开发。
小结
nghttp2_submit_headers()提交的是逻辑 HEADERS 帧,入队于 outbound 队列,END_HEADERS/CONTINUATION 拆分与 END_STREAM 之外的标志位全部由库接管;stream_id = -1仅客户端可用且返回新流 ID,但该流在nghttp2_before_frame_send_callback触发前不可复用;- 伪头字段由应用负责按序提供;名称默认被拷贝并小写化,
NO_COPY_*标志下生命周期责任转移到应用; - 五个错误码(
-901/-509/-501/-529/-505)分别对应内存、流 ID 耗尽、非法 0、重复提交与服务端误用,可直接用于错误处理分支; - Fluent Bit 在实际请求路径上选择
nghttp2_submit_request()+stream_user_data回调的组合,与该 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),仅供参考