【C++三方组件】cpp-httplib:一个头文件起 HTTP 服务

【C++三方组件】cpp-httplib:一个头文件起 HTTP 服务

【摘要】:cpp-httplib 为 C++ 提供易于嵌入的 HTTP 服务端与客户端。本文介绍它适合怎样的工具与服务,说明自行处理 HTTP 请求的维护成本,再通过路由、参数、JSON、上传下载、静态文件和自测案例展示用法。

【版本基准】:cpp-httplib 0.57.1(MIT)|C++17;JSON 案例另用 nlohmann/json 3.12.0。完整代码见 httplib_demo.cpp。同一程序也为第 26、27 篇提供本地 mock。

1. What:cpp-httplib 是什么

cpp-httplib 是一个可作为单头文件集成的 HTTP 库,同一套代码中同时提供 Server 和 Client。服务端通过注册路由响应请求,客户端通过 Get、Post 等方法发起调用。

它的服务端以阻塞 I/O 和线程池为基础:连接交给任务队列中的工作线程处理,handler 可以按顺序读取请求、调用业务代码、设置响应。它没有要求每个 handler 都写成异步状态机。

这类接口适合给已有 C++ 程序增加控制或调试端点、构建本地 mock、提供简单内部服务。认证、权限、持久化和复杂业务编排仍由应用负责。

2. Why:为什么不用 socket 自己写一个 HTTP 服务

如果只接收一段固定文本,自行实现并不复杂。变成 HTTP 服务之后,还需要处理请求行、头字段、请求体长度、分块编码、连接复用、路径参数和错误响应。继续加入文件上传、TLS 和静态资源后,测试与维护的范围会显著扩大。

需求 自行实现的工作 cpp-httplib 提供的入口
不同 URL 执行不同逻辑 解析、匹配方法和路径 Get、Post 等路由
读取查询和路径参数 URL 解码、参数提取 params、path_params
生成 HTTP 响应 状态行、头、正文和连接语义 Response
接收上传、发送文件 multipart、内容传输与头处理 表单接口、内容提供器、静态目录挂载
测试自己的服务 另找客户端或手写请求 同库 Client

它减少的是 HTTP 基础处理和接入成本。JSON 解析需要另选库;数据校验、异常响应内容和业务限流也要自己定义。单头文件并不意味着开启 TLS、压缩等可选能力后依然没有外部依赖。

同步 handler 的代价也很明确:等待数据库或慢客户端会占用工作线程。是否适合某个服务,要看并发连接、处理时长、排队和尾延迟,不能只按"内网"或"公网"作判断。

3. How:接入与运行

可以将固定版本的 httplib.h 加入项目,也可以使用 vcpkg 的 cpp-httplib 包。示例使用 JSON 库解析请求体,因此还需 nlohmann-json:

cmake 复制代码
find_package(httplib CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
add_executable(app httplib_demo.cpp)
target_compile_features(app PRIVATE cxx_std_17)
target_link_libraries(app PRIVATE httplib::httplib nlohmann_json::nlohmann_json)

直接拷贝头文件时,Windows 需要链接 ws2_32。0.57.1 的 Windows 目标要求 Windows 10 或更新版本 ,配套工程显式定义 _WIN32_WINNT=0x0A00,不再沿用旧稿中面向 Windows 8 的值。编译条件随版本变化,应以固定版本头文件为准。

按 配套说明选择 BLOG_COMPONENTS=httplib 构建。程序有两种运行方式:

bash 复制代码
./build/httplib_demo
./build/httplib_demo serve 18080

第一条在随机回环端口启动服务,用 Client 验证多种请求后自动退出;第二条持续监听 127.0.0.1:18080,供其他客户端调用,使用结束后在该终端按 Ctrl-C。

3.1 普通路由、查询参数与路径参数

handler 接收 const Request& 和可修改的 Response&。以下代码均在监听前注册:

cpp 复制代码
httplib::Server server;
server.Get("/hi", [](const auto&, auto& res) {
  res.set_content("Hello World!", "text/plain");
});
server.Get("/echo", [](const auto& req, auto& res) {
  res.set_content(req.get_param_value("msg"), "text/plain; charset=utf-8");
});
server.Get("/users/:id", [](const auto& req, auto& res) {
  res.set_content(nlohmann::json{{"id", req.path_params.at("id")}}.dump(),
                  "application/json");
});

三条路由分别演示固定响应、查询参数和路径参数。客户端可以直接运行:

bash 复制代码
curl "http://127.0.0.1:18080/hi"
curl "http://127.0.0.1:18080/echo?msg=hello%20world"
curl "http://127.0.0.1:18080/users/42"

对应响应体为 Hello World!、hello world 和 {"id":"42"}。Windows PowerShell 中若 curl 被映射为别名,请使用 curl.exe。

实际业务还应验证参数是否存在、格式与取值范围是否正确。路由匹配成功并不代表输入已通过业务校验。

3.2 JSON 请求:解析、校验、返回

HTTP 库把请求体放入 req.body,JSON 库负责解析:

cpp 复制代码
server.Post("/json", [](const auto& req, auto& res) {
  auto body = nlohmann::json::parse(req.body, nullptr, false);
  if (body.is_discarded() || !body.is_object() ||
      !body.contains("msg") || !body["msg"].is_string()) {
    res.status = 400;
    res.set_content(R"({"error":"msg must be a string"})", "application/json");
    return;
  }
  res.set_content(nlohmann::json{{"received", body["msg"]}}.dump(),
                  "application/json");
});

这里关闭解析异常,显式检查语法与字段类型。缺少字段、类型错误和 JSON 语法错误都返回 400;合法输入则返回 200。它演示的是边界检查,不只是把任意字符串当成 JSON 回传。

可以用第 26、27 篇的 post 模式调用,也可以在本篇自测中同时验证合法与非法输入。

3.3 上传和下载:关注当前版本的表单接口

0.57.1 的 multipart 文件访问位于 req.form:

cpp 复制代码
server.Post("/upload", [](const auto& req, auto& res) {
  if (!req.form.has_file("file")) {
    res.status = 400;
    res.set_content("missing file", "text/plain");
    return;
  }
  const auto& file = req.form.get_file("file");
  res.set_content("uploaded " + std::to_string(file.content.size()) + " bytes",
                  "text/plain");
});
server.Get("/download", [](const auto&, auto& res) {
  res.set_content("download payload\n", "application/octet-stream");
});

准备好 upload.txt 后,可以用 curl -F "file=@upload.txt" http://127.0.0.1:18080/upload,或运行 cpr 的上传案例。/download 返回一段确定内容,便于练习客户端流式写文件。

本例上传文件内容已经在内存中,适合有明确大小限制的小文件。大文件要评估流式读取接口;若写到磁盘,不应直接把客户端传来的文件名当作服务器路径。

3.4 静态文件:给内部工具配一个页面

创建 public 目录并放入 index.html,启动:

bash 复制代码
./build/httplib_demo serve 18080 public

配套程序会在启动前执行:

cpp 复制代码
if (!server.set_mount_point("/static", directory))
  throw std::runtime_error("static directory does not exist");

访问 /static/index.html 即可获取该文件。静态目录应是专门准备的资源目录,避免把工作目录、配置或凭据一并放入可访问范围。目录不存在时,示例报告错误,不会假装挂载成功。

3.5 错误与资源限制:在启动前统一配置

cpp 复制代码
server.set_payload_max_length(1024 * 1024);
server.set_read_timeout(5, 0);
server.set_write_timeout(5, 0);
server.set_error_handler([](const auto& req, auto& res) {
  if (res.status == 404)
    res.set_content("no route: " + req.path, "text/plain");
});
server.set_exception_handler([](const auto&, auto& res, std::exception_ptr) {
  res.status = 500;
  res.set_content("internal error", "text/plain");
});

当前版本设置请求体上限的接口名是 set_payload_max_length。库本身有默认限制,应用仍应按接口需求设定合适值,而不是依赖"默认应该够用"。读写超时通常描述 I/O 等待,不能代替所有业务操作的总时间预算。

这里的 404 处理器只改写 404,保留 /json 已经生成的 400 错误正文。异常处理器避免把内部异常细节直接发送给客户端;详细诊断应写入服务端日志。

可通过 server.new_task_queue 定制工作队列。即使使用流式响应,若生成内容的逻辑仍长期占着处理线程,也不能据此认定并发问题已经解决。

3.6 自包含测试:随机端口与明确退出

配套程序的默认模式先绑定端口,再启动监听线程:

cpp 复制代码
const int port = server.bind_to_any_port("127.0.0.1");
if (port < 0) throw std::runtime_error("bind failed");
std::thread listener([&] { server.listen_after_bind(); });
struct Stopper {
  httplib::Server& server;
  std::thread& thread;
  ~Stopper() { server.stop(); thread.join(); }
} stopper{server, listener};
server.wait_until_ready();

httplib::Client client("127.0.0.1", port);
client.set_connection_timeout(2, 0);
client.set_read_timeout(2, 0);
client.set_write_timeout(2, 0);
client.set_keep_alive(true);

绑定成功后仍等监听准备就绪;请求失败或校验抛异常时,清理对象也会执行 stop 和 join。复用 Client 时显式开启 keep-alive,不能仅凭"使用同一个 Client 对象"就假设连接一定保持。

客户端结果先检查有没有收到有效响应,再读取 HTTP 状态;404 也是有效响应。完整示例逐项检查状态与正文,成功输出:

text 复制代码
GET /hi -> 200 Hello World!
GET /echo -> 200 hello world
GET /users/42 -> 200 {"id":"42"}
POST /json -> 200 {"received":"hello"}
invalid JSON -> 400 {"error":"msg must be a string"}
GET /missing -> 404 no route: /missing
all checks passed

4. 适用边界与扩展

本篇使用 HTTP 回环服务,未启用 TLS。HTTPS 可以通过 OpenSSL 支持配置 SSLServer/SSLClient,还需要证书与信任链配置;SSE、分块传输等功能则可在需要时查阅对应版本示例。

对于简单工具接口,cpp-httplib 的接入成本低;希望使用更完整的 Web 框架组织路由与中间件,可以比较 Crow 等方案;需要在 Asio 上定制协议行为,可以比较 Beast。最终应使用实际连接数量、慢请求和响应大小验证容量。

5. 参考资料

相关推荐
不会就选b2 小时前
Linux之应用层协议HTTP(二)
网络·网络协议·http
无名猿2 小时前
new/delete 与 malloc/free:为什么绝对不能混用
c++·内存管理·现代c++·踩坑记录
小小小小钰儿2 小时前
1.5-Python网络编程
开发语言·网络·python·web安全·计算机·网络安全
hiahiahia1232 小时前
实现完整 Tool Dispatcher
开发语言·前端
AC赳赳老秦3 小时前
公开音频转写信息提取:OpenClaw 处理发布会与听证会文本并提取核心决策信息
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw
@yanyu6663 小时前
C编译器安装与第一个C程序
c语言·开发语言
落魄实习生3 小时前
Agent Scope Java 2.x 系列【2】 ReActAgent
java·开发语言