【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. 参考资料
- cpp-httplib 0.57.1:固定版本说明和示例。
- 该版本头文件:平台要求、配置默认值和 API。
- nlohmann/json:本文 JSON 解析部分的依赖。
- 完整示例:默认自测,以及可复用的 mock 服务。