第 23 篇 收官第三季算法篇;第四季进入工程实战。第一篇从最常见的后端能力入手:用 cpp-httplib 搭 HTTP 服务,用 nlohmann/json 处理 JSON——几十行代码就能跑起一个 REST 接口。
demo:ref/cpp_demo/networking/http_json/。
这是「现代 C++ 实战」系列的第 24 篇,第四季开篇。建议先读 第 01 篇:CMake 与现代构建(FetchContent)和 第 23 篇。
一、HTTP 基础速览
1 2 3 4 5 6 7
| 客户端 服务端 │ GET /camera HTTP/1.1 │ │ Host: localhost:8080 ────────→ │ 路由匹配 → 处理 → 响应 │ │ │ ← HTTP/1.1 200 OK │ │ Content-Type: image/jpeg │ │ [二进制 body] │
|
| 概念 |
说明 |
| 方法 |
GET 读取、POST 创建、PUT 更新、DELETE 删除 |
| 路径 |
/users、/camera——路由的匹配键 |
| 状态码 |
200 成功、404 未找到、400 请求错误、500 服务端错误 |
| Content-Type |
application/json、image/jpeg、text/plain |
| REST |
用 URL 表资源,用 HTTP 方法表操作 |
二、cpp-httplib:头文件即服务器
cpp-httplib 是单头文件 HTTP 库,适合原型和轻量服务:
1 2 3 4 5 6 7 8 9
| #include <httplib.h>
httplib::Server svr;
svr.Get("/hello", [](const httplib::Request& req, httplib::Response& res) { res.set_content("Hello, World!", "text/plain"); });
svr.listen("0.0.0.0", 8080);
|
demo 的 main.cpp 在此基础上加了日志中间件和图片接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| svr.set_logger([](const httplib::Request& req, const httplib::Response& res) { std::cout << "[请求] " << req.method << " " << req.path << " | 来源: " << req.remote_addr << " | 状态码: " << res.status << std::endl; });
svr.Get("/camera", [](const httplib::Request& req, httplib::Response& res) { std::ifstream file("yellow_ducks.jpeg", std::ios::binary); if (!file) { res.status = 404; res.set_content("Image not found", "text/plain"); return; } std::string image_data((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); res.set_content(image_data, "image/jpeg"); });
|
要点:
res.status 设置状态码(默认 200)
res.set_content(body, mime_type) 设置响应体
- 二进制文件用
std::ios::binary 读取,避免换行符被转换
三、nlohmann/json:现代 C++ 的 JSON
nlohmann/json 是事实标准的 JSON 库,接口接近 STL:
1 2 3 4 5 6 7 8 9 10
| #include <nlohmann/json.hpp> using json = nlohmann::json;
json j = {{"name", "WALL-E"}, {"age", 2000}}; std::string s = j.dump(2);
json parsed = json::parse(R"({"name":"EVE","age":500})"); std::string name = parsed["name"];
|
自定义类型(NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE 宏):
1 2 3 4 5 6 7 8 9 10
| struct User { int id; std::string name; std::string email; }; NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(User, id, name, email)
User u{1, "Alice", "alice@example.com"}; json j = u; User u2 = j.get<User>();
|
四、REST CRUD 模式
把 JSON 和路由组合,就是标准 REST API。内存中用 std::vector 或 std::unordered_map 存数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| std::vector<User> users = {{1, "Alice", "alice@example.com"}};
svr.Get("/users", [](const httplib::Request&, httplib::Response& res) { res.set_content(json(users).dump(), "application/json"); });
svr.Get(R"(/users/(\d+))", [](const httplib::Request& req, httplib::Response& res) { int id = std::stoi(req.matches[1]); for (auto& u : users) if (u.id == id) { res.set_content(json(u).dump(), "application/json"); return; } res.status = 404; res.set_content(R"({"error":"not found"})", "application/json"); });
svr.Post("/users", [](const httplib::Request& req, httplib::Response& res) { auto j = json::parse(req.body); User u = j.get<User>(); users.push_back(u); res.status = 201; res.set_content(json(u).dump(), "application/json"); });
svr.Put(R"(/users/(\d+))", [](const httplib::Request& req, httplib::Response& res) { int id = std::stoi(req.matches[1]); auto j = json::parse(req.body); for (auto& u : users) if (u.id == id) { u = j.get<User>(); u.id = id; break; } res.set_content(json(users).dump(), "application/json"); });
svr.Delete(R"(/users/(\d+))", [](const httplib::Request& req, httplib::Response& res) { int id = std::stoi(req.matches[1]); users.erase(std::remove_if(users.begin(), users.end(), [id](const User& u) { return u.id == id; }), users.end()); res.status = 204; });
|
| 方法 |
路径 |
作用 |
成功状态码 |
| GET |
/users |
列表 |
200 |
| GET |
/users/:id |
详情 |
200 / 404 |
| POST |
/users |
创建 |
201 |
| PUT |
/users/:id |
更新 |
200 |
| DELETE |
/users/:id |
删除 |
204 |
demo 当前实现了 /camera 图片接口;CRUD 是同一套 httplib + json 的自然延伸,下一篇 SQLite 会接上持久化。
五、FetchContent 拉依赖
CMakeLists.txt 用 第 01 篇 学过的 FetchContent 自动下载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| include(FetchContent)
FetchContent_Declare(httplib URL https://github.com/yhirose/cpp-httplib/archive/refs/tags/v0.15.3.tar.gz ) FetchContent_Declare(nlohmann_json URL https://github.com/nlohmann/json/archive/refs/tags/v3.11.3.tar.gz ) set(JSON_BuildTests OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(httplib nlohmann_json)
add_executable(httplib_json_demo main.cpp) target_link_libraries(httplib_json_demo PRIVATE httplib::httplib nlohmann_json::nlohmann_json )
|
HTTPS 支持需链接 OpenSSL:
1 2 3
| find_package(OpenSSL REQUIRED) target_link_libraries(httplib_json_demo PRIVATE OpenSSL::SSL OpenSSL::Crypto) target_compile_definitions(httplib_json_demo PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
macOS:brew install openssl,CMake 可能需要 -DOPENSSL_ROOT_DIR=/opt/homebrew/opt/openssl。
资源文件复制到构建目录:
1 2
| configure_file(${CMAKE_SOURCE_DIR}/yellow_ducks.jpeg ${CMAKE_BINARY_DIR}/yellow_ducks.jpeg COPYONLY)
|
六、错误处理与日志
| 场景 |
处理 |
| 文件不存在 |
res.status = 404 + 错误消息 |
| JSON 解析失败 |
try/catch (json::parse_error&) → 400 |
| 未捕获异常 |
全局 handler 或返回 500 |
1 2 3 4 5
| svr.set_exception_handler([](const httplib::Request&, httplib::Response& res, std::exception_ptr ep) { res.status = 500; res.set_content(R"({"error":"internal server error"})", "application/json"); });
|
set_logger 记录每个请求的方法、路径、来源 IP、状态码——开发阶段够用;生产环境可换 spdlog(第 01 篇 fetch_content demo 已集成)。
七、运行 demo 与 curl 测试
1 2 3
| cd ref/cpp_demo/networking/http_json ./build.sh ./build.sh --run
|
服务监听 0.0.0.0:8080,另开终端测试:
1 2 3 4 5 6 7 8 9 10
| curl -v http://localhost:8080/camera -o duck.jpg file duck.jpg
curl http://localhost:8080/users curl -X POST http://localhost:8080/users \ -H "Content-Type: application/json" \ -d '{"id":2,"name":"Bob","email":"bob@example.com"}' curl -X DELETE http://localhost:8080/users/2
|
八、与其他技术栈对比
| 方案 |
特点 |
| cpp-httplib |
头文件、零依赖(HTTP)、适合原型 |
| Boost.Beast |
异步、与 Boost.Asio 集成,适合高性能 |
| Crow / Pistache |
类似 Flask 的路由风格 |
| gRPC |
二进制协议,微服务间通信 |
学习路径:httplib 入门 → 需要持久化接 SQLite(第 25 篇)→ 需要并发接 第 14 篇 线程池。
九、小结
| 要点 |
内容 |
| HTTP |
方法 + 路径 + 状态码 + Content-Type |
| cpp-httplib |
Get/Post 路由、set_content、中间件 |
| nlohmann/json |
dump/parse、宏绑定自定义类型 |
| CMake |
FetchContent 拉 httplib + json |
| REST |
CRUD 五件套,JSON 作 body |
| 测试 |
curl 验证接口 |
下一篇给服务加上持久化:SQLite 嵌入式数据库 + DatabaseManager——见 第 25 篇:SQLite 数据库实战(计划)。