【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. 参考资料

相关推荐
白杨尚青14 分钟前
C++进阶篇(二):多态——虚函数、重写、虚函数表与三个经典坑(附完整代码仓库)
开发语言·c++·多态·虚函数·重写·动态绑定
syagain_zsx23 分钟前
spdlog实战:日志分级、滚动与性能调优
linux·c++·日志库·二次开发日志库
lhh_qrsly1 小时前
HTTP / HTTPS / MQTT / MQTTs(嵌入式学习版)
学习·http·https
WJ.Polar18 小时前
Java封装、继承、多态
java·开发语言
摇滚侠18 小时前
Java 中如何表示负数,有符号和无符号的区别,补码的计算规则,char byte short int long 的范围
java·开发语言
weixin_3077791318 小时前
C++代码实现MATLAB中的fitrtree函数功能
开发语言·c++·算法·matlab
AI wins19 小时前
c++新增頭文件
c++
Shadow(⊙o⊙)19 小时前
仿muduo实现高并发服务器——事件就绪管理Channel.hpp
服务器·c++
爱读书的小胖19 小时前
Codewars:Rot13(C)解题分析
c语言·开发语言