ARTICLE DETAIL

资讯详情

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

OpenSSL QUIC + nghttp3 实现 HTTP/3 客户端/服务端:demos/http3 实战解析

OpenSSL QUIC + nghttp3 实现 HTTP/3 客户端/服务端:demos/http3 实战解析 OpenSSL QUIC nghttp3 实现 HTTP/3 客户端/服务端demos/http3 实战解析【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl导读本文围绕 OpenSSL 仓库中的 demos/http3 示例展开讲解如何将 OpenSSL 的 QUIC 实现与第三方 HTTP/3 协议栈 nghttp3 结合构建可运行的 HTTP/3 客户端与最小服务端。通过阅读本文你将掌握 nghttp3 与 OpenSSL QUIC API 的绑定思路、QUIC 单向流/双向流在 HTTP/3 中的角色、SSL_poll/SSL_read_ex/SSL_write_ex2等核心 API 的实际用法并能动手编译运行一个真正发出 HTTP/3 请求的示例程序。该示例由两部分组成这一结构也是本文的主线一个适配层demos/http3/ossl-nghttp3.c 与配套头文件 demos/http3/ossl-nghttp3.h把 nghttp3 绑定到 OpenSSL 的 QUIC 实现之上一个简单应用demos/http3/ossl-nghttp3-demo.c通过该适配层发起 HTTP/3 请求。前置条件与依赖在动手之前需要准备以下内容已构建的 OpenSSL 库示例通过-L../.. -lcrypto -lssl链接本仓库构建出的libcrypto与libssl运行时通过LD_LIBRARY_PATH../..指定共享库路径见 demos/http3/Makefile。nghttp3 库与头文件README 明确要求安装 nghttp3 及其头文件Ubuntu 上可直接安装软件包libnghttp3-dev。nghttp3 是 ngtcp2 项目组维护的 HTTP/3 协议库负责 QPACK 头部压缩、HTTP/3 帧解析与流状态管理等纯协议层工作它本身不关心数据如何在网络上传输因此需要与 OpenSSL QUIC 的传输层能力对接——这正是本示例适配层存在的意义。支持 QUIC 的 OpenSSL 构建示例调用OSSL_QUIC_client_method()、OSSL_QUIC_server_method()见 demos/http3/ossl-nghttp3-demo.c 与 demos/http3/ossl-nghttp3-demo-server.c因此需要启用 QUIC 特性的 OpenSSL 版本。如果你希望全自动获取并构建 nghttp3仓库提供了辅助脚本 demos/http3/gen_nghttp3.pl它会git clonenghttp3、初始化lib/sfparse与tests/munit子模块并以-DENABLE_LIB_ONLY1通过 CMake 构建库再把libnghttp*拷贝到demos/http3目录下。编译示例demos/http3目录下的 Makefile 面向 *nix 风格系统目标有两个CFLAGS -I../../include -g -Wall -Wsign-compare LDFLAGS -L../.. LDLIBS -lcrypto -lssl -lnghttp3 all: ossl-nghttp3-demo ossl-nghttp3-demo-server在demos/http3目录下直接执行$ make即可同时产出两个可执行文件客户端ossl-nghttp3-demo和服务端ossl-nghttp3-demo-server。Makefile 中也保留了更严格的 OpenSSL 编码风格 CFLAGS 示例-stdc90 -pedantic -pedantic-errors默认被注释掉仅作参考。注意如果链接的是共享库默认情况运行时必须保证libcrypto和libssl在库搜索路径上。仓库根目录构建完成后库文件位于仓库根目录下因此运行命令统一带有LD_LIBRARY_PATH../..。运行客户端发起你的第一个 HTTP/3 请求构建成功后用主机名:端口作为唯一参数运行客户端$ make $ LD_LIBRARY_PATH../.. ./ossl-nghttp3-demo www.google.com:443程序的行为如下以文本形式把 HTTP 响应头输出到 stderr每行名称: 值头部结束后空一行见 demos/http3/ossl-nghttp3-demo.c 中的on_recv_header/on_end_headers回调随后把响应体原样写到 stdouton_recv_data回调见同文件 L68-L99。关于 CA 证书取决于系统配置可能需要设置SSL_CERT_FILE或SSL_CERT_DIR环境变量指向存放受信任根 CA 证书的位置$ SSL_CERT_FILE/path/to/ca-bundle.crt LD_LIBRARY_PATH../.. ./ossl-nghttp3-demo www.example.com:443客户端代码中的验证逻辑demos/http3/ossl-nghttp3-demo.c值得注意先调用SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL)关闭证书校验紧接着调用SSL_CTX_set_default_verify_paths(ctx)加载系统默认 CA 路径。也就是说只要设置了SSL_CERT_FILE/SSL_CERT_DIR系统 CA 会被加载但演示程序并不强制校验证书。扩展用法请求文件列表与下载目录客户端实际支持更丰富的参数形式用法提示见 demos/http3/ossl-nghttp3-demo.cusage: ./ossl-nghttp3-demo host:port [requestfile.txt download_dir]当提供第三个和第四个参数时程序会把第二个参数视为请求文件——每行一个路径把第三个参数视为下载目录响应体将按请求路径写入该目录下的文件而非输出到 stdout$ cat requests.txt / /index.html /favicon.ico $ LD_LIBRARY_PATH../.. ./ossl-nghttp3-demo www.example.com:443 requests.txt ./download适配层 APInghttp3 与 OpenSSL QUIC 的桥梁头文件 demos/http3/ossl-nghttp3.h 定义了适配层对外暴露的完整 API。理解这些接口是读懂示例的关键函数作用OSSL_DEMO_H3_CONN_new_for_conn(SSL *qconn, ...)基于一个已创建的 QUIC 连接 SSL 对象QCSO创建 HTTP/3 连接。BIO 必须能通过BIO_get_ssl取到 SSL 对象若 QUIC 连接尚未完成握手会自动设置 HTTP/3 ALPNh3若已连接则要求 ALPN 已配置且尚未创建任何流OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const BIO_ADDRINFO *addr, ...)等价于new_for_conn但内部自动完成 UDP socket BIO 创建与 QUIC 连接 SSL 对象创建地址形如www.example.com:443要求ctx使用 QUIC 客户端SSL_METHODOSSL_DEMO_H3_CONN_connect等价于SSL_connect(OSSL_DEMO_H3_CONN_get0_connection(conn))OSSL_DEMO_H3_CONN_free释放连接及底层 QUIC SSL 对象和所有流OSSL_DEMO_H3_CONN_get_user_data/OSSL_DEMO_H3_STREAM_get_user_data取回应用自定义数据OSSL_DEMO_H3_CONN_get0_connection取回底层 QUIC 连接 SSL 对象OSSL_DEMO_H3_CONN_handle_events处理连接上的所有待处理事件出错返回 0OSSL_DEMO_H3_CONN_submit_request在连接上提交一个新的 HTTP/3 请求行为与nghttp3_conn_submit_request()类似关于回调与用户数据有两个容易踩坑的约定头文件中做了明确说明new_for_conn的user_data是应用提供的不透明值可通过OSSL_DEMO_H3_CONN_get_user_data取回但传给各回调函数的conn_user_data是指向OSSL_DEMO_H3_CONN的指针而不是这个 user_data。submit_request的stream_user_data同理回调里拿到的stream_user_data是指向OSSL_DEMO_H3_STREAM的指针要取回应用数据需调用OSSL_DEMO_H3_STREAM_get_user_data。源码级剖析一个 HTTP/3 连接是如何建立的OSSL_DEMO_H3_CONN_new_for_conndemos/http3/ossl-nghttp3.c是适配层的核心它完成了 QUIC 与 HTTP/3 之间最关键的几个绑定动作1. 配置 HTTP/3 ALPNstatic const unsigned char alpn[] { 2, h, 3 }; ... if (SSL_set_alpn_protos(conn-qconn, alpn, sizeof(alpn))) {{ 2, h, 3 }是 ALPN 协议列表的标准编码长度字节2加上字符串h3。调用成功返回 0所以代码里以非零作为失败分支。2. 关闭默认流模式显式管理流if (!SSL_set_default_stream_mode(conn-qconn, SSL_DEFAULT_STREAM_MODE_NONE)) {默认流模式被置为SSL_DEFAULT_STREAM_MODE_NONE所有流都由应用显式创建。每个 QUIC 流对应一个独立的 QUIC 流 SSL 对象QSSO这也自动启用了对入站流的接受能力对应SSL_set_incoming_stream_policy(3)文档所述机制。3. 创建 HTTP/3 必须的单向管理流HTTP/3 要求连接双方各维护几条单向管理流控制流control stream传递 SETTINGS 等连接级参数QPACK 编码流encoder stream与QPACK 解码流decoder stream承载 QPACK 动态表同步。代码通过h3_conn_create_streamL103-L127逐一创建。可以看到流类型的区分方式static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type) { ... uint64_t flags SSL_STREAM_FLAG_ADVANCE; if (type ! OSSL_DEMO_H3_STREAM_TYPE_REQ) flags | SSL_STREAM_FLAG_UNI; /* 除请求流外均为单向流 */ if ((s-s SSL_new_stream(conn-qconn, flags)) NULL) { ... } s-id SSL_get_stream_id(s-s);即请求流REQ是双向流控制流与两条 QPACK 流都是单向流SSL_STREAM_FLAG_UNI并统一通过SSL_STREAM_FLAG_ADVANCE让 QUIC 引擎自动推进流 ID。4. 告诉 nghttp3 各管理流的 IDec nghttp3_conn_bind_control_stream(conn-h3conn, s_ctl_send-id); ec nghttp3_conn_bind_qpack_streams(conn-h3conn, s_qpenc_send-id, s_qpdec_send-id);出站管理流需要显式告诉 nghttp3 其 QUIC 流 ID而入站方向则无需告知——每个入站单向流的首个字节就是流类型控制流为 0x00、QPACK 编码流为 0x02、QPACK 解码流为 0x03nghttp3 会自动识别服务端发来的管理流 ID。这是 HTTP/3 规范约定的机制示例注释中对此有明确说明。5. 回调转发机制适配层需要在一些关键事件上做自己的处理比如统计应用数据消费量、在流关闭时释放 QSSO因此采用保存原回调 替换为内部包装函数的方式conn-recv_data_cb intl_callbacks.recv_data; ... intl_callbacks.recv_data h3_conn_recv_data; intl_callbacks.stream_close h3_conn_stream_close; intl_callbacks.stop_sending h3_conn_stop_sending; intl_callbacks.reset_stream h3_conn_reset_stream; intl_callbacks.deferred_consume h3_conn_deferred_consume;以h3_conn_reset_stream为例L185-L206当 nghttp3 决定重置某个流时适配层先转发给应用回调再调用SSL_stream_reset()把重置动作落实到 QUIC 引擎static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id, ...) { ... if (stream-s ! NULL) { args.quic_error_code app_error_code; if (!SSL_stream_reset(stream-s, args, sizeof(args))) return 1; } return ret; }类似地h3_conn_stream_close在转发完应用回调后会从哈希表中移除并释放该流h3_conn_stop_sending则直接释放对应的 QSSO。而h3_conn_recv_data/h3_conn_deferred_consume的核心作用是累计 nghttp3 消费的应用数据字节数供后续读取泵pump计算消费总量。6. 连接级数据结构每个OSSL_DEMO_H3_CONNL67-L88内部维护qconnQUIC 连接 SSL 对象qconn_bio包装 QCSO 的 BIOh3connnghttp3 连接对象streams以LHASH_OF(OSSL_DEMO_H3_STREAM)实现的流 ID → OSSL_DEMO_H3_STREAM映射OpenSSL 自带的哈希表结构DEFINE_LHASH_OF_EX宏生成见 L37user_data应用不透明指针。事件循环handle_events 的三步流水线OSSL_DEMO_H3_CONN_handle_eventsdemos/http3/ossl-nghttp3.c是适配层的驱动核心注释把它的工作归纳为三件事处理新的入站流循环调用SSL_accept_stream(conn-qconn, SSL_ACCEPT_STREAM_NO_BLOCK)直到返回 NULL把服务端发来的控制流、QPACK 流等全部接入。把出站数据从 HTTP/3 引擎泵到 QUIC循环调用nghttp3_conn_writev_stream()获取 nghttp3 想要写出的向量注意该函数名以 nghttp3 视角命名实际是输出要写的数据再对每个向量调用SSL_write_ex2()写入对应的 QSSO若 nghttp3 指示 FIN则加上SSL_WRITE_FLAG_CONCLUDE让 OpenSSL 在数据写完后自动收尾。把入站数据从 QUIC 泵到 HTTP/3 引擎构造SSL_POLL_ITEM列表调用SSL_poll()找出可读流再对每个就绪流执行h3_conn_pump_stream。h3_conn_pump_streamL469-L576展示了流式数据搬运的标准模式先调用SSL_read_ex()从 QSSO 读取最多 4096 字节BUF_SIZE把读到的数据连同fin标记通过nghttp3_conn_read_stream()交给 HTTP/3 栈注意这个函数同样以 nghttp3 视角命名实际是把数据输入协议栈处理两个特殊终止情形SSL_ERROR_ZERO_RETURN表示流正常收尾此时以fin1调用nghttp3_conn_read_streamSSL_STREAM_STATE_RESET_REMOTE表示对端重置了流此时读取错误码并调用nghttp3_conn_close_stream消费量的计算采用非应用数据nghttp3_conn_read_stream返回值 ec 应用数据回调累计的consumed_app_data两部分求和的方式并用断言保证consumed buf_total - buf_cur防止把未消费的数据丢掉。出站方向还有一个值得学习的背压处理当SSL_write_ex2返回SSL_ERROR_WANT_WRITE时说明 QUIC 发送缓冲区已满此时调用nghttp3_conn_block_stream()通知 nghttp3 暂停生成该流数据一旦写入成功再调用nghttp3_conn_unblock_stream()恢复。写入后通过nghttp3_conn_add_write_offset()与nghttp3_conn_add_ack_offset()告知 nghttp3 数据已消费、可释放缓冲。提交 HTTP/3 请求OSSL_DEMO_H3_CONN_submit_requestL744-L778的实现非常简单创建一个新的双向请求流然后把 nghttp3 的名字-值对数组nghttp3_nv交给nghttp3_conn_submit_request。客户端 demo 中构造的请求头demos/http3/ossl-nghttp3-demo.c包括make_nv(nva[num_nv], :method, GET); make_nv(nva[num_nv], :scheme, https); make_nv(nva[num_nv], :authority, bare_hostname); make_nv(nva[num_nv], :path, path); make_nv(nva[num_nv], user-agent, OpenSSL-Demo/nghttp3);请求提交后主程序进入等待完成循环L146-L152不断调用OSSL_DEMO_H3_CONN_handle_events直到on_end_stream回调把全局done置 1表示一次 HTTP 事务结束。DNS 多地址与 QUIC 连接尝试客户端 demo 的main中有一段针对 UDP 特性的细致处理demos/http3/ossl-nghttp3-demo.c用BIO_parse_hostserv(addr, hostname, service, 0)拆分主机:端口用BIO_lookup_ex(..., SOCK_DGRAM, IPPROTO_UDP, bai)做 DNS 解析可能返回多个地址双栈环境下很常见逐个遍历BIO_ADDRINFO链对每个地址创建 socket BIO 并尝试建立 QUIC 连接。代码注释解释了原因与 TCP 不同UDP 上没有真正的握手BIO 子系统的connect(2)对 UDP 只是假定对端可达只有 QUIC 引擎在 TLS 握手阶段才能真正发现地址不可达因此应用需要在 DNS 返回的每个地址上依次发起连接尝试直到成功。配套服务端ossl-nghttp3-demo-server仓库还附带一个最小 HTTP/3 服务端 demos/http3/ossl-nghttp3-demo-server.c用法usage: ./ossl-nghttp3-demo-server port server.crt server.key它展示了服务端视角的完整流程核心要点包括使用SSL_new_listenerSSL_listen创建 QUIC 监听器L1053-L1061并显式SSL_set_blocking_mode(listener, 0)切换到非阻塞模式通过SSL_accept_connection接受新 QUIC 连接为每个连接新建nghttp3_conn_server_new服务端连接对象并设置SSL_INCOMING_STREAM_POLICY_ACCEPT入站流策略利用SSL_poll的事件位SSL_POLL_EVENT_IC/ISB/ISU/R/EC/ECD等驱动状态机区分新连接、新双向流请求流、新单向流、可读、连接开始终止/已终止等事件服务端同样需要创建自己的三条单向管理流quic_server_h3streamsL395-L460通过nghttp3_conn_bind_qpack_streams/nghttp3_conn_bind_control_stream绑定响应生成逻辑L1160-L1198 附近的nghttp3_conn_submit_response从FILEPREFIX环境变量指定的目录读取文件根据扩展名设置content-type.png→image/png、.ico→image/vnd.microsoft.icon、.htm→text/html否则application/octet-stream找不到文件时返回内置的 20 字节测试串数据回传使用nghttp3_conn_writev_stream分批取出写向量逐向量调用SSL_write_ex2最后一个向量若带 FIN 则加SSL_WRITE_FLAG_CONCLUDE再用nghttp3_conn_add_write_offset确认消费。服务端代码中还留下了若干针对互操作性的注释例如对 Chrome 在流 2 上返回 -607 的 workaroundL368-L388可作为研究 QUIC 实现兼容性问题的参考。从 QUIC 到 HTTP/3对照官方指南继续深入demos/http3是 OpenSSL 仓库中 QUIC 示例家族的一员。更基础的 QUIC 用法可参考指南示例目录 demos/guide其中 demos/guide/README.md 给出了quic-client-block、quic-client-non-block、quic-server-block等示例的编译与运行方式例如# 先运行 QUIC 服务端 LD_LIBRARY_PATH../.. ./quic-server-block 4443 ./chain.pem ./pkey.pem # 再运行 QUIC 客户端 SSL_CERT_FILEchain.pem LD_LIBRARY_PATH../.. ./quic-client-block localhost 4443指南文档位于 doc/man7从 doc/man7/ossl-guide-quic-introduction.pod 入门可依次阅读 doc/man7/ossl-guide-quic-client-block.pod、doc/man7/ossl-guide-quic-client-non-block.pod、doc/man7/ossl-guide-quic-multi-stream.pod 与 doc/man7/ossl-guide-quic-server-block.pod。demos/http3示例与这些基础示例的关系可以这样理解demos/guide解决的是如何用 OpenSSL QUIC API 收发字节流而demos/http3解决的是如何在 QUIC 字节流之上承载 HTTP/3 语义——前者是传输层后者通过 nghttp3 补齐了 QPACK、帧解析与流类型约定。小结本文完整梳理了 OpenSSL 仓库 demos/http3 示例的架构与实现双组件结构ossl-nghttp3.c/h提供通用绑定层ossl-nghttp3-demo.c与ossl-nghttp3-demo-server.c分别演示客户端与服务端用法核心绑定点ALPNh3协商、SSL_DEFAULT_STREAM_MODE_NONE下的显式流管理、控制流/QPACK 流与 nghttp3 的bind_*绑定、回调转发机制事件驱动以接受新流 → 泵出站数据 →SSL_poll 泵入站数据的三步流水线驱动连接辅以block_stream/unblock_stream背压控制实战运行make编译后LD_LIBRARY_PATH../.. ./ossl-nghttp3-demo host:port即可完成一次真实的 HTTP/3 请求。对于希望在自有应用中集成 HTTP/3 的开发者本示例提供了一个可直接参考的最小骨架传输层完全交给 OpenSSL QUIC协议层完全交给 nghttp3中间的适配逻辑流映射、数据泵、事件分发就是ossl-nghttp3.c这约 780 行代码所展示的内容。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表