news 2026/10/2 7:11:47

cpp-httplib 实战:使用 make_file_body() 以原始二进制形式 POST/PUT 文件内容

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
cpp-httplib 实战:使用 make_file_body() 以原始二进制形式 POST/PUT 文件内容
  • 后端
  • 网络

【免费下载链接】cpp-httplib

A C++ header-only HTTP/HTTPS server and client library

项目地址:https://gitcode.com/GitHub_Trending/cp/cpp-httplib
点击查看免费下载

导读

在对接 S3 兼容存储 API、图片上传接口等场景时,往往需要把整个文件内容原样作为 HTTP 请求体发送,而不是包装成 multipart 表单。cpp-httplib 提供了httplib::make_file_body(),它把文件路径解析为「文件大小 +ContentProvider」的组合,让你通过Post()/Put()直接以Content-Length方式流式上传大文件。读完本文,你将掌握该 API 的调用方式、底层读取机制、失败处理要点及其适用边界,并能结合仓库源码理解它为何可以做到大文件不占用内存。

为什么需要「原始二进制」请求体

普通文本或小文件可以用cli.Post(path, body, content_type)直接把std::string作为请求体发送。但有两类需求不适合这种方式:

  • 请求体必须精确等于文件字节内容,不能有多余的分隔符、boundary 或字段头——例如 S3 兼容 API 的PUT /bucket/key上传,以及接收原始图片数据的端点;
  • 文件很大,把整个文件读进std::string会带来显著内存开销。

multipart 上传(见 C07. Upload a file as multipart form data)适用于同时携带多个字段或文件;而本场景只需要「文件内容直通请求体」。make_file_body()正是为此设计。

基本用法

#include <httplib.h> #include <iostream> int main() { httplib::Client cli("https://storage.example.com"); // 返回 (文件大小, ContentProvider) 的组合 auto [size, provider] = httplib::make_file_body("backup.tar.gz"); if (size == 0) { std::cerr << "Failed to open file" << std::endl; return 1; } auto res = cli.Put("/bucket/backup.tar.gz", size, provider, "application/gzip"); if (res && res->status == 200) { std::cout << "Upload succeeded" << std::endl; } else { std::cerr << "Upload failed: " << httplib::to_string(res.error()) << std::endl; } return 0; }

要点说明:

  • make_file_body()接受文件路径(std::string),返回std::pair<size_t, ContentProvider>:first是文件字节大小,second是内容提供器。
  • 将size、provider直接传给Post()或Put(),文件内容便作为请求体原样发送。
  • 第三个参数是Content-Type,例如"application/gzip"、"application/octet-stream"、"image/jpeg",按目标端点要求填写即可。

这与仓库 README.md 中的示例用法一致:

auto [size, provider] = httplib::make_file_body("/path/to/data.bin"); auto res = cli.Post("/upload", size, provider, "application/octet-stream");

从源码结构看,客户端为此提供了若干配套重载,例如(见 httplib.h):

Result Post(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr); Result Put(const std::string &path, size_t content_length, ContentProvider content_provider, const std::string &content_type, UploadProgress progress = nullptr);

即「显式content_length+ContentProvider+Content-Type(可选进度回调)」这一族 API,与make_file_body()的返回类型精确对接。

ContentProvider 是如何做到流式读取的

ContentProvider在 httplib.h 中定义为一个函数对象:

using ContentProvider = std::function<bool(size_t offset, size_t length, DataSink &sink)>;

它接收offset(本次要发送的起始偏移)和length(本次要发送的字节数),通过sink.write(buf, n)把数据交出去,返回bool表示是否成功。DataSink(见 httplib.h)封装了write、done、is_writable等回调,并提供一个std::ostream os便于以流方式输出。

make_file_body()的实现(见 httplib.h)分两步:

  1. 探测文件大小:以std::ios::binary | std::ios::ate打开文件,用tellg()取得大小并返回;打开失败则返回{0, ContentProvider{}}。
  2. 构造分块读取的 provider:真正的上传阶段,provider 每次最多读取8192 字节(char buf[8192]),并把读到的内容立即交给sink.write():
ContentProvider provider = filepath -> bool { std::ifstream f(filepath, std::ios::binary); if (!f) { return false; } f.seekg(static_cast<std::streamoff>(offset)); if (!f.good()) { return false; } char buf[8192]; while (length > 0) { auto to_read = (std::min)(sizeof(buf), length); f.read(buf, static_cast<std::streamsize>(to_read)); auto n = static_cast<size_t>(f.gcount()); if (n == 0) { return false; } if (!sink.write(buf, n)) { return false; } length -= n; } return true; };

因此无论文件多大,同一时刻内存中最多只驻留一个 8 KiB 的缓冲区,大文件上传也不会把整个文件载入内存——这正是本 API 相对「整体读入字符串再发送」的核心优势。

文件打不开时会发生什么

make_file_body()打开文件失败时,返回的size为0,provider为空函数对象(ContentProvider{})。

如果忽略检查而直接发送,请求体会是垃圾数据,必须始终先检查size:

