cpp-httplib 流式接收响应:用 ContentReceiver 逐块处理大文件、NDJSON 与日志流
发布时间:2026/10/1 16:57:53来源:尧图网络
后端网络【免费下载链接】cpp-httplibA C header-only HTTP/HTTPS server and client library项目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib点击查看免费下载本指南聚焦 cpp-httplib 客户端侧“按块接收响应体”的核心能力——ContentReceiver回调。它适用于下载大文件避免整包驻留内存、实时消费 NDJSON换行分隔 JSON与日志流等场景读完你即可掌握流式 GET 的标准写法、中途取消传输的机制、逐行解析 NDJSON 的缓冲模式并理解其底层实现接收缓冲、Content-Length/Chunked/无长度三种分帧路径。对应文档位于 docs-src/pages/en/cookbook/c10-stream-response.md。什么是 ContentReceiver从“整包下载”到“按块接收”cpp-httplib 默认的cli.Get(/path)会把整个响应体组装进res-body一个std::string。这在响应较大时会把全部数据载入内存代价明显。改用ContentReceiver后库在读取网络数据的循环中把每一块字节直接交给你的回调你可以边收边写盘、边收边解析。在 httplib.h 中它的定义是一个简单的函数对象using ContentReceiverWithProgress std::functionbool( const char *data, size_t data_length, size_t offset, size_t total_length); using ContentReceiver std::functionbool(const char *data, size_t data_length);ContentReceiver接收两个参数指向当前数据块的指针data和块长度data_length回调返回bool返回false即表示“停止接收”。而ContentReceiverWithProgress额外携带offset当前偏移与total_length总长度是进度显示与取消逻辑的底层形态普通ContentReceiver在内部会被包装成它见 httplib.h 附近的适配逻辑。逐块处理把每个 chunk 当作数据流消费原始文档给出了最直接的用法——把收到的每个块立刻写出并刷新#include httplib.h #include iostream httplib::Client cli(http://localhost:8080); auto res cli.Get(/logs/stream, [](const char *data, size_t len) { std::cout.write(data, len); std::cout.flush(); return true; // 返回 false 即停止接收 });要点如下回调按到达顺序被调用。数据块的顺序与服务端发送顺序一致因此适合流水线式消费比如把块直接追加到文件、管道或日志聚合器。返回false中途停止。从实现看接收循环在 httplib.h 中执行if (!out(buf, n, r, len)) return ReadContentResult::Error;回调返回false会终止循环并把本次请求标记为Error::Canceled错误字符串“Connection handling canceled”见 httplib.h。这等价于“下载了一部分就放弃”适合用于用户取消、容量超限等场景。块大小是固定的接收缓冲。接收循环使用CPPHTTPLIB_RECV_BUFSIZ默认 16384 字节见 httplib.h作为临时缓冲auto to_read (std::min)(read_len, CPPHTTPLIB_RECV_BUFSIZ);。也就是说你的回调每次拿到的len不会超过 16KB无需假设“一次调用等于一行或一条记录”。底层三态分帧Content-Length / 无长度 / Chunked为什么回调“按块到达”就能覆盖所有响应类型因为在 httplib.h 的read_content中库根据响应头自动选择三条读取路径Transfer-Encoding: chunked走read_content_chunkedhttplib.h由ChunkedDecoder逐块解帧后把 payload 交给回调无Content-Length走read_content_without_lengthhttplib.h一直读到连接关闭n 0视为成功结束有Content-Length走read_content_with_lengthhttplib.h按声明的长度精确循环读取。此外如果启用了解压缩decompressprepare_content_receiverhttplib.h会在回调外再包一层解压器库先解压再调用你的ContentReceiver同时用payload_max_length防御“zip bomb”防止解压后体积超限。这意味着你无需关心响应是 chunked 还是定长、是否压缩——回调拿到的始终是解码后的字节流。按行解析 NDJSON标准的缓冲 分行模式对于 NDJSONnewline-delimited JSON或按行输出的日志流数据块边界与行边界通常不对齐因此需要自己累积缓冲、按\n切行。原始文档给出了标准写法#include httplib.h #include nlohmann/json.hpp #include string std::string buffer; auto res cli.Get(/events, { buffer.append(data, len); size_t pos; while ((pos buffer.find(\n)) ! std::string::npos) { auto line buffer.substr(0, pos); buffer.erase(0, pos 1); if (!line.empty()) { auto j nlohmann::json::parse(line); handle_event(j); // 你的业务处理函数 } } return true; });这个模式包含三个关键步骤缺一不可累积buffer.append(data, len)把当前块追加到缓冲区——因为一行可能跨越多个网络块切行while循环用buffer.find(\n)反复取出完整行并erase已消费部分——因为一个块里可能包含多行残留while结束后 buffer 中剩下的就是“不完整的一行尾部”留给下一个块继续拼接。这是实时消费流式 API 的标准做法也正是仓库中 SSE 客户端示例 example/ssecli-stream.cc 所用的同款思路while (result.next()) { buffer.append(result.data(), result.size()); ... }按行解析SSEEvent。如果你的数据源是 SSEServer-Sent Events可以直接使用库内置的sse::SSEClient它封装了自动重连、按事件名分发与Last-Event-ID跟踪见 E04 文档。重要警告使用 ContentReceiver 时 res-body 为空原始文档明确警告传入ContentReceiver后res-body保持为空。原因是库检测到流式接收器后不再把数据组装进Response::body而是把所有权交给回调。因此必须在回调内部自行存储追加到std::string、写入std::ofstream或就地处理每个块不要在请求结束后再去读res-body那里不会有流式数据若响应体很小、你确实需要完整内存副本直接用res-body即可——C01 文档 对比了两种方式的取舍res-body简单但整包驻留内存ContentReceiver适合大文件。流式写文件大文件下载的推荐姿势配合std::ofstream流式下载既省内存又带失败即停的语义#include httplib.h #include fstream httplib::Client cli(http://localhost:8080); std::ofstream ofs(output.bin, std::ios::binary); if (!ofs) { return 1; } auto res cli.Get(/large-file, { ofs.write(data, len); return static_castbool(ofs); // 写盘失败时返回 false自动中断下载 });回调返回static_castbool(ofs)是一个巧妙而稳健的做法文件流一旦进入失败态磁盘满、IO 错误立刻返回false终止传输避免无限收数据。下载前先看响应头ResponseHandler ContentReceiver如果希望在下载正文前先检查Content-Length等响应头例如根据文件大小决定是否继续可以把ResponseHandler与ContentReceiver组合使用auto res cli.Get(/large-file, [](const httplib::Response res) { auto len res.get_header_value(Content-Length); std::cout Size: len std::endl; return true; // 返回 false 则跳过正文下载 }, { ofs.write(data, len); return static_castbool(ofs); });ResponseHandler在响应头到达之后、正文开始之前被调用false可直接跳过正文。签名可见于 httplib.h 的Get(path, ResponseHandler, ContentReceiver, DownloadProgress)重载。同样地Post/Put/Patch的响应体也可以流式接收对应重载见 httplib.h。与进度回调组合边下载边显示进度ContentReceiver与进度回调DownloadProgress参数为(current, total)可以同时使用——前者负责消费数据后者负责汇报进度二者互不冲突#include httplib.h #include atomic #include iostream std::atomicbool cancelled{false}; httplib::Client cli(http://localhost:8080); auto res cli.Get(/large-file, { // ContentReceiver写盘 ofs.write(data, len); return static_castbool(ofs); }, { // DownloadProgress显示进度 auto percent (total 0) ? (current * 100 / total) : 0; std::cout \rDownloading: percent % ( current / total ) std::flush; return !cancelled.load(); // 返回 false 取消传输 }); std::cout std::endl;几点说明详见 C11 文档进度回调每次数据到达都会触发total来自Content-Length服务端不发送时可能为0此时无法计算百分比只能显示已收字节数返回false中止传输这正是 UI 中“取消按钮”的接线方式——用一个std::atomicbool标志位下一次进度 tick 就停止从实现看进度回调在接收循环内于每个块之后被调用if (progress) { if (!progress(r, len)) return ReadContentResult::Error; }httplib.h所以“每块进度”是精确的。上传侧同理Post/Put的最后一个参数可传UploadProgress发送循环在 httplib.h 中按CPPHTTPLIB_SEND_BUFSIZ分片写入并在每片后调用进度回调返回false同样产生Error::Canceled。流式接收的配套能力ContentReader、服务端流式发送与 SSE掌握ContentReceiver之后还有三个相邻能力值得一并了解它们在 httplib.h 与示例中相互呼应1.ContentReaderPOST 响应体也能流式读ContentReader把“流式消费”抽象成一个可调用对象using Reader std::functionbool(ContentReceiver receiver); class ContentReader { bool operator()(ContentReceiver receiver) const; // ... };它既可以消费普通响应体也支持 multipart 表单读取operator()(FormDataHeader, ContentReceiver)。当需要把“读一段数据”的流程传递出去时例如封装一个可复用的下载工具函数用ContentReader比直接传ContentReceiver更灵活。2. 服务端侧set_content_provider / set_chunked_content_provider流式接收的另一端是流式发送。服务端通过 Response::set_content_provider 系列接口提供分块响应res.set_content_provider( length, application/octet-stream, [](size_t offset, size_t length, httplib::DataSink sink) - bool { // 从 offset 开始写出 length 字节返回 false 表示出错 return sink.write(...); }); res.set_chunked_content_provider( application/octet-stream, [](size_t offset, httplib::DataSink sink) - bool { // chunked 模式无需预先知道总长度 return sink.write(...) sink.done(); });配套的sink.write()、sink.done()与sink.is_writable()由 httplib.h 的发送循环驱动该循环逐块调用 provider 并处理“提前完成”与“无进展”等异常情况对应Error::Canceled/Error::Write。chunked 发送的具体实现见 httplib.h 的write_content_chunked。理解这一侧有助于你在调试流式 API 时准确判断问题出在客户端还是服务端。3. SSE流式消费的现成封装如果流式数据是text/event-stream类型的 SSE不必手写缓冲分行逻辑。库提供sse::SSEClientE04 文档一行注册回调即可httplib::Client cli(http://localhost:8080); httplib::sse::SSEClient sse(cli, /events); sse.on_message([](const httplib::sse::SSEMessage msg) { std::cout data: msg.data std::endl; }); sse.start(); // 阻塞运行GUI 场景用 start_async() stop()它内置自动重连、按event:名分发on_event、Last-Event-ID跟踪与重连间隔配置set_reconnect_interval/set_max_reconnect_attempts。仓库示例 example/ssecli-stream.cc 则展示了基于httplib::stream::Get手写事件循环的完整参考实现其中“累积 buffer → 按\n切行 → 解析 → 保留残段”的流程与本文 NDJSON 模式完全一致可作为加深理解的对照。小结流式接收的决策速查场景推荐方案关键点大文件下载Get(path, ContentReceiver)ofstream逐块写盘res-body为空NDJSON / 日志流Get(path, ContentReceiver) buffer 切行find(\n)循环 残段保留边下载边显示进度ContentReceiverDownloadProgress两者可同时传false即取消下载前检查响应头ResponseHandlerContentReceiver头到达后、正文前回调服务端流式发送set_content_provider/set_chunked_content_providerchunked 模式无需预知长度SSE 实时事件sse::SSEClient自动重连 Last-Event-ID核心一条始终不变传入ContentReceiver后响应体不再自动存入res-body一切数据都在回调里。记住这一条流式下载、逐行解析、实时消费三类需求便都能以统一的回调模式优雅实现。赞分享后端网络【免费下载链接】cpp-httplibA C header-only HTTP/HTTPS server and client library项目地址https://gitcode.com/GitHub_Trending/cp/cpp-httplib点击查看免费下载相关推荐从 pandas 到 PyArrowPySpark 标量类型安全转换矩阵与 golden 测试机制深度解析从 pandas 到 PyArrowPySpark 标量类型安全转换矩阵与 golden 测试机制深度解析 本篇文章围绕 Apache Spark 仓库中的后端网络OpenStatus Docker 部署完全指南从快速启动到生产级自托管OpenStatus Docker 部署完全指南从快速启动到生产级自托管 OpenStatus 是一个开源的状态页与可用性监控平台支持通过代码YAML/T后端网络cpp-httplib 流式 API 实战用 stream::Get 与 open_stream 实现逐块读取、SSE 与反向代理cpp httplib 流式 API 实战用 stream::Get 与 open_stream 实现逐块读取、SSE 与反向代理 本篇文章以 cpp htt后端网络创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网