ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

nghttp2_select_alpn 详解:基于 nghttp2 库的 HTTP/2 服务端 ALPN 协议协商实现

nghttp2_select_alpn 详解:基于 nghttp2 库的 HTTP/2 服务端 ALPN 协议协商实现 nghttp2_select_alpn 详解基于 nghttp2 库的 HTTP/2 服务端 ALPN 协议协商实现【免费下载链接】fluent-bitFast and Lightweight Logs, Metrics and Traces processor for Linux, BSD, OSX and Windows项目地址: https://gitcode.com/GitHub_Trending/fl/fluent-bit导读nghttp2_select_alpn是 nghttp2 HTTP/2 C 库为服务端提供的 ALPNApplication-Layer Protocol Negotiation应用层协议协商辅助函数用于在 TLS 握手阶段从客户端提交的协议列表中自动挑选 HTTP/2h2或 HTTP/1.1http/1.1从而让一个服务端口同时优雅地支持新旧两种协议。本文以 lib/nghttp2-1.65.0/doc/nghttp2_select_alpn.rst 文档为骨架结合仓库内的头文件声明、核心实现、单元测试与官方示例完整讲解该函数的输入输出格式、三步选择算法、与 OpenSSL 回调的集成方式及常见边界情况帮助读者在自己基于 nghttp2 的服务端程序中正确接入 HTTP/2。函数概览与声明位置nghttp2_select_alpn的完整声明位于 nghttp2 公共头文件 lib/nghttp2-1.65.0/lib/includes/nghttp2/nghttp2.h 中其原型为NGHTTP2_EXTERN int nghttp2_select_alpn(const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen);原型文档nghttp2.h 中对应注释明确说明这是一个服务端 ALPN 辅助函数helper function for dealing with ALPN in server side它本身不参与网络 I/O也不解析 TLS 报文而是把从客户端协议列表中选出一个协议这一纯逻辑步骤封装成可复用函数供 TLS 层的 ALPN 选择回调调用。该函数的实现位于 lib/nghttp2-1.65.0/lib/nghttp2_alpn.c与其并存的还有一个被标记为Deprecated已弃用的旧版函数nghttp2_select_next_protocol。头文件中的警告注释nghttp2.h#L5704-L5706明确建议新代码改用nghttp2_select_alpn。两者选择算法完全一致主要区别在于旧版out参数类型为unsigned char **不带const新版更符合 OpenSSL 回调对out指向只读协议数据的使用方式。输入格式length-prefixed、非 NUL 结尾的协议列表函数的第三个参数in携带客户端peer按其偏好顺序排列的协议列表该列表采用length-prefixed长度前缀编码每个协议项由一个字节的长度 相应字节的协议标识符组成列表整体不以 NUL 结尾总字节数由inlen精确给出。原文档给出了h2与http/1.1并存时的内存布局示例in[0] 2 in[1..2] h2 in[3] 8 in[4..11] http/1.1 inlen 12逐字节解读位置内容含义in[0]2第一个协议标识符的长度为 2 字节in[1..2]h2第一个协议为 HTTP/2h2in[3]8第二个协议标识符的长度为 8 字节in[4..11]http/1.1第二个协议为 HTTP/1.1inlen12整个列表共 12 字节1218这种编码格式正是 RFC 7301ALPN 规定的ProtocolNameList在 TLS 扩展中的传输格式因此in/inlen可以直接复用 TLS 栈如 OpenSSLALPN 回调传入的客户端列表数据无需二次转换。仓库单元测试 lib/nghttp2-1.65.0/tests/nghttp2_alpn_test.c 中用字节数组构造了更复杂的列表来验证解析例如包含http/1.1、h2、spdy/3三个候选的列表nghttp2_alpn_test.c#L43-L45const unsigned char p[] {8, h, t, t, p, /, 1, ., 1, 2, h, 2, 6, s, p, d, y, /, 3};可以看到h2并未排在客户端列表的第一位这正好用于检验按内容匹配而非按位置匹配的选择逻辑。三步选择算法与返回值语义nghttp2_select_alpn的协议选择遵循严格的优先级顺序nghttp2_alpn.c#L59-L70第 1 步优先选择 HTTP/2。若客户端列表中包含当前库支持的 HTTP/2 协议标识符则选中h2函数返回1后续步骤不再执行。nghttp2 内部通过宏NGHTTP2_PROTO_ALPN \x2h2表示带 2 字节长度前缀的h2定义见 nghttp2.h#L109-L116对应的明文标识符为NGHTTP2_PROTO_VERSION_ID h2nghttp2.h#L92-L98。第 2 步退而求其次选择 HTTP/1.1。若列表中不含h2但包含http/1.1则选中http/1.1返回0。这是保证新老协议并存、平滑回退的关键无法协商出 HTTP/2 的连接仍可退化为 HTTP/1.1 继续服务而不是直接握手失败。第 3 步无交集non-overlap。若列表既不包含h2也不包含http/1.1则不做任何选择返回-1。此时*out与*outlen保持原样不被修改left untouched由调用方TLS 回调决定如何处置——通常返回SSL_TLSEXT_ERR_NOACK表示不确认 ALPN 扩展让连接按无 ALPN 的普通方式继续。选中协议时的输出当选中h2时h2的指针被写入*out其长度2被写入*outlen即NGHTTP2_PROTO_VERSION_ID_LEN。返回值速查表返回值含义*out/*outlen1选中 HTTP/2h2指向h2长度 20选中 HTTP/1.1http/1.1指向http/1.1长度 8-1无交集未选择保持原值不变底层实现线性扫描匹配从源码看实际匹配工作由内部静态函数select_alpn完成nghttp2_alpn.c#L29-L41static int select_alpn(const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, const char *key, unsigned int keylen) { unsigned int i; for (i 0; i keylen inlen; i (unsigned int)(in[i] 1)) { if (memcmp(in[i], key, keylen) 0) { *out (unsigned char *)in[i 1]; *outlen in[i]; return 0; } } return -1; }它利用 length-prefixed 结构按步长in[i] 1长度字节 标识符本身遍历整个列表用memcmp将每个条目与目标协议\x2h2或\x8http/1.1逐字节比较。匹配成功后*out指向长度字节之后的协议标识符起始位置*outlen取长度字节的值。整个实现不依赖任何动态分配是典型的嵌入式友好型纯函数设计。与 OpenSSL 回调的集成完整可运行示例原文档给出了一段标准集成代码定义一个签名符合 OpenSSL 要求的 ALPN 选择回调在回调中调用nghttp2_select_alpn并通过SSL_CTX_set_alpn_select_cb注册到 SSL 上下文中。以下代码完整保留并稍作注释static int alpn_select_proto_cb(SSL* ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { int rv; rv nghttp2_select_alpn(out, outlen, in, inlen); if (rv -1) { return SSL_TLSEXT_ERR_NOACK; } if (rv 1) { ((MyType*)arg)-http2_selected 1; } return SSL_TLSEXT_ERR_OK; } ... SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, my_obj);集成要点说明回调签名必须严格匹配 OpenSSL 的alpn_select_cb原型int (*)(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)。nghttp2_select_alpn的前四个参数与回调的后四个参数一一对应因此可以近乎直接透传。返回值-1无交集对应SSL_TLSEXT_ERR_NOACK表示不向客户端返回 ALPN 协议选择连接继续由应用层决定后续行为。arg参数可用于状态记录如示例中当rv 1协商出 HTTP/2时通过((MyType*)arg)-http2_selected 1记录协商结果便于握手完成后应用层据此走 HTTP/2 处理路径。SSL_TLSEXT_ERR_OK表示接受本次选择OpenSSL 会依据*out/*outlen把选中的协议写入 TLS 握手报文返回给客户端。官方示例中的真实用法仓库自带的官方示例 lib/nghttp2-1.65.0/examples/libevent-server.c 给出了与本仓库 doc 文档几乎一致的落地实现。其回调libevent-server.c#L110-L124简化了状态记录仅当rv ! 1时返回SSL_TLSEXT_ERR_NOACKstatic int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { int rv; (void)ssl; (void)arg; rv nghttp2_select_alpn(out, outlen, in, inlen); if (rv ! 1) { return SSL_TLSEXT_ERR_NOACK; } return SSL_TLSEXT_ERR_OK; }随后在创建 SSL 上下文的create_ssl_ctx中注册回调libevent-server.c#L163SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL);注意示例在此处传入的arg为NULL同时整体策略是只接受 h2否则不确认——这是 HTTP/2 专用服务器的常见做法而 doc 文档中的示例则允许退化到http/1.1返回0时同样返回SSL_TLSEXT_ERR_OK适合需要同时服务 HTTP/1.1 与 HTTP/2 的混合场景。两种策略各有适用场景读者可依据自身需求选择。测试验证三种分支均有覆盖nghttp2 的单元测试 lib/nghttp2-1.65.0/tests/nghttp2_alpn_test.c 完整覆盖了该函数的三个返回分支可作为理解行为的权威参照1. 命中h2返回 1构造包含http/1.1、h2、spdy/3的列表断言返回值1且输出长度等于NGHTTP2_PROTO_VERSION_ID_LEN2、输出内容等于NGHTTP2_PROTO_VERSION_IDh2nghttp2_alpn_test.c#L43-L59。注意h2并不在列表首位验证了匹配与顺序无关仅关注是否包含。2. 未命中h2、命中http/1.1返回 0构造仅含spdy/4、spdy/2.1、http/1.1的列表断言返回值0输出长度为 8、内容为http/1.1nghttp2_alpn_test.c#L62-L81。3. 完全无交集返回 -1构造仅含spdy/4、spdy/2.1、http/1.0的列表注意是http/1.0而非http/1.1断言返回值-1且out保持NULL、outlen保持0——印证了文档中*out与*outlen不被修改的约定nghttp2_alpn_test.c#L83-L102。测试还同时验证了已弃用的nghttp2_select_next_protocol具有相同行为说明两者仅是接口形式差异选择逻辑完全等价。关键边界与注意事项结合文档、源码与测试使用nghttp2_select_alpn时需注意以下要点in必须严格符合 length-prefixed 格式且inlen必须精确等于列表总字节数。实现按in[i] 1步进遍历若格式损坏如长度字节超过剩余空间遍历会提前安全退出并最终返回-1但调用方仍应确保数据来自可信的 TLS 栈解析结果。匹配是包含性而非顺序性只要列表中任意位置出现h2即返回 1与客户端偏好排序无关只有完全不含h2时才考虑http/1.1。h2c明文 HTTP/2不在本函数处理范围内本函数仅处理 TLS 场景下的 ALPNh2c需要通过 HTTP/1.1 Upgrade 头或其他机制协商。*out指向的是in缓冲区内部函数不复制协议数据输出指针是输入列表的子区间。因此调用方不得在握手完成前释放或修改in所指缓冲区。返回值语义与 OpenSSL 回调返回值不要混淆nghttp2_select_alpn返回的是选择了什么1/0/-1而回调返回的是SSL_TLSEXT_ERR_*常量告诉 TLS 栈如何处置本次选择二者需要显式映射。旧函数已弃用新代码应直接使用nghttp2_select_alpn避免使用nghttp2_select_next_protocol。总结nghttp2_select_alpn用约 40 行源码实现了一个标准、无副作用、可嵌入任意 TLS 后端的 ALPN 选择器输入为 RFC 7301 格式的客户端协议列表输出为优先h2、其次http/1.1、否则不选的三态结果。它在 nghttp2 生态中的角色是连接 TLS 层与 HTTP/2 层的桥梁——通过SSL_CTX_set_alpn_select_cb注册后即可让单个服务端口在 TLS 握手中自动完成 HTTP/2 与 HTTP/1.1 的协商与回退。对于基于 nghttp2 构建服务端包括本仓库中 Fluent Bit 的 HTTP/2 相关能力的开发者而言理解该函数的选择优先级、输出语义与回调集成方式是正确启用 HTTP/2 的第一步。【免费下载链接】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),仅供参考
返回列表