auto [size, provider] = httplib::make_file_body("backup.tar.gz"); if (size == 0) { // 处理文件不存在 / 无权限等情况,而不是继续发送 }

这一点在测试中有直接佐证。test/test.cc 的MakeFileBodyTest.Basic先写一个 4096 字节的临时文件,再make_file_body()后断言fb.first > 0,随后用cli.Post("/upload", fb.first, fb.second, "application/octet-stream")上传,服务端收到的req.body与文件内容完全相等,验证了「大小 + provider」组合端到端的正确性。

警告:Content-Length 是预先固定下来的

make_file_body()在返回前就用tellg()读了一次文件大小,这个值随后会作为Content-Length承诺给对端。因此:

  • 若上传过程中文件被截短,provider 读到n == 0(EOF 提前到来)时会返回false,传输以失败告终——实现中的注释也明确指出:调用方已把测量到的大小作为 Content-Length 发出,请求体无法补全时只能像其他错误一样失败(见 httplib.h);
  • 若文件在测量后被写大,则发送内容仍以测量时的大小为限。

也就是说,如果你无法保证上传期间文件大小不变,这个 API 就不适合。此时应改用不确定长度的分块传输(chunked)方案,或先在本地固定一份快照再上传。

test/test.cc 的MakeFileBodyTest.TruncatedFileMakesTheProviderFail精确复现了这一场景:先测量 100 字节的文件,随后把文件截短为 10 字节,再调用 provider 期望其返回false,且写入 sink 的只有 10 字节。而MakeFileBodyTest.WholeFileIsSent(test/test.cc)用 9000 字节内容(跨多个 8 KiB 读取)验证了 provider 能把整个文件完整送出,两次测试共同构成了该 API 的边界行为契约。

与 multipart 上传如何取舍

如果你需要的是「表单字段 + 文件」的组合,或一次携带多个文件,应该走 multipart 路线:使用httplib::make_file_provider()构造FormDataProviderItems,再调用cli.Post("/upload", {}, {}, providers)(见 README.md),详见 C07. Upload a file as multipart form data。

选择依据可以简单归纳为:

需求推荐 API
请求体 = 单个文件原始字节,且有确定的 Content-Lengthmake_file_body()+Post()/Put()
需要多个字段/文件,或字段与文件混合make_file_provider()/FormDataProviderItems+ multipart
文件大小无法预先固定、需要流式分块ContentProviderWithoutLength+ chunked 上传(见 C09. Chunked upload)

小结

  • httplib::make_file_body(path)返回(size, ContentProvider),配合Post()/Put()即可把文件作为原始二进制请求体发送,Content-Type由你指定。
  • 底层按 8192 字节分块经DataSink写出,超大文件也不会整体驻留内存。
  • 文件打不开时返回size == 0,必须显式检查后再发送。
  • Content-Length 在调用前就已固定,上传期间文件大小可能变化时请改用其他方案。
  • 多文件/字段场景请回到 multipart 路线(C07),分块流式场景可参考 C09。
  • 后端
  • 网络

【免费下载链接】cpp-httplib

A C++ header-only HTTP/HTTPS server and client library

项目地址:https://gitcode.com/GitHub_Trending/cp/cpp-httplib
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 7:10:20

Hugging Face LeRobot与OpenVLA具身智能技术拆解及实操指南

近期社交平台上流传一段视频&#xff0c;一只外表为鸭形的机器人能够精准抓取桌面物品。部分自媒体误传为Hugging Face官方推出新款鸭形机器人。事实上&#xff0c;Hugging Face作为AI模型与数据集托管平台&#xff0c;并未发布任何实体硬件。该鸭形机器人实为开源社区开发者利…

作者头像 李华
网站建设 2026/10/2 7:10:01

影刀RPA实操指南:登录态保持实战——Cookie备份与失效自动重登

影刀RPA实操指南&#xff1a;登录态保持实战——Cookie备份与失效自动重登 做数据采集的流程&#xff0c;最怕的不是报错&#xff0c;而是跑到半夜发现页面早就被踢回了登录页&#xff0c;后面抓到的全是空数据。用影刀RPA做网页自动化&#xff0c;登录态失效是出现频率最高的翻…

作者头像 李华
网站建设 2026/10/2 7:09:49

元初混沌体系 第四卷 太赫兹高频通信与超宽带频谱体系:第九十六篇 航空、海事、远洋船舶太赫兹全域通信方案

第九十六篇 航空、海事、远洋船舶太赫兹全域通信方案前置提要本篇隶属于元初混沌体系・第四卷《太赫兹高频通信与超宽带频谱体系》第六单元全域组网、产业落地、代差升维总纲&#xff08;91–108&#xff09;。承接第九十五篇智慧工厂、智慧城市太赫兹超大带宽工业应用范式&…

作者头像 李华
网站建设 2026/10/2 7:09:18

FlowNova 海外程序化广告落地实战指南

很多技术团队在筹划出海广告业务时&#xff0c;往往被“自建平台”的宏大叙事吓退。一提到广告交易枢纽&#xff0c;脑海中浮现的便是庞大的服务器集群、复杂的实时竞价协议以及难以捉摸的全球流量清洗规则。实际上&#xff0c;对于大多数希望快速验证商业模式的应用开发者或代…

作者头像 李华
网站建设 2026/10/2 7:08:09

EchoWM 先跑通最小一次生成、再接交互控制

做可交互的视听生成&#xff0c;卡点往往不在交互层&#xff0c;而在“一次完整生成”本身没跑通。EchoWM 这类项目要先确认三件事&#xff1a;权重能被加载、一个示例输入能被读进去、输出能落到磁盘并能被播放器或解码工具打开。这三件事成立之前接交互控制&#xff0c;只会把…

作者头像 李华
网站建设 2026/10/2 7:07:43

TP301 DTU实战调试指南:MODBUS透传、TCP心跳与TLINK协议配置

简介&#xff1a;本资源是TP301系列无线数据传输终端&#xff08;DTU&#xff09;的官方使用说明书&#xff0c;面向工业物联网工程师、自动化系统集成人员及嵌入式设备调试人员&#xff0c;解决DTU设备快速部署、协议对接与故障排查等核心问题。文档覆盖产品硬件接口说明&…

作者头像 李华