欢迎光临
我们一直在努力

cpp-httplib 完全指南:核心 API、普通响应与流式响应全解析

文章目录

  • 一、cpp-httplib 的介绍
  • 二、cpp-httplib库 的安装
  • 三、cpp-httplib库 的重要组成结构
    • 1. httplib::Request
    • 2. httplib::Response
      • 2.1 基本结构
      • 2.2 httplib::Response::set_content 详解(普通响应)
      • 2.3 httplib::Response::set_chunked_content_provider 详解(流式响应函数)
    • 3. httplib::Server
      • 3.1 基本结构
      • 3.2 httplib::Server::set_mount_point 方法解析(设置 静态文件服务挂载点)
      • 3.3 手动注册路由规则(示例演示)
    • 4. httplib::Client
      • 4.1 基本结构
      • 4.2 通过 httplib::Client::Post接口发送普通请求(httplib::Client::Post 接口详解)
      • 4.3 通过 httplib::Client::send接口发送流式响应请求(httplib::Client::send 接口详解)
      • 4.4 httplib::Client::Post接口 和 httplib::Client::send接口的对比
    • 5. httplib::Result 与 httplib::Response的关系
    • 6. Client → Server 完整请求流程(普通响应)
    • 7. Client → Server 完整请求流程(流式响应)

一、cpp-httplib 的介绍

C++ HTTP 库(cpp-httplib)是一个轻量级的 C++ HTTP 客户端/服务器库,它提供了简单的 API 来创建 HTTP 服务器和客户端,支持同步和异步操作。以下是一些关于 cpp-httplib 的主要特点:

  • 轻量级: cpp-httplib 的设计目标是简单和轻量,只有一个头文件包含即可,不依赖于任何外部库。
  • 跨平台: 它支持多种操作系统,包括 Windows、Linux 和 macOS。
  • 同步和异步操作: 库提供了同步和异步两种操作方式,允许开发者根据需要选择。
  • 支持 HTTP/1.1: 它实现了 HTTP/1.1 协议,包括持久连接和管道化。
  • Multipart form-data: 支持发送和接收 multipart/form-data 类型的请求,这对于文件上传非常有用。
  • SSL/TLS 支持: 通过使用 OpenSSL 或 mbedTLS 库,cpp-httplib 支持 HTTPS 和 WSS。
  • 简单易用: API 设计简洁,易于学习和使用。
  • 性能: 尽管是轻量级库,但性能表现良好,适合多种应用场景。
  • 社区活跃: cpp-httplib 有一个活跃的社区,不断有新的功能和改进被加入。
  • 二、cpp-httplib库 的安装

    cpp-httplib 是一个轻量级的 C++ HTTP 库,采用 header-only 设计,只需包含一个头文件即可使用。以下是cpp-httplib 的安装:

    # httplib下载
    dev@dev-host:~/workspace$ git clone https://github.com/yhirose/cpp-httplib.git
    # 注意:cpp-httplib是 header-only库,只需要⼀个头⽂件,在项目中只需时只需要包含该头⽂件即可
    # 我的 cpp-httplib库下载在根目录下
    bit@bit08:~$ ls
    AIProj clash cpp-httplib test will
    bit@bit08:~$ cd cpp-httplib/
    bit@bit08:~/cpp-httplib/cpp-httplib$ ls
    benchmark cmake CMakeLists.txt docker docker-compose.yml Dockerfile
    example httplib.h LICENSE meson.build meson_options.txt README.md
    split.py test

    # 将httplib.h拷⻉到系统⽬录下,在程序中#include <httplib.h>时能直接找到
    bit@bit08:~/cpp-httplib/cpp-httplib$ sudo cp httplib.h /usr/include/

    三、cpp-httplib库 的重要组成结构

    1. httplib::Request

    namespace httplib {
    struct Request {
    std::string method;
    std::string path;
    Headers headers;
    std::string body;
    Params params;

    // 响应处理回调函数
    // using ResponseHandler = std::function<bool(const Response&)> ;
    ResponseHandler response_handler;
    // 内容接收回调函数
    // using ContentReceiverWithProgress= function<bool(const char* data, size_t len,
    // uint64_t offset, uint64_t total)>
    ContentReceiverWithProgress content_receiver;
    }
    }

    • method 表示 HTTP 请求方法(如 GET、POST、PUT 等),类型为 std::string。

    • path 存储请求的目标路径(例如 /api/resource),类型为 std::string。

    • headers 类型为 Headers(通常为 std::unordered_map<std::string, std::string>),存储 HTTP 请求头键值对。

    • body 存储请求体数据(如 POST 请求的 JSON字符串 或表单数据),类型为 std::string。

    • params 类型为 Params(可能为 std::unordered_map<std::string, std::string>),用于存储 URL 查询参数(如 ?key=value 解析后的键值对)。


    在这里插入图片描述

    普通HTTP响应体中,一个完整响应 包含一个响应头 和 一个响应体(响应数据), 在HTTP流式返回响应体中,一个完整响应 包含一个响应头 和 多个响应块(chunk)。在流式返回时,会先返回响应头,然后在逐个返回各个响应体。因此在发送流式响应时,需要在请求参数中告知HTTP服务器,响应头 和 chunk该如何处理。

    流式响应专用:

    response_handler 为响应处理回调函数(对响应头进行处理,只调用一次),实际类型为 std::function<bool(const Response&)> ,如果发起请求时设置该函数,当客户端收到完整的HTTP响应头后,会调用该函数,并传入构造好的Response对象。

    content_recevier 为内容接收回调函数(对 chunk 进行处理,每个 chunk 调用一次),是处理流式处理响应的关键,类型为:function<bool(const char* data, size_t len, uint64_t offset, uint64_t total)>

    • data: 指向当前接收到的数据块的指针
    • len: 当前数据块的长度
    • offset: 当前数据块在请求体中的偏移量
    • total: 请求体的总长度
    • 返回值: true表示继续接收数据,false表示停止接收数据

    设置该回调函数后,客户端不会等待整个响应体传输完再存到 response.body中,而是每收到一小块数据(chunk)就立刻调用该回调函数,处理实时数据


    (1)使用场景

    该结构通常用于 HTTP 服务器端接收请求 或 客户端构造请求,通过填充成员变量实现请求数据的传递和处理。

    (2)注意事项

    • 确保 method 和 path 符合 HTTP 协议规范。
    • headers 和 params 需注意键的大小写敏感性(根据 RFC 标准建议用小写)

    2. httplib::Response

    2.1 基本结构

    namespace httplib {
    struct Response {
    std::string version;
    int status = 1;
    std::string reason;
    Headers headers;
    std::string body;

    // 设置响应体内容和类型
    void set_content(const std::string &s,
    const std::string &content_type);
    // 添加或修改响应头
    void set_header(const std::string &key,
    const std::string &val);

    // set_chunked_content_provider 是 httplib::Response 提供的 流式响应发送接口 ,
    // 用于 分块传输(Chunked Transfer Encoding) 场景。
    void set_chunked_content_provider(const std::string& content_type,
    std::function<bool(size_t offset, DataSink& sink)> provide);
    }
    }

    • version HTTP协议版本(如HTTP/1.1)

    • status HTTP状态码(如200表示成功)

    • reason 状态码的文本描述(如OK)

    • headers HTTP响应头集合(类型为Headers,通常是键值对容器)

    • body 响应体内容(如HTML 或 JSON数据)

    2.2 httplib::Response::set_content 详解(普通响应)

    (1) 接口签名

    void set_content(const std::string& content, const std::string& content_type);

    参数类型说明
    content std::string 响应体内容(一次性全部提供)
    content_type std::string MIME 类型,如 "text/plain"、"application/json"

    • 示例1:简单文本响应

    场景:一个简单的问候接口,返回纯文本。

    #include <httplib.h>

    int main() {
    httplib::Server server;

    server.Get("/hello", [](const httplib::Request& req, httplib::Response& res) {
    res.set_content("你好,世界!", "text/plain");
    });

    server.listen("0.0.0.0", 8080);
    return 0;
    }

    客户端请求:

    curl http://localhost:8080/hello

    HTTP 响应:

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Content-Length: 18

    你好,世界!

    关键点:set_content 会自动计算并设置 Content-Length 头,接收方知道何时读完响应体。


    • 示例2:JSON 响应

    场景:一个用户信息查询接口,返回 JSON 格式数据。

    #include <httplib.h>
    #include <jsoncpp/json/json.h>

    // 构建 JSON 字符串的工具函数
    std::string buildUserJson(const std::string& name, int age, const std::string& email) {
    Json::Value root;
    root["name"] = name;
    root["age"] = age;
    root["email"] = email;

    Json::StreamWriterBuilder builder;
    return Json::writeString(builder, root);
    }

    int main() {
    httplib::Server server;

    server.Get("/user/zhangsan", [](const httplib::Request& req, httplib::Response& res) {
    std::string json = buildUserJson("张三", 25, "zhangsan@example.com");
    res.set_content(json, "application/json");
    });

    server.Get("/user/lisi", [](const httplib::Request& req, httplib::Response& res) {
    std::string json = buildUserJson("李四", 30, "lisi@example.com");
    res.set_content(json, "application/json");
    });

    server.listen("0.0.0.0", 8080);
    return 0;
    }

    HTTP 响应:

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 52

    {"name":"张三","age":25,"email":"zhangsan@example.com"}


    • 示例3:带状态码和自定义头的响应

    场景:模拟一个登录接口,成功返回 200,失败返回 401。

    #include <httplib.h>
    #include <jsoncpp/json/json.h>

    std::string makeJson(const std::string& key, const std::string& value) {
    Json::Value root;
    root[key] = value;
    return Json::writeString(Json::StreamWriterBuilder(), root);
    }

    int main() {
    httplib::Server server;

    server.Post("/login", [](const httplib::Request& req, httplib::Response& res) {
    // 解析请求体
    std::string body = req.body;
    // 假设 body = {"username": "admin", "password": "123456"}

    // 模拟验证
    bool loginSuccess = (body.find("\\"admin\\"") != std::string::npos);

    if (loginSuccess) {
    // 设置自定义头
    res.set_header("X-Request-Id", "abc-123-def");
    res.set_header("X-Server-Version", "1.0.0");

    // 设置状态码和响应体
    res.status = 200;
    res.set_content(makeJson("token", "eyJhbGciOiJIUzI1NiJ9…"), "application/json");
    } else {
    res.status = 401;
    res.set_content(makeJson("error", "用户名或密码错误"), "application/json");
    }
    });

    server.listen("0.0.0.0", 8080);
    return 0;
    }

    成功响应:

    HTTP/1.1 200 OK
    Content-Type: application/json
    X-Request-Id: abc-123-def
    X-Server-Version: 1.0.0
    Content-Length: 42

    {"token":"eyJhbGciOiJIUzI1NiJ9…"}

    失败响应:

    HTTP/1.1 401 Unauthorized
    Content-Type: application/json
    Content-Length: 36

    {"error":"用户名或密码错误"}


    (2) set_content 底层机制

    ┌─────────────────────────────────────────────────────────────────┐
    │ set_content 内部执行流程 │
    ├─────────────────────────────────────────────────────────────────┤
    │ │
    │ res.set_content("Hello", "text/plain") │
    │ │ │
    │ ├── ① 设置 Content-Type → "text/plain" │
    │ │ │
    │ ├── ② 设置响应体字符串 → body = "Hello" │
    │ │ │
    │ ├── ③ 自动计算 Content-Length → 5 │
    │ │ │
    │ └── ④ 标记响应体完整,一次性发送 │
    │ │
    │ 处理函数返回后: │
    │ │ │
    │ └── httplib 框架发送完整 HTTP 响应 │
    │ HTTP/1.1 200 OK │
    │ Content-Type: text/plain │
    │ Content-Length: 5 │
    │ │
    │ Hello │
    │ │
    └─────────────────────────────────────────────────────────────────┘


    (3) set_content 与 set_chunked_content_provider 的对比

    对比维度set_content()set_chunked_content_provider()
    响应方式 一次性发送完整响应体 分块逐步发送
    Content-Length 自动计算并设置 不设置(使用 Transfer-Encoding: chunked)
    内存占用 需完整构建响应体 只需当前块在内存中
    响应延迟 需等待全部内容生成 可立即发送首块
    调用复杂度 简单,一行代码 复杂,需注册回调函数
    适用场景 小数据、静态内容、普通 API 流式数据、大数据、实时推送

    代码对比:

    // 方式1:set_content(一次性)
    server.Get("/api/data", [](auto& req, auto& res) {
    std::string data = getAllData(); // 一次获取全部数据
    res.set_content(data, "application/json"); // 一次发送
    });

    // 方式2:set_chunked_content_provider(逐步)
    server.Get("/api/stream", [](auto& req, auto& res) {
    res.set_chunked_content_provider("application/json",
    [](size_t offset, auto& sink) -> bool {
    // 逐步生成并发送数据
    for (int i = 0; i < 100; i++) {
    std::string chunk = getNextChunk();
    sink.write(chunk.c_str(), chunk.size());
    }
    sink.done();
    return false;
    });
    });


    (4) 常用 MIME 类型速查

    content_type用途
    "text/plain" 纯文本
    "text/html" HTML 页面
    "application/json" JSON 数据
    "application/xml" XML 数据
    "text/css" CSS 样式
    "application/javascript" JavaScript 脚本
    "image/png" PNG 图片
    "image/jpeg" JPEG 图片
    "application/octet-stream" 二进制文件下载
    "text/event-stream" SSE 流(通常用 chunked 方式)

    (5) 总结

    要点说明
    本质 一次性设置完整的响应体,框架自动处理 Content-Length
    调用时机 在处理函数内,调用 set_content 后函数返回
    状态码 默认 200,可通过 res.status 覆盖
    自定义头 通过 res.set_header() 在 set_content 前后均可设置
    适用场景 所有普通 API 响应、静态页面、JSON 数据返回
    限制 不能用于流式场景,响应体必须一次全部生成

    2.3 httplib::Response::set_chunked_content_provider 详解(流式响应函数)

    (1) 接口签名

    void set_chunked_content_provider(
    const std::string& content_type,
    std::function<bool(size_t offset, DataSink& sink)> provider
    );


    • 示例1:实时进度条推送

    场景:模拟一个文件处理任务,服务器每处理 20% 就向客户端推送一次进度,最终推送完成通知。

    #include <httplib.h>
    #include <thread>
    #include <chrono>

    int main() {
    httplib::Server server;

    server.Get("/progress", [](const httplib::Request& req, httplib::Response& res) {
    // 设置响应头
    res.set_header("Cache-Control", "no-cache");
    res.set_header("Connection", "keep-alive");
    res.set_header("X-Accel-Buffering", "no"); // 禁用 nginx 缓冲

    // 注册分块内容提供者
    res.set_chunked_content_provider("text/plain",
    [](size_t offset, httplib::DataSink& sink) -> bool {

    // 模拟 5 个步骤,每个步骤耗时 1 秒
    for (int i = 1; i <= 5; i++) {
    // 模拟耗时操作
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 构建进度消息
    int percent = i * 20;
    std::string msg = "进度: " + std::to_string(percent) + "%\\n";

    // 发送给客户端
    sink.write(msg.c_str(), msg.size());
    }

    // 发送完成消息
    std::string done = "处理完成!\\n";
    sink.write(done.c_str(), done.size());
    sink.done(); // 标记流结束

    return false; // 不再提供更多数据
    });
    });

    server.listen("0.0.0.0", 8080);
    return 0;
    }

    客户端收到的内容(实时逐行显示):

    进度: 20%
    进度: 40%
    进度: 60%
    进度: 80%
    进度: 100%
    处理完成!

    时间线:

    第1秒: 客户端收到 "进度: 20%"
    第2秒: 客户端收到 "进度: 40%"
    第3秒: 客户端收到 "进度: 60%"
    第4秒: 客户端收到 "进度: 80%"
    第5秒: 客户端收到 "进度: 100%"
    第5秒: 客户端收到 "处理完成!" + 连接关闭


    • 示例2:大文件分块下载

    场景:一个 100MB 的大文件,按每块 1MB 分块发送,避免服务端内存爆炸。

    #include <httplib.h>
    #include <fstream>
    #include <vector>

    const size_t CHUNK_SIZE = 1024 * 1024; // 每块 1MB

    int main() {
    httplib::Server server;

    server.Get("/download/large_file", [](const httplib::Request& req, httplib::Response& res) {
    // 打开文件
    std::ifstream file("/path/to/large_file.bin", std::ios::binary);
    if (!file.is_open()) {
    res.status = 404;
    res.set_content("文件不存在", "text/plain");
    return;
    }

    // 获取文件大小
    file.seekg(0, std::ios::end);
    size_t fileSize = file.tellg();
    file.seekg(0, std::ios::beg);

    res.set_header("Content-Type", "application/octet-stream");

    // 分块发送
    res.set_chunked_content_provider("application/octet-stream",
    [file = std::move(file), fileSize](
    size_t offset, httplib::DataSink& sink) mutable -> bool {

    std::vector<char> buffer(CHUNK_SIZE);

    // 读取文件块
    file.read(buffer.data(), CHUNK_SIZE);
    size_t bytesRead = file.gcount();

    if (bytesRead > 0) {
    // 发送当前块
    sink.write(buffer.data(), bytesRead);
    }

    // 如果读取完毕,关闭流
    if (bytesRead < CHUNK_SIZE) {
    sink.done();
    return false;
    }

    return false; // 注意:框架可能只调用一次回调
    });
    });

    server.listen("0.0.0.0", 8080);
    return 0;
    }

    流程对比:

    传统方式(内存爆炸):
    读取 100MB 文件 → 全部放入内存 → 一次性发送 → 内存占用 100MB+

    分块方式(内存友好):
    读取 1MB → 发送 1MB → 释放内存 → 读取下一个 1MB → …
    内存占用始终 ≤ 1MB


    (2) 回调函数 offset 参数的用法

    offset 表示当前已发送的总字节数,可用于实现断点续传或进度计算。

    server.Get("/stream", [](const httplib::Request& req, httplib::Response& res) {
    res.set_chunked_content_provider("text/plain",
    [](size_t offset, httplib::DataSink& sink) -> bool {

    // 第一次调用 offset = 0
    // 发送 10 字节
    std::string chunk1 = "0123456789"; // 10 字节
    sink.write(chunk1.c_str(), chunk1.size());
    // 此时 offset 变为 10

    return false; // 仅调用一次
    });
    });

    多次调用回调的场景(返回 true 继续调用):

    server.Get("/multi", [](const httplib::Request& req, httplib::Response& res) {
    res.set_chunked_content_provider("text/plain",
    [counter = 0](size_t offset, httplib::DataSink& sink) mutable -> bool {

    counter++;
    std::string msg = "第" + std::to_string(counter)
    + "次调用, offset=" + std::to_string(offset) + "\\n";
    sink.write(msg.c_str(), msg.size());

    if (counter >= 3) {
    sink.done();
    return false; // 停止调用
    }
    return true; // 继续调用
    });
    });

    客户端收到:

    第1次调用, offset=0
    第2次调用, offset=25
    第3次调用, offset=50


    (3) DataSink 完整接口

    方法签名说明
    write bool write(const char* data, size_t n) 写入 n 字节数据
    done void done() 标记流结束
    is_writable bool is_writable() 检查客户端是否还连接着

    is_writable() 的使用场景:检测客户端是否断开连接。

    res.set_chunked_content_provider("text/plain",
    [](size_t offset, httplib::DataSink& sink) -> bool {

    for (int i = 0; i < 100; i++) {
    // 检查客户端是否还在
    if (!sink.is_writable()) {
    // 客户端已断开,停止发送
    return false;
    }

    // 发送数据
    std::string msg = "数据块 #" + std::to_string(i) + "\\n";
    sink.write(msg.c_str(), msg.size());
    std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    sink.done();
    return false;
    });


    (4) 核心机制图解

    ┌─────────────────────────────────────────────────────────────────┐
    │ set_chunked_content_provider 机制 │
    ├─────────────────────────────────────────────────────────────────┤
    │ │
    │ ① 注册回调 │
    │ res.set_chunked_content_provider(type, callback) │
    │ └── 仅注册,不执行 │
    │ │
    │ ② 处理函数返回 │
    │ └── httplib 框架接管 │
    │ │
    │ ③ 框架发送 HTTP 响应头 │
    │ HTTP/1.1 200 OK │
    │ Content-Type: text/plain │
    │ Transfer-Encoding: chunked ← 关键:分块传输编码 │
    │ │
    │ ④ 框架调用回调函数 │
    │ callback(offset, dataSink) │
    │ │
    │ ⑤ 回调内部通过 dataSink 发送数据 │
    │ dataSink.write(data, len) │
    │ └── 框架自动封装为 HTTP chunk 格式发送 │
    │ │
    │ ⑥ 回调返回 false │
    │ └── 框架调用 dataSink.done() 标志 → 发送结束块,关闭连接 │
    │ │
    └─────────────────────────────────────────────────────────────────┘

    HTTP Chunked 编码格式:

    HTTP/1.1 200 OK
    Content-Type: text/plain
    Transfer-Encoding: chunked

    5\\r\\n ← 块大小(16进制,5=5字节)
    Hello\\r\\n ← 块数据
    7\\r\\n ← 块大小(7字节)
    World!\\r\\n ← 块数据
    0\\r\\n ← 大小为0,表示结束
    \\r\\n


    (5) 与普通响应的对比

    对比维度set_content()set_chunked_content_provider()
    响应体 一次性构建完整内容 逐步生成、逐步发送
    Content-Length 必须设置 不需要(自动使用 chunked 编码)
    内存占用 全部响应内容在内存中 只有当前块在内存中
    延迟 高(等全部生成完) 低(首块即刻发送)
    适用场景 小数据、静态内容 流式数据、大数据、实时推送

    (6) 总结

    要点说明
    本质 将 HTTP 响应头和数据发送分离,响应头先发送,数据体后续分块推送
    核心优势 无需预知 Content-Length,适合流式/实时/大数据场景
    回调返回值 true = 还有数据(框架会再次调用),false = 结束
    必须调用 dataSink.done() 通知客户端流结束
    典型场景 SSE 推送、进度条、日志流、大文件下载、实时数据

    3. httplib::Server

    3.1 基本结构

    namespace httplib {
    class Server {
    using Handler = std::function<void(const Request &, Response &)>;

    Server &Get(const std::string &pattern, Handler handler);

    Server &Post(const std::string &pattern, Handler handler);

    Server &Put(const std::string &pattern, Handler handler);

    Server &Delete(const std::string &pattern, Handler handler);

    bool listen(const std::string &host, int port);

    void set_mount_point(const std::string& mount_point, const std::string& path);
    }
    }

    3.2 httplib::Server::set_mount_point 方法解析(设置 静态文件服务挂载点)

    ChatServer/
    ├── ChatServer.cpp # 服务器实现
    ├── ChatServer.h # 服务器头文件
    ├── main.cpp # 程序入口
    ├── CMakeLists.txt # CMake 构建配置
    └── build/ # 构建输出目录
    ├── AIChatServer # 可执行文件
    ├── www/ # 静态资源目录
    │ ├── index.html
    │ ├── style.css
    │ └── app.js
    └── CMakeFiles/... # CMake 构建中间文件

    set_mount_point 是 httplib 库中用于设置 静态文件服务挂载点 的方法,将指定的 URL 路径映射到本地文件系统目录,实现静态资源(HTML、CSS、JS、图片等)的自动服务。

    AIChatServer 是由 ChatServer.cpp、ChatServer.h 和 main.cpp构建形成的 服务端可执行文件

    • 假设在 ChatServer.cpp 文件下:

    // 创建http服务器实例
    std::unique_ptr<httplib::Server> _chatServer = std::make_unique<httplib::Server>();
    // 将根路径 /(构建http通信时的根路径)挂载到当前目录(可执行文件所在目录;也就是build目录)下的 www 文件夹。
    _chatServer->set_mount_point("/", "./www");

    • 工作机制 (1) 请求匹配 :当收到 HTTP 请求时,httplib 会先检查请求路径是否匹配已注册的挂载点 (2) 文件查找 :若匹配成功,会在对应的本地目录中查找请求的文件 (3) 默认索引 :若请求路径为 / ,httplib 会自动查找 index.html 文件 (4) 路由优先级 :手动注册的路由(如 /api/session )优先级高于静态文件挂载点

    • 访问示例 假设服务器运行在 http://localhost:8080 , 目录结构如下:

    build/ # 构建输出目录
    ├── AIChatServer # 可执行文件
    └── www/ # 静态资源目录
    ├── index.html
    ├── style.css
    └── app.js

    则可通过以下 URL 访问:

    根路径 /(构建http通信时的根路径)挂载到当前目录(可执行文件所在目录;也就是build目录)下的 www 文件夹

    URL映射文件
    http://localhost:8080/ (构建http通信时的根路径;触发默认索引) www/index.html
    http://localhost:8080/index.html www/index.html
    http://localhost:8080/style.css www/style.css
    http://localhost:8080/app.js www/app.js

    3.3 手动注册路由规则(示例演示)

    • ChatServer.h

    #pragma once
    #include <httplib.h>
    #include <memory>
    #include <ai_chat_sdk/ChatSDK.h>

    namespace ai_chat_server{

    // 服务器配置信息
    struct ServerConfig{
    std::string host = "0.0.0.0"; // 服务器绑定ip
    int port = 8080; // 服务器绑定端口
    std::string logLevel = "INFO"; // 日志级别

    // 模型需要的配置信息
    double temperature = 0.7; // 温度参数
    int maxTokens = 1024; // 最大token数

    // API Key
    std::string deepseekAPIKey; // deepseek API Key
    std::string geminiAPIKey; // gemini API Key
    std::string chatGPTAPIKey; // chatGPT API Key

    // Ollama
    std::string ollamaModelName; // Ollama模型名称
    std::string ollamaModelDesc; // Ollama模型描述
    std::string ollamaEndpoint; // Ollama的url地址
    };

    class ChatServer{
    public:
    ChatServer(const ServerConfig& config);

    bool start(); // 启动服务器
    void stop(); // 停止服务器
    bool isRunning()const; // 是否正在运行

    private:
    // 构造响应体 (默认是错误响应体,JSON格式)
    std::string buildResponse(const std::string& message, bool success = false);
    // 处理创建会话请求
    void handleCreateSessionRequest(const httplib::Request& request, httplib::Response& response);
    // 处理删除会话请求
    void handleDeleteSessionRequest(const httplib::Request& request, httplib::Response& response);
    // 处理获取会话列表请求
    void handleGetSessionListsRequest(const httplib::Request& request, httplib::Response& response);
    // 处理发送消息请求-全量返回
    void handleSendMessageRequest(const httplib::Request& request, httplib::Response& response);

    // 设置HTTP路由规则
    void setHttpRoutes();

    private:
    ServerConfig _config; // 服务器配置信息
    std::unique_ptr<httplib::Server> _chatServer = nullptr; // HTTP服务器
    std::shared_ptr<ai_chat_sdk::ChatSDK> _chatSDK = nullptr; // 聊天SDK
    std::atomic<bool> _isRunning = {false}; // 是否正在运行
    };

    } // end ai_chat_server

    • ChatServer.cpp

    #include "ChatServer.h"
    #include <ai_chat_sdk/util/mylog.h>
    #include <jsoncpp/json/json.h>
    #include <thread>

    namespace ai_chat_server{

    ChatServer::ChatServer(const ServerConfig& config)
    : _config(config)
    {
    // 初始化ChatSDK实例 (参数是 数据库名称, 可以不填, 默认是 "ai_chat.db")
    _chatSDK = std::make_shared<ai_chat_sdk::ChatSDK>();

    // 提供所有模型提供者的配置信息
    std::vector<std::shared_ptr<ai_chat_sdk::Config>> config_list;

    config_list.push_back(std::make_shared<ai_chat_sdk::ApiConfig>("deepseek-v4-flash", config.temperature, config.maxTokens, config.deepseekAPIKey,
    "https://api.deepseek.com"));
    config_list.push_back(std::make_shared<ai_chat_sdk::ApiConfig>("gemini-2.5-flash", config.temperature, config.maxTokens, config.geminiAPIKey,
    "https://generativelanguage.googleapis.com"));
    config_list.push_back(std::make_shared<ai_chat_sdk::ApiConfig>("gpt-5.4-mini", config.temperature, config.maxTokens, config.chatGPTAPIKey,
    "https://api.openai.com"));
    config_list.push_back(std::make_shared<ai_chat_sdk::OllamaConfig>(config.ollamaModelName, config.temperature, config.maxTokens, config.ollamaEndpoint,
    config.ollamaModelDesc));

    // ai_chat_sdk::ChatSDK对象 注册并初始化所有模型提供者
    _chatSDK->registerAndInit_AllProviders(config_list);

    // 创建http服务器实例
    _chatServer = std::make_unique<httplib::Server>();
    }

    // 启动服务器
    bool ChatServer::start()
    {
    if(_isRunning.load())
    {
    ERR("ChatServer is running!!!");
    return false;
    }

    // 设置路由规则
    setHttpRoutes();

    // 设置静态资源的路径
    // 前端页面相关的所有文件都放在www目录下 注意:将来前端页面名称命名为index.html
    // 当用户在浏览器中输入:http://ip:port/index.html http://ip:port也能访问index.html页面
    // 在httplib中,默认情况下,如果请求路径中只有ip和端口,httplib默认会使用index.html文件
    _chatServer->set_mount_point("/", "./www");

    // 为了不卡服务器,不卡主线程,服务器在单独的线程中运行
    std::thread serverThread([this](){
    INFO("ChatServer start on {} :{}", _config.host, _config.port);
    // 启动服务器 (开始监听 HTTP 请求)
    _chatServer->listen(_config.host, _config.port);
    // httplib::Server::listen() 是 阻塞的,会一直运行直到调用 httplib::Server::stop() 方法
    INFO("ChatServer end on {} :{}", _config.host, _config.port);
    });

    serverThread.detach(); // 分离线程,将线程资源从主线程中分离出来,避免主线程阻塞
    _isRunning.store(true);
    INFO("ChatServer start success!!!");
    return true;
    }

    // 停止服务器
    void ChatServer::stop()
    {
    if(!_isRunning.load()){
    ERR("ChatServer is not running!!!");
    return;
    }

    // 当你调用 httplib::Server::stop() 方法:
    // 1. 正在运行的 HTTP 服务器会停止接受新连接 – 服务器不再接受新的 HTTP 请求
    // 2. 等待现有连接完成 – 允许当前正在处理的请求完成(优雅关闭)
    // 3. 释放服务器资源 – 清理服务器占用的资源
    if(_chatServer){
    _chatServer->stop();
    // httplib::Server::stop() 方法用于 停止正在运行的 HTTP 服务器
    }

    _isRunning.store(false);
    INFO("ChatServer stop success!!!");
    }

    // 是否正在运行
    bool ChatServer::isRunning()const
    {
    return _isRunning.load();
    }

    // 构造响应体 (默认是错误响应体,JSON格式)
    std::string ChatServer::buildResponse(const std::string& message, bool success)
    {
    Json::Value root;
    root["success"] = success;
    root["message"] = message;

    // 先定义 Json::StreamWriterBuilder工厂类
    Json::StreamWriterBuilder swber;
    // 通过工厂类 Json::StreamWriterBuilder 构建Json::StreamWriter类对象
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());

    // 通过Json::StreamWriter类中的 writer接口进行序列化
    std::stringstream ss;
    swb->write(root, &ss);

    std::string response = ss.str();

    return response;
    }

    // 处理创建会话请求
    void ChatServer::handleCreateSessionRequest(const httplib::Request& request, httplib::Response& response)
    {
    Json::Value val;
    // 从请求体中获取JSON字符串
    std::string json_str = request.body;

    // 先定义 Json::CharReaderBuilder工厂类
    Json::CharReaderBuilder srder;
    std::unique_ptr<Json::CharReader> srd(srder.newCharReader());

    // 通过Json::CharReader类中的 parse接口进行反序列化
    std::string error;
    bool ret = srd->parse(json_str.c_str(), json_str.c_str() + json_str.size(), &val, &error);
    if(!ret)
    {
    // 解析JSON字符串失败
    std::string errorJsonStr = buildResponse("Failed to parse JSON string", false);
    response.set_content(errorJsonStr, "application/json");
    response.status = 400; // 客户端发送的请求有语法错误,服务器无法理解或处理该请求
    return;
    }

    std::string model_name = val["model"].asString();
    if(model_name.empty())
    {
    std::string errorJsonStr = buildResponse("Model name is empty.", false);
    response.set_content(errorJsonStr, "application/json");
    response.status = 400; // 客户端发送的请求不符合规范,服务器无法处理该请求
    return;
    }

    // 创建会话
    std::string session_id = _chatSDK->createSession(model_name);
    if(session_id.empty())
    {
    std::string errorJsonStr = buildResponse("Failed to create session.", false);
    response.set_content(errorJsonStr, "application/json");
    // 可能是模型名字有误,请用户检查
    response.status = 500; // 服务器内部错误,无法创建会话
    return;
    }

    // 构造响应体 (JSON格式)
    Json::Value resp;
    resp["success"] = true;
    resp["message"] = "Session created successfully.";

    Json::Value data;
    data["session_id"] = session_id;
    data["model"] = model_name;
    resp["data"] = data;

    // 先定义 Json::StreamWriterBuilder工厂类
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());

    // 通过Json::StreamWriter类中的 writer接口进行序列化
    std::stringstream ss;
    swb->write(resp, &ss);

    response.set_content(ss.str(), "application/json");
    response.status = 200; // 成功
    }

    // 处理删除会话请求
    void ChatServer::handleDeleteSessionRequest(const httplib::Request& request, httplib::Response& response)
    {
    // 获取会话id,注意:会话id是一个路径参数
    std::string sessionId = request.matches[1];

    // 删除会话
    bool success = _chatSDK->deleteSession(sessionId);
    if(!success)
    {
    std::string errorJsonStr = buildResponse("delete session failed, session not found", false);
    response.set_content(errorJsonStr, "application/json");
    response.status = 404; // 会话不存在
    return;
    }

    std::string successJsonStr = buildResponse("delete session successfully", true);

    response.set_content(successJsonStr, "application/json");
    response.status = 200; // 成功
    }

    // 处理获取会话列表请求
    void ChatServer::handleGetSessionListsRequest(const httplib::Request& request, httplib::Response& response)
    {
    std::vector<std::string> session_list = _chatSDK->getSessionList();
    Json::Value resp;
    resp["success"] = true;
    resp["message"] = "Session list retrieved successfully.";

    for(const auto& session_id : session_list)
    {
    std::shared_ptr<ai_chat_sdk::Session> session = _chatSDK->getSession(session_id);
    session->_messages = _chatSDK->getSessionHistory(session_id);
    Json::Value val;
    val["id"] = session_id;
    val["model"] = session->_model_name;
    val["created_at"] = static_cast<int64_t>(session->_created_at);
    val["updated_at"] = static_cast<int64_t>(session->_updated_at);
    val["message_count"] = session->_messages.size();
    val["first_user_message"] = "";
    if(!session->_messages.empty())
    {
    val["first_user_message"] = session->_messages[0]._content;
    }

    resp["data"].append(val);
    }

    // 先定义 Json::StreamWriterBuilder工厂类
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());

    // 通过Json::StreamWriter类中的 writer接口进行序列化
    std::stringstream ss;
    swb->write(resp, &ss);

    response.set_content(ss.str(), "application/json");
    response.status = 200; // 成功
    }

    // 处理发送消息请求-全量返回
    void ChatServer::handleSendMessageRequest(const httplib::Request& request, httplib::Response& response)
    {
    Json::Value val;
    // 从请求体中获取JSON字符串
    std::string json_str = request.body;

    // 先定义 Json::CharReaderBuilder工厂类
    Json::CharReaderBuilder srder;
    std::unique_ptr<Json::CharReader> srd(srder.newCharReader());

    // 通过Json::CharReader类中的 parse接口进行反序列化
    std::string error;
    bool ret = srd->parse(json_str.c_str(), json_str.c_str() + json_str.size(), &val, &error);
    if(!ret)
    {
    // 解析JSON字符串失败
    std::string errorJsonStr = buildResponse("Failed to parse JSON string", false);
    response.set_content(errorJsonStr, "application/json");
    response.status = 400; // 客户端发送的请求有语法错误,服务器无法理解或处理该请求
    return;
    }

    // 从JSON字符串中提取会话id和消息内容
    std::string sessionId = val["session_id"].asString();
    std::string message = val["message"].asString();

    if(sessionId.empty() || message.empty()){
    std::string errorJsonStr = buildResponse("session_id or message is empty");
    response.status = 400; // 解析请求参数失败
    response.set_content(errorJsonStr, "application/json");
    return;
    }

    // 发送消息
    std::string responseMessage = _chatSDK->sendMessage(sessionId, message);
    if(responseMessage.empty()){
    std::string errorJsonStr = buildResponse("send message failed", false);
    response.set_content(errorJsonStr, "application/json");
    response.status = 500; // 发送消息失败, 服务器内部错误
    return;
    }

    // 解析响应消息
    Json::Value resp;
    resp["success"] = true;
    resp["message"] = "Message sent successfully.";

    Json::Value data;
    data["session_id"] = sessionId;
    resp["response"] = responseMessage;

    resp["data"] = data;

    // 先定义 Json::StreamWriterBuilder工厂类
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());

    // 通过Json::StreamWriter类中的 writer接口进行序列化
    std::stringstream ss;
    swb->write(resp, &ss);

    response.set_content(ss.str(), "application/json");
    response.status = 200; // 成功
    }

    // 设置HTTP路由规则
    void ChatServer::setHttpRoutes()
    {
    // 处理创建会话请求
    _chatServer->Post("/api/session", [this](const httplib::Request& request, httplib::Response& response){
    handleCreateSessionRequest(request, response);
    });

    // 处理删除会话请求
    _chatServer->Delete("/api/session/(.*)", [this](const httplib::Request& request, httplib::Response& response){
    handleDeleteSessionRequest(request, response);
    });

    // 处理获取会话列表请求
    _chatServer->Get("/api/sessions", [this](const httplib::Request& request, httplib::Response& response){
    handleGetSessionListsRequest(request, response);
    });

    // 处理发送消息请求-全量返回
    _chatServer->Post("/api/message", [this](const httplib::Request& request, httplib::Response& response){
    handleSendMessageRequest(request, response);
    });
    }
    } // namespace ai_chat_sdk


    • 路由映射表
    HTTP 方法URL 路径处理函数功能描述
    POST /api/session handleCreateSessionRequest 创建会话
    DELETE /api/session/(.*) handleDeleteSessionRequest 删除指定会话
    GET /api/sessions handleGetSessionListsRequest 获取会话列表
    POST /api/message handleSendMessageRequest 发送消息(全量返回)
    • 路径参数说明

    正则捕获组 (.*) 用于匹配动态路径参数:

    // 删除会话路由
    _chatServer->Delete("/api/session/(.*)", [...]);
    // 请求示例: DELETE /api/session/abc123
    // request.matches[1] = "abc123" (会话ID)


    • httplib 中路由的匹配优先级:

    (1) 手动注册的路由 (如 /api/session )→ 最高优先级 (2) 静态文件挂载点 (如 / 映射到 www )→ 次优先级

    4. httplib::Client

    4.1 基本结构

    namespace httplib {
    class Client {
    public:
    // 允许直接传入 C 风格字符串作为主机地址,避免不必要的 std::string 构造
    explicit Client(const std::string &host, int port);
    // 省略端口参数,默认使用 HTTP(80) 或 HTTPS(443) 标准端口,根据后续是否启用 SSL 自动选择
    explicit Client(const std::string& host);
    // 通过完整 URL 初始化(如 http://example.com:8080),内部解析协议、主机和端口。
    // 需处理以下情况:
    // (1) 自动识别 http/https 协议
    // (2) 提取域名和端口(若未指定则使用协议默认端口)
    explicit Client(const std::string& url);

    // 设置连接超时时间
    // (1) sec 参数:以秒为单位的超时值(整数类型)
    // (2) usec 参数:以微秒为单位的附加超时值(整数类型)
    void set_connection_timeout(time_t sec, time_t usec);
    // 设置读取超时时间
    void set_read_timeout(time_t sec, time_t usec);

    Result Get(const std::string &path, const Headers &headers);

    // 原始 Post接口(保留)
    Result Post(const std::string &path, const std::string &body,
    const std::string &content_type);
    // 带 Headers类型 的 Post接口
    Result Post(const char* path, const Headers& headers,
    const std::string& body, const std::string &content_type);

    Result Put(const std::string &path, const std::string &body,
    const std::string &content_type);

    Result Delete(const std::string &path, const std::string &body,
    const std::string &content_type);

    // send() 是 httplib::Client 提供的 底层请求发送接口 ,相比 Get() 、 Post() 等便捷方法,
    // 它提供了更精细的控制能力, 特别适合流式响应场景 。
    Result send(const Request& req);

    private:
    std::string host_; // 存储目标服务器的地址(如域名 或 IP)。
    int port_; // 服务器端口号,通常为整数类型(如 80 或 443)
    size_t timeout_sec_; // 请求超时时间(秒),通常为无符号整型。
    bool keep_alive_; // 布尔值,指示是否保持长连接(Keep-Alive)
    }
    }

    4.2 通过 httplib::Client::Post接口发送普通请求(httplib::Client::Post 接口详解)

    // 发送消息(非流式响应)
    std::string DeepSeekProvider::sendMessage(const std::vector<Message>& messages,
    const std::map<std::string,std::string>& request_param)
    {
    // 检查模型是否初始化成功
    if(!_isAvailable) {
    ERR("model is not available");
    return "";
    }

    // 获取采样温度 和 max_tokens
    double temperature = 0.7;
    int max_tokens = 2048;
    if(request_param.find("temperature") != request_param.end()) {
    temperature = std::stod(request_param.at("temperature"));
    }
    if(request_param.find("max_tokens") != request_param.end()) {
    max_tokens = std::stoi(request_param.at("max_tokens"));
    }

    // 构建历史消息(含最新请求消息)
    Json::Value messages_array(Json::arrayValue);
    for(const auto& msg : messages) {
    Json::Value msg_obj;
    msg_obj["role"] = msg._role;
    msg_obj["content"] = msg._content;
    messages_array.append(msg_obj);
    }

    // 构建请求体
    Json::Value request_body;
    request_body["model"] = getModelName();
    request_body["messages"] = messages_array;
    request_body["stream"] = false;
    request_body["temperature"] = temperature;
    request_body["max_tokens"] = max_tokens;

    // 序列化请求体 (将Json::Value对象 转换为 字符串格式的JSON字符串)
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());
    std::stringstream ss;
    int write_ret = swb->write(request_body, &ss);
    if(write_ret != 0)
    {
    ERR("Json序列化失败!");
    return "";
    }
    std::string request_body_str = ss.str();
    DBG("request_body_str : {}", request_body_str);

    /////////////////////////////// http相关内容 ///////////////////////////////
    // 创建HTTP客户端
    httplib::Client client(_base_url);
    client.set_connection_timeout(30, 0); // 30秒超时
    client.set_read_timeout(60, 0); // 60秒读取超时

    // 设置请求头
    httplib::Headers headers = {
    {"Authorization", "Bearer " + _api_key},
    {"Content-Type", "application/json"}
    };

    // 发送POST请求
    httplib::Result res = client.Post("/chat/completions", headers,
    request_body_str, "application/json");
    // 网络层失败(连接失败、超时等)
    if(!res) {
    ERR("HTTP Post error: {}", httplib::to_string(res.error()));
    return "";
    }
    // 请求成功但应用层失败(HTTP 状态码非 200)
    if(res->status != 200) {
    ERR("POST请求失败, status code : {}", res->status);
    return "";
    }
    DBG("POST请求成功, response body : {}", res->body);
    ///////////////////////////////////////////////////////////////////////////

    // 解析响应体(字符串格式的JSON字符串)为 Json::Value对象
    Json::CharReaderBuilder srder;
    std::unique_ptr<Json::CharReader> srd(srder.newCharReader());
    Json::Value resp;
    std::string error;
    bool parse_ret = srd->parse(res->body.c_str(),res->body.c_str() + res->body.size(), &resp, &error);
    if(!parse_ret) {
    ERR("Json解析失败, error : {}", error);
    return "";
    }

    // 从解析后的Json::Value对象中提取响应内容
    if(resp.isMember("choices") && resp["choices"].isArray() && !resp["choices"].empty())
    {
    Json::Value choice = resp["choices"][0];
    if(choice.isMember("message") && choice["message"].isObject()) {
    Json::Value message = choice["message"];
    if(message.isMember("content") && message["content"].isString())
    {
    DBG("message content : {}", message["content"].asString());
    return message["content"].asString();
    }
    }
    }

    // 解析失败,返回错误信息
    ERR("Invalid response format from DeepSeek API");
    return "";
    }


    (1) 接口概述

    Post() 是 httplib::Client 提供的便捷 HTTP POST 请求方法,用于向服务器发送 POST 请求并获取响应。相比底层的 send() 接口,它封装了请求构建过程,使用更简洁。


    (2) 常用重载形式

    httplib::Client::Post 提供了多种重载,适配不同场景:

    重载形式返回值适用场景
    Post(path) Result 无请求体的 POST
    Post(path, body, content_type) Result 带原始请求体
    Post(path, params) Result URL 编码表单
    Post(path, headers, body, content_type) Result 自定义头 + 请求体

    (3) 核心签名(项目中使用的形式)

    Result Post(const std::string& path, const Headers& headers,
    const std::string& body, const std::string& content_type);

    参数类型说明
    path std::string 请求路径(如 /chat/completions)
    headers Headers HTTP 请求头集合
    body std::string 请求体内容
    content_type std::string Content-Type 标识
    返回值 Result 响应结果封装

    (4) 在项目中的实际使用

    // 设置请求头
    httplib::Headers headers = {
    {"Authorization", "Bearer " + _api_key},
    {"Content-Type", "application/json"}
    };

    // 发送 POST 请求
    httplib::Result res = client.Post("/chat/completions", headers,
    request_body_str, "application/json");

    调用流程:

    ┌─────────────────────────────────────────────────────────────┐
    │ 1. 构建请求头 (Authorization + Content-Type) │
    ├─────────────────────────────────────────────────────────────┤
    │ 2. 序列化请求体为 JSON 字符串 │
    ├─────────────────────────────────────────────────────────────┤
    │ 3. 调用 Post() 发送请求 │
    │ ├── 建立 TCP 连接 │
    │ ├── 发送 HTTP 请求 │
    │ ├── 接收完整响应 │
    │ └── 返回 Result 对象 │
    ├─────────────────────────────────────────────────────────────┤
    │ 4. 检查响应结果 │
    │ ├── !res → 网络层失败(超时、连接失败等) │
    │ └── res->status != 200 → 应用层失败 │
    └─────────────────────────────────────────────────────────────┘


    (5) 返回值 Result 详解

    auto res = client.Post(...);

    // 1. 布尔判断(网络层状态)
    if(!res) {
    // 网络错误:连接失败、超时、DNS解析失败等
    httplib::Error err = res.error();
    ERR("HTTP error: {}", httplib::to_string(err));
    return "";
    }

    // 2. 访问响应对象(应用层状态)
    if(res->status == 200) {
    // 请求成功
    std::string body = res->body; // 响应体
    std::string header = res->get_header_value("Content-Type"); // 获取响应头
    } else {
    // HTTP 错误
    ERR("status: {}, body: {}", res->status, res->body);
    }

    Result 支持的操作:

    操作说明
    operator bool() 判断网络层是否成功
    error() 获取网络错误码
    ->status HTTP 状态码
    ->body 响应体内容
    ->headers 响应头集合
    ->get_header_value(name) 获取指定响应头

    (6) 请求超时配置

    在调用 Post() 前可配置客户端超时:

    httplib::Client client(_base_url);
    client.set_connection_timeout(30, 0); // 连接超时 30 秒
    client.set_read_timeout(60, 0); // 读取超时 60 秒
    // 注意:流式响应需要更长的 read_timeout(如 300 秒)

    auto res = client.Post("/chat/completions", headers, body, "application/json");


    (7) 完整请求示例

    // 创建客户端
    httplib::Client cli("https://api.deepseek.com");

    // 设置超时
    cli.set_connection_timeout(30, 0);
    cli.set_read_timeout(60, 0);

    // 设置请求头
    httplib::Headers headers = {
    {"Authorization", "Bearer sk-xxx"},
    {"Content-Type", "application/json"}
    };

    // 构建请求体
    std::string body = R"({"model":"deepseek-v4-flash","messages":[{"role":"user","content":"你好"}]})";

    // 发送请求
    auto res = cli.Post("/chat/completions", headers, body, "application/json");

    // 处理响应
    if(res && res->status == 200) {
    std::cout << "Response: " << res->body << std::endl;
    } else {
    std::cerr << "Error: " << (res ? std::to_string(res->status) : httplib::to_string(res.error())) << std::endl;
    }

    4.3 通过 httplib::Client::send接口发送流式响应请求(httplib::Client::send 接口详解)

    • 完整示例:

    // 实现流式响应
    std::string DeepSeekProvider::sendMessageStream(const std::vector<Message>& messages,
    const std::map<std::string,std::string>& request_param,
    std::function<void(const std::string&, bool)> callback)
    {
    // 检查模型是否初始化成功
    if(!_isAvailable) {
    ERR("model is not available");
    return "";
    }

    // 获取采样温度 和 max_tokens
    double temperature = 0.7;
    int max_tokens = 2048;
    if(request_param.find("temperature") != request_param.end()) {
    temperature = std::stod(request_param.at("temperature"));
    }
    if(request_param.find("max_tokens") != request_param.end()) {
    max_tokens = std::stoi(request_param.at("max_tokens"));
    }

    // 构建历史消息(含最新请求消息)
    Json::Value messages_array(Json::arrayValue);
    for(const auto& msg : messages) {
    Json::Value msg_obj;
    msg_obj["role"] = msg._role;
    msg_obj["content"] = msg._content;
    messages_array.append(msg_obj);
    }

    // 构建请求体
    Json::Value request_body;
    request_body["model"] = getModelName();
    request_body["messages"] = messages_array;
    request_body["stream"] = true; // 开启流式响应
    request_body["temperature"] = temperature;
    request_body["max_tokens"] = max_tokens;

    // 序列化请求体(将Json::Value对象 转换为 字符串格式的JSON字符串)
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());
    std::stringstream ss;
    int write_ret = swb->write(request_body, &ss);
    if(write_ret != 0)
    {
    ERR("Json序列化失败!");
    return "";
    }
    std::string request_body_str = ss.str();
    DBG("request_body_str : {}", request_body_str);

    //////////////////////////////////////////// 与非流式响应有区别的内容 ////////////////////////////////////////////
    // 创建HTTP Client
    httplib::Client client(_base_url);
    client.set_connection_timeout(30, 0); // 30秒超时
    client.set_read_timeout(300, 0); // 流式响应需要更⻓的时间

    // 设置请求头
    httplib::Headers headers = {
    {"Authorization", "Bearer " + _api_key},
    {"Content-Type", "application/json"}
    };

    // 流式处理变量
    std::string buffer; // 接收流式响应的数据块
    bool gotError = false; // 响应是否成功(成功false,失败true)
    std::string errorMsg; // 错误描述符
    int statusCode = 0; // 状态码
    bool streamFinish = false; // 标记流式返回数据是否结束
    std::string fullResponse; // 累积完整的响应

    ///////////////////// 构建HTTP请求(设置处理函数) ////////////////////////
    // 创建请求对象
    httplib::Request req;
    req.method = "POST";
    req.path = "/chat/completions";
    req.headers = headers;
    req.body = request_body_str;
    // HTTP协议响应的格式:
    // ⼀个状态行:HTTP/1.1 200 OK
    // ⼀组响应头:Content-Type,Content-Length
    // 空行:\\r\\n\\r\\n
    // 响应体
    // 普通的HTTP响应是 ⼀头⼀体
    // 流式响应:只有⼀个响应头,但响应体被拆分成多个块(chunks)陆续发送,即⼀头多块

    // 响应头处理(检查状态码): 状态码不是200时,记录错误信息
    req.response_handler = [&](const httplib::Response& res) {
    int statusCode = res.status;
    if(200 != statusCode){
    gotError = true;
    errorMsg = "HTTP Error:" + std::to_string(statusCode);
    return false; // 终止请求(不再调用 content_receiver)
    }
    return true; // 继续接收数据(继续调用 content_receiver 接收响应体chunk)
    };

    // 响应体处理(流式回调)
    // data:指向当前接收到的数据块的指针
    // len:当前数据块的长度
    // offset:当前数据块在响应体中的偏移量
    // totalLength:响应体的总长度
    // 流式响应 主要关注前两个参数 data 和 len 就足够了。因为在流式响应的场景下,总长度通常未知,需要持续接收直到 [DONE] 标记出现
    req.content_receiver = [&](const char* data, size_t len, uint64_t offset, uint64_t totalLength)
    {
    // 如果http请求头出错,就不需要再继续接收了
    if(gotError)
    {
    return false;
    }
    // 追加新数据到缓冲区
    buffer.append(data, len);
    DBG("buffer: {}", buffer);
    // 处理所有完整的事件,事件和事件之间以\\n\\n分隔
    size_t pos = 0;
    while((pos = buffer.find("\\n\\n")) != std::string::npos)
    {
    std::string event = buffer.substr(0, pos);
    buffer.erase(0, pos+2); // 移除已经处理的事件
    // 处理空⾏和注释, 以:开头的是注释⾏
    if(event.empty() || event[0] == ':')
    {
    continue;
    }
    // 检查事件类型: data: JSON字符串
    if(event.compare(0, 6, "data: ") == 0)
    {
    std::string jsonStr = event.substr(6);
    ////// 关键代码:处理结束标记(判断流式响应是否结束) ///////
    if(jsonStr == "[DONE]")
    {
    callback("", true); // 返回true, 代表流式响应结束
    streamFinish = true; // 标记流式响应结束
    return true;
    }

    // 反序列化:将JSON字符串 转换为 Json::Value对象
    Json::Value chunk;
    Json::CharReaderBuilder srder;
    std::unique_ptr<Json::CharReader> srd(srder.newCharReader());
    std::string errs;
    bool read_ret = srd->parse(jsonStr.c_str(), jsonStr.c_str() + jsonStr.size(), &chunk, &errs);
    if(!read_ret)
    {
    ERR("Json反序列化失败: {}", errs);
    return false;
    }

    // 提取Json::Value对象中的 content字段
    if(chunk.isMember("choices") && chunk["choices"].isArray() &&
    !chunk["choices"].empty() && chunk["choices"][0].isMember("delta") &&
    chunk["choices"][0]["delta"].isMember("content"))
    {
    std::string content = chunk["choices"][0]["delta"]["content"].asString();
    // 累积到完整响应
    fullResponse += content;
    callback(content, false); // 调用回调函数处理增量内容, false代表流式响应未结束
    }
    }
    }
    return true; // 继续接收数据
    };
    //////////////////////// HTTP请求构建结束 /////////////////////////

    // 发送HTTP请求并处理结果
    auto res = client.send(req);
    // send的返回值类型是Result类型,Result类型内部实现了operator bool(), 允许将Result类型的实例隐式转换为bool类型
    if(!res)
    {
    // 请求连接失败,⽹络问题、DNS解析失败等
    ERR("HTTP Post error: {}", httplib::to_string(res.error()));
    return "";
    }

    // 确保流正常结束
    if(!streamFinish){
    WARN("Stream ended without [DONE] marker");
    callback("", true);
    }
    return fullResponse;
    }


    (1) 接口概述

    send() 是 httplib::Client 提供的底层请求发送接口,相比 Get()、Post() 等便捷方法,它提供了更精细的控制能力,特别适合流式响应场景。


    (2) 签名与参数

    Result send(const Request& req);

    参数类型说明
    req httplib::Request 完整的请求对象,包含方法、路径、头、体及回调函数
    返回值 httplib::Result 请求结果封装,支持布尔判断和错误访问

    (3) 核心优势:回调机制

    send() 接口的关键在于通过 Request 对象设置响应处理回调函数,实现流式数据处理:

    // 创建HTTP Client
    httplib::Client client(_base_url);
    client.set_connection_timeout(30, 0); // 30秒超时
    client.set_read_timeout(300, 0); // 流式响应需要更⻓的时间

    // 设置请求头
    httplib::Headers headers = {
    {"Authorization", "Bearer " + _api_key},
    {"Content-Type", "application/json"}
    };

    httplib::Request req;
    req.method = "POST";
    req.path = "/chat/completions";
    req.headers = headers;
    req.body = request_body_str;

    // 1. 响应头回调 – 检查状态码
    req.response_handler = [&](const httplib::Response& res) {
    if(res.status != 200) {
    gotError = true;
    return false; // 终止接收
    }
    return true; // 继续接收响应体
    };

    // 2. 响应体回调 – 流式接收数据块
    req.content_receiver = [&](const char* data, size_t len, uint64_t offset, uint64_t totalLength) {
    buffer.append(data, len); // 累积数据
    // 处理 SSE 格式…
    return true; // 继续接收
    };

    auto res = client.send(req);

    回调函数签名对比:

    回调签名作用
    response_handler bool(const Response&) 处理响应头,决定是否继续接收
    content_receiver bool(const char*, size_t, uint64_t, uint64_t) 处理响应体数据块

    (4) 返回值处理

    auto res = client.send(req);

    // 方式1:布尔判断(网络层成功/失败)
    if(!res) {
    ERR("HTTP error: {}", httplib::to_string(res.error()));
    return "";
    }

    // 方式2:访问响应对象(应用层状态)
    if(res->status != 200) {
    ERR("status code: {}", res->status);
    }

    Result 类型的特性:

    • 内部实现了 operator bool(),支持隐式转换为布尔值
    • 网络层失败(连接超时、DNS 解析失败等)→ res 为 false
    • 网络层成功 → res 为 true,可通过 res->status 访问 HTTP 状态码

    4.4 httplib::Client::Post接口 和 httplib::Client::send接口的对比

    • 核心区别速览
    维度Post()send()
    定位 便捷高层 API 底层通用接口
    使用复杂度 简单,一行调用 复杂,需手动构建 Request
    响应接收方式 一次性完整接收 支持流式增量接收
    回调机制 不支持 支持 response_handler + content_receiver
    HTTP 方法 固定 POST 可自定义(GET/POST/PUT/DELETE 等)
    适用场景 普通 API 请求、表单提交 SSE 流式响应、大文件下载、自定义协议
    代码量

    (1) 使用方式对比

    Post() – 简洁调用

    // 项目中的使用 (DeepSeekProvider.cpp:109-110)
    httplib::Headers headers = {
    {"Authorization", "Bearer " + _api_key},
    {"Content-Type", "application/json"}
    };
    auto res = client.Post("/chat/completions", headers,
    request_body_str, "application/json");

    send() – 底层控制

    // 项目中的使用 (DeepSeekProvider.cpp:228-320)
    httplib::Request req;
    req.method = "POST";
    req.path = "/chat/completions";
    req.headers = headers;
    req.body = request_body_str;

    // 设置响应头回调
    req.response_handler = [&](const httplib::Response& res) {
    return (res.status == 200);
    };

    // 设置响应体流式回调
    req.content_receiver = [&](const char* data, size_t len, uint64_t, uint64_t) {
    buffer.append(data, len);
    // 实时处理数据块…
    return true;
    };

    auto res = client.send(req);


    (2) 响应处理机制对比

    Post() – 同步阻塞,完整接收

    请求发送 ──→ 等待完整响应 ──→ 返回完整响应体
    (阻塞等待)

    auto res = client.Post(...);
    // 此时 res->body 已经包含完整的响应体
    if(res && res->status == 200) {
    std::string full_response = res->body; // 一次性获取全部数据
    }

    send() – 流式回调,增量处理

    请求发送 ──→ 响应头到达 ──→ response_handler 回调

    ├──→ 数据块1到达 ──→ content_receiver 回调
    ├──→ 数据块2到达 ──→ content_receiver 回调
    └──→ …更多数据块…

    req.content_receiver = [&](const char* data, size_t len, ...) {
    // 实时处理每个数据块
    process_chunk(data, len);
    return true; // 返回 false 可中断接收
    };


    (3) 回调函数详解

    send() 独有的回调机制:

    回调函数触发时机作用返回值意义
    response_handler 响应头完全接收后 检查状态码、响应头 true=继续接收;false=终止
    content_receiver 每个响应体数据块到达时 处理流式数据 true=继续接收;false=终止

    回调参数详解:

    // response_handler 参数
    req.response_handler = [&](const httplib::Response& res) {
    int status = res.status; // HTTP 状态码
    auto headers = res.headers; // 响应头集合
    return (status == 200); // 决定是否继续
    };

    // content_receiver 参数
    req.content_receiver = [&](const char* data, // 数据块指针
    size_t len, // 数据块长度
    uint64_t offset, // 当前偏移量
    uint64_t totalLength) // 总长度(流式可能为 0)
    {
    buffer.append(data, len);
    return true;
    };


    (4) 适用场景对比

    选择 Post() 的场景:

    • 普通 REST API 调用
    • 表单提交
    • 响应体较小,可一次性接收
    • 代码简洁性优先

    选择 send() 的场景:

    • SSE(Server-Sent Events)流式响应
    • 大文件下载(边接收边写入)
    • 需要实时处理响应数据
    • 自定义协议实现
    • 需要精确控制请求过程

    (5) 在项目中的实际应用

    项目使用 Post()(非流式请求):

    // DeepSeekProvider.cpp:109-121
    auto res = client.Post("/chat/completions", headers,
    request_body_str, "application/json");
    if(!res) {
    ERR("HTTP Post error: {}", httplib::to_string(res.error()));
    return "";
    }
    if(res->status != 200) {
    ERR("POST请求失败, status code : {}", res->status);
    return "";
    }
    // 直接访问完整响应体
    std::string full_response = res->body;

    项目使用 send()(流式请求):

    // DeepSeekProvider.cpp:228-334
    req.content_receiver = [&](const char* data, size_t len, ...) {
    buffer.append(data, len);
    // 解析 SSE 格式数据
    while((pos = buffer.find("\\n\\n")) != std::string::npos) {
    std::string event = buffer.substr(0, pos);
    // 处理每个 SSE 事件…
    if(event.compare(0, 6, "data: ") == 0) {
    std::string jsonStr = event.substr(6);
    if(jsonStr == "[DONE]") {
    callback("", true); // 通知流结束
    return true;
    }
    // 解析增量内容并回调
    std::string content = parse_content(jsonStr);
    callback(content, false); // 实时推送
    }
    }
    return true;
    };

    auto res = client.send(req);


    (6) 性能与资源对比

    维度Post()send()
    内存占用 高(需完整缓冲响应体) 低(可边接收边处理)
    响应延迟 高(需等待完整响应) 低(可实时处理首块数据)
    适用数据量 小到中等 任意大小(支持流式)

    (7) 错误处理对比

    Post() 错误处理:

    auto res = client.Post(...);
    if(!res) {
    // 网络层错误:超时、连接失败、DNS 失败等
    httplib::Error err = res.error();
    switch(err) {
    case httplib::Error::Connection: ...
    case httplib::Error::Timeout: ...
    }
    } else if(res->status != 200) {
    // 应用层错误:HTTP 状态码非 200
    }

    send() 错误处理:

    // 方式1:通过 response_handler 提前检测
    req.response_handler = [&](const httplib::Response& res) {
    if(res.status != 200) {
    gotError = true;
    return false; // 终止接收
    }
    return true;
    };

    // 方式2:通过 content_receiver 返回值中断
    req.content_receiver = [&](const char*, size_t, ...) {
    if(gotError) return false; // 提前终止
    // 处理数据…
    return true;
    };

    auto res = client.send(req);
    if(!res) {
    // 网络层错误
    }


    (8) 何时选择哪个接口?

    ┌─────────────────────────────────┐
    │ HTTP 请求需求分析 │
    └──────────────────┬──────────────┘

    ┌──────────────────┴──────────────────┐
    │ │
    是否需要流式响应? 普通请求?
    │ │
    ┌───────┴───────┐ ┌─────────┴─────────┐
    │ │ │ │
    需要 不需要 简单请求 复杂需求
    │ │ │ │
    ▼ ▼ ▼ ▼
    send() Post() Post() send()
    │ │ │ │
    SSE/大文件下载 一次性接收 表单/JSON 自定义协议


    • 总结
    特性Post()send()
    易用性 ★★★★★ ★★★
    灵活性 ★★★ ★★★★★
    流式支持 不支持 支持
    代码量
    推荐场景 普通 API 调用 SSE/流式/自定义场景

    核心建议:优先使用 Post() 处理常规请求;当需要流式响应或精细控制时,使用 send()。

    5. httplib::Result 与 httplib::Response的关系

    httplib::Result 是 httplib::Response的包装类

    class Result {
    private:
    // HasResponse: 连接成功
    // HasError:网络层失败,未连接上应用层
    enum { HasResponse, HasError } state;
    // 记录错误信息
    Error error;

    // httplib::Result中包含 httplib::Response对象
    Response response;

    public:
    operator bool() const { return state == HasResponse; }

    Response* operator->() { return &response; }
    Response& operator*() { return response; }

    Error error() const { return error; }
    };

    • 使用场景(判断 HTTP 请求可能失败的两种情况):

    httplib::Result res = client.Post(...);
    // 场景 1:网络层失败(连接失败、超时等)
    if(!res) { // Result 本身是无效的
    ERR("HTTP Post error: {}", httplib::to_string(res.error()));
    return "";
    }

    // 场景 2:请求成功但应用层失败(HTTP 状态码非 200)
    if(res->status != 200) { // Result 有效,但 Response 的状态码不对
    ERR("POST请求失败, status code : {}", res->status);
    return "";
    }

    DBG("POST请求成功, response body : {}", res->body);

    6. Client → Server 完整请求流程(普通响应)

    以项目中的创建会话请求为例,完整展示从 httplib::Client 发送请求到 httplib::Server 处理响应的全过程。


    (1) 流程总览

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │ Client 请求发起阶段 │
    ├─────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ [1] 构建请求体 ──→ [2] 设置请求头 ──→ [3] 调用 Post() ──→ [4] TCP 连接 │
    │ │
    └──────────────────────────────┬──────────────────────────────────────────────┘
    │ TCP/IP 网络传输

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │ Server 请求处理阶段 │
    ├─────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ [5] 接收连接 ──→ [6] 解析 HTTP ──→ [7] 路由匹配 ──→ [8] 执行处理函数 │
    │ │ │ │
    │ │ ▼ │
    │ │ [9] 构建响应体 │
    │ │ │ │
    │ └────────────────────────────────────────────┼────────────────────────┘
    │ ▼ │
    │ [10] 发送响应 │
    └──────────────────────────────┬──────────────────────────────────────────────┘
    │ TCP/IP 网络传输

    ┌─────────────────────────────────────────────────────────────────────────────┐
    │ Client 响应接收阶段 │
    ├─────────────────────────────────────────────────────────────────────────────┤
    │ │
    │ [11] 接收响应 ──→ [12] 解析状态码 ──→ [13] 处理响应体 │
    │ │
    └─────────────────────────────────────────────────────────────────────────────┘


    (2) 详细流程分解

    • 阶段一:Client 端准备与发送

    步骤 1-4:Client 发起请求

    // 假设有一个客户端调用(例如前端页面或其他服务)
    httplib::Client client("http://localhost:8080");
    client.set_connection_timeout(30, 0);
    client.set_read_timeout(60, 0);

    // [1] 构建请求体(JSON格式)
    std::string request_body = R"({"model": "deepseek-v4-flash"})";

    // [2] 设置请求头
    httplib::Headers headers = {
    {"Content-Type", "application/json"}
    };

    // [3] 调用 Post() 发送请求
    // [4] 底层建立 TCP 连接,发送 HTTP 请求
    auto res = client.Post("/api/session", headers, request_body, "application/json");

    发送的 HTTP 请求内容:

    POST /api/session HTTP/1.1
    Host: localhost:8080
    Content-Type: application/json
    Content-Length: 39

    {"model": "deepseek-v4-flash"}


    • 阶段二:Server 端接收与处理

    步骤 5:Server 接收连接

    // ChatServer.cpp:57-63 – 服务器在独立线程中运行
    std::thread serverThread([this](){
    // [5] listen() 阻塞等待并接收客户端连接
    _chatServer->listen(_config.host, _config.port);
    });

    步骤 6:解析 HTTP 请求

    httplib::Server 内部自动完成:

    • 解析 HTTP 请求行(POST /api/session HTTP/1.1)
    • 解析请求头(Content-Type, Content-Length 等)
    • 读取请求体({"model": "deepseek-v4-flash"})
    • 构建 httplib::Request 对象

    步骤 7:路由匹配

    // ChatServer.cpp:457-493 – 路由注册
    void ChatServer::setHttpRoutes() {
    // 注册路由表
    _chatServer->Post("/api/session", [this](const httplib::Request& request, httplib::Response& response){
    handleCreateSessionRequest(request, response); // 匹配后执行此回调
    });
    // … 其他路由
    }

    路由匹配规则:

  • 精确匹配优先:/api/session 精确匹配
  • 正则匹配其次:/api/session/(.*) 使用正则捕获路径参数
  • 静态文件最后:set_mount_point("/", "./www") 处理其他请求
  • 步骤 8:执行处理函数

    void ChatServer::handleCreateSessionRequest(const httplib::Request& request, httplib::Response& response)
    {
    // 解析请求体
    Json::Value val;
    std::string json_str = request.body; // 获取请求体内容

    // 反序列化 JSON
    Json::CharReaderBuilder srder;
    std::unique_ptr<Json::CharReader> srd(srder.newCharReader());
    bool ret = srd->parse(json_str.c_str(), json_str.c_str() + json_str.size(), &val, &error);

    // 提取参数
    std::string model_name = val["model"].asString();

    // 调用 SDK 创建会话
    std::string session_id = _chatSDK->createSession(model_name);

    // … 错误处理
    }

    步骤 9:构建响应体(处理函数中的内容)

    // 构造成功响应
    Json::Value resp;
    resp["success"] = true;
    resp["message"] = "Session created successfully.";

    Json::Value data;
    data["session_id"] = session_id;
    data["model"] = model_name;
    resp["data"] = data;

    // 序列化为 JSON 字符串
    Json::StreamWriterBuilder swber;
    std::unique_ptr<Json::StreamWriter> swb(swber.newStreamWriter());
    std::stringstream ss;
    swb->write(resp, &ss);

    // 设置响应内容
    response.set_content(ss.str(), "application/json");
    response.status = 200;

    步骤 10:发送响应

    httplib::Server 自动完成:

    • 设置响应头(Content-Type, Content-Length)
    • 发送状态行(HTTP/1.1 200 OK)
    • 发送响应体

    发送的 HTTP 响应内容:

    HTTP/1.1 200 OK
    Content-Type: application/json
    Content-Length: 89

    {"success":true,"message":"Session created successfully.","data":{"session_id":"abc123","model":"deepseek-v4-flash"}}


    • 阶段三:Client 端接收与处理

    步骤 11-13:Client 处理响应

    // 客户端调用代码
    auto res = client.Post("/api/session", headers, request_body, "application/json");

    // [11] 接收响应
    // [12] 解析状态码
    if(!res) {
    // 网络层失败:连接超时、DNS 失败等
    std::cerr << "HTTP error: " << httplib::to_string(res.error()) << std::endl;
    } else if(res->status == 200) {
    // [13] 处理响应体
    std::string response_body = res->body;
    // 解析 JSON 获取 session_id
    // {"success":true,"message":"Session created successfully.","data":{"session_id":"abc123","model":"deepseek-v4-flash"}}
    } else {
    // 应用层失败:HTTP 状态码非 200
    std::cerr << "Error: " << res->status << std::endl;
    }


    (3) 关键数据结构传递

    Client 端 Server 端
    ───────── ─────────
    httplib::Request httplib::Request
    ├── method = "POST" ←┼→ ├── method = "POST"
    ├── path = "/api/session" ←┼→ ├── path = "/api/session"
    ├── headers = {…} ←┼→ ├── headers = {…}
    └── body = "{…}" ←┼→ └── body = "{…}"

    httplib::Response httplib::Response
    ├── status = 200 ←─┼← ├── status = 200
    ├── headers = {…} ←─┼← ├── headers = {…}
    └── body = "{…}" ←─┼← └── body = "{…}"


    (4) 总结

    阶段组件核心操作
    请求准备 Client 构建请求体、设置请求头
    请求发送 Client 建立 TCP 连接、发送 HTTP 请求
    请求接收 Server listen() 阻塞等待、accept() 接收连接
    请求解析 Server 解析 HTTP 协议、构建 Request 对象
    路由匹配 Server 根据方法+路径查找处理函数
    业务处理 Server 执行处理函数、调用 SDK
    响应构建 Server 序列化响应体、设置状态码
    响应发送 Server 发送 HTTP 响应、关闭连接
    响应处理 Client 检查状态码、解析响应体

    核心机制:httplib 通过回调函数实现路由分发,将 HTTP 请求转换为业务逻辑调用,实现了网络层与业务层的解耦。

    7. Client → Server 完整请求流程(流式响应)

    示例场景

    服务器:模拟一个天气预测服务,逐城市推送未来 5 天的天气数据。

    客户端:实时接收并显示每个城市的天气信息。


    (1) 完整可运行代码

    • Server 端

    // weather_server.cpp
    #include <httplib.h>
    #include <jsoncpp/json/json.h>
    #include <thread>
    #include <chrono>
    #include <vector>
    #include <sstream>

    // 模拟天气数据
    struct WeatherData {
    std::string city;
    int temperature;
    std::string condition;
    };

    // 模拟从数据库获取天气数据
    std::vector<WeatherData> getWeatherData() {
    return {
    {"北京", 28, "晴天"},
    {"上海", 32, "多云"},
    {"广州", 35, "雷阵雨"},
    {"成都", 25, "阴天"},
    {"哈尔滨", 18, "小雨"},
    };
    }

    // 将一条天气数据转为 JSON 字符串
    std::string weatherToJson(const WeatherData& w) {
    Json::Value root;
    root["city"] = w.city;
    root["temperature"] = w.temperature;
    root["condition"] = w.condition;
    return Json::writeString(Json::StreamWriterBuilder(), root);
    }

    int main() {
    httplib::Server server;

    // 流式天气推送接口
    server.Get("/weather/stream", [](const httplib::Request& req, httplib::Response& res) {
    // ============ ① 设置响应头 ============
    res.status = 200;
    res.set_header("Content-Type", "text/event-stream");
    res.set_header("Cache-Control", "no-cache");
    res.set_header("Connection", "keep-alive");
    res.set_header("Access-Control-Allow-Origin", "*");

    // ============ ② 注册流式数据提供者 ============
    res.set_chunked_content_provider("text/event-stream",
    [](size_t offset, httplib::DataSink& sink) -> bool {

    auto weatherList = getWeatherData();

    for (size_t i = 0; i < weatherList.size(); i++) {
    // 模拟网络延迟(从气象局获取数据需要时间)
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 构建 SSE 格式数据
    std::string json = weatherToJson(weatherList[i]);
    std::string sseData = "data: " + json + "\\n\\n";

    // 发送给客户端
    sink.write(sseData.c_str(), sseData.size());
    }

    // 发送结束标记
    std::string doneData = "data: [DONE]\\n\\n";
    sink.write(doneData.c_str(), doneData.size());
    sink.done();

    return false; // 不再提供数据
    });
    });

    std::cout << "天气服务器启动: http://0.0.0.0:8080" << std::endl;
    server.listen("0.0.0.0", 8080);
    return 0;
    }

    • Client 端

    // weather_client.cpp
    #include <httplib.h>
    #include <jsoncpp/json/json.h>
    #include <iostream>
    #include <string>

    int main() {
    httplib::Client client("http://localhost:8080");
    client.set_connection_timeout(10, 0);
    client.set_read_timeout(30, 0); // 流式响应需要较长超时

    // ============ ① 构建 Request 对象 ============
    httplib::Request req;
    req.method = "GET";
    req.path = "/weather/stream";

    std::string buffer; // 接收缓冲区
    bool streamFinished = false; // 流结束标记
    bool gotError = false; // 错误标记

    // ============ ② 注册响应头回调 ============
    req.response_handler = [&](const httplib::Response& res) {
    int statusCode = res.status;
    std::cout << "[回调] response_handler 被调用" << std::endl;
    std::cout << " HTTP 状态码: " << statusCode << std::endl;

    if (statusCode != 200) {
    gotError = true;
    std::cerr << " 错误: 非200状态码" << std::endl;
    return false; // 终止接收
    }
    std::cout << " 状态码正常,准备接收数据…" << std::endl;
    return true; // 继续接收响应体
    };

    // ============ ③ 注册响应体回调 ============
    req.content_receiver = [&](const char* data, size_t len,
    uint64_t offset, uint64_t totalLength) {
    if (gotError) {
    return false; // 之前出错,不再接收
    }

    // 追加新数据到缓冲区
    buffer.append(data, len);

    // 处理 SSE 事件(以 \\n\\n 分隔)
    size_t pos = 0;
    while ((pos = buffer.find("\\n\\n")) != std::string::npos) {
    std::string event = buffer.substr(0, pos);
    buffer.erase(0, pos + 2); // 移除已处理的事件

    if (event.empty()) continue;

    // 解析 "data: …" 格式
    if (event.compare(0, 6, "data: ") == 0) {
    std::string payload = event.substr(6);

    if (payload == "[DONE]") {
    std::cout << "\\n[回调] content_receiver: 收到结束标记 [DONE]" << std::endl;
    streamFinished = true;
    return true;
    }

    // 解析 JSON
    Json::Value root;
    Json::CharReaderBuilder builder;
    std::string errs;
    std::istringstream iss(payload);
    if (Json::parseFromStream(builder, iss, &root, &errs)) {
    std::cout << " 📍 " << root["city"].asString()
    << " | 🌡️ " << root["temperature"].asInt() << "°C"
    << " | ☁️ " << root["condition"].asString() << std::endl;
    }
    }
    }
    return true; // 继续接收
    };

    // ============ ④ 发送请求 ============
    std::cout << "正在请求天气数据…" << std::endl;
    auto res = client.send(req);

    // ============ ⑤ 检查结果 ============
    if (!res) {
    std::cerr << "网络错误: " << httplib::to_string(res.error()) << std::endl;
    return 1;
    }

    std::cout << "\\n✅ 天气数据接收完毕!" << std::endl;
    return 0;
    }


    (2) 运行效果

    • Client 端输出

    正在请求天气数据…
    [回调] response_handler 被调用
    HTTP 状态码: 200
    状态码正常,准备接收数据…
    📍 北京 | 🌡️ 28°C | ☁️ 晴天
    📍 上海 | 🌡️ 32°C | ☁️ 多云
    📍 广州 | 🌡️ 35°C | ☁️ 雷阵雨
    📍 成都 | 🌡️ 25°C | ☁️ 阴天
    📍 哈尔滨 | 🌡️ 18°C | ☁️ 小雨

    [回调] content_receiver: 收到结束标记 [DONE]

    ✅ 天气数据接收完毕!

    时间线:

    第1秒: 📍 北京 | 🌡️ 28°C | ☁️ 晴天
    第2秒: 📍 上海 | 🌡️ 32°C | ☁️ 多云
    第3秒: 📍 广州 | 🌡️ 35°C | ☁️ 雷阵雨
    第4秒: 📍 成都 | 🌡️ 25°C | ☁️ 阴天
    第5秒: 📍 哈尔滨 | 🌡️ 18°C | ☁️ 小雨
    第5秒: [DONE]


    (3) 流程步骤详解

    ┌─────────────────────────────────────────────────────────────────────┐
    │ 步骤 Client 端 Server 端 │
    ├─────────────────────────────────────────────────────────────────────┤
    │ │
    │ ① 构建 Request 对象 │
    │ req.method = "GET" │
    │ req.path = "/weather/stream" │
    │ │
    │ ② 注册 response_handler │
    │ 检查状态码 │
    │ │
    │ ③ 注册 content_receiver │
    │ 解析 SSE 事件 │
    │ │
    │ ④ client.send(req) │
    │ └── TCP 连接 ────────────────────→ │
    │ └── 发送 HTTP 请求 ──────────────→ 接收请求 │
    │ 路由匹配 GET /weather/stream
    │ 执行处理函数 │
    │ │
    │ ⑤ 设置响应头 │
    │ Content-Type: text/event-stream
    │ │
    │ ⑥ 注册 provider │
    │ set_chunked_content_provider()
    │ 处理函数返回 │
    │ │
    │ ⑦ 框架发送响应头 │
    │ HTTP/1.1 200 OK │
    │ ⑧ response_handler 回调 ←─────────── Transfer-Encoding: chunked
    │ 状态码=200,返回 true │
    │ │
    │ ⑨ 框架调用 provider │
    │ sleep(1s) 模拟延迟 │
    │ sink.write("data: {…}\\n\\n")
    │ ⑩ content_receiver 回调 ←─────────── "北京"天气数据 │
    │ 解析 JSON,打印天气信息 │
    │ │
    │ ⑪-⑭ 重复⑨-⑩ 4次 ←─────────────── 上海、广州、成都、哈尔滨 │
    │ │
    │ ⑮ 发送结束标记 │
    │ sink.write("data: [DONE]\\n\\n")
    │ sink.done() │
    │ ⑯ content_receiver 回调 ←─────────── "[DONE]" │
    │ 检测到结束标记,streamFinished=true │
    │ │
    │ ⑰ 连接关闭 ←─────────────────────── ⑱ 连接关闭 │
    │ │
    └─────────────────────────────────────────────────────────────────────┘


    (4) 关键对比:Client 端回调 vs Server 端回调

    ┌─────────────────────────────────────────────────────────────────┐
    │ Client 端 (接收方) │
    ├─────────────────────────────────────────────────────────────────┤
    │ │
    │ response_handler: "响应头到了,我要不要继续?" │
    │ 参数: (const Response& res) │
    │ 返回: true=继续接收 / false=终止 │
    │ │
    │ content_receiver: "新数据块到了,我处理一下" │
    │ 参数: (data, len, offset, totalLength) │
    │ 返回: true=继续接收 / false=终止 │
    │ │
    └─────────────────────────────────────────────────────────────────┘

    ┌─────────────────────────────────────────────────────────────────┐
    │ Server 端 (发送方) │
    ├─────────────────────────────────────────────────────────────────┤
    │ │
    │ provider: "该我发送数据了,我要发什么?" │
    │ 参数: (offset, DataSink& sink) │
    │ 通过 sink.write() 发送数据 │
    │ 通过 sink.done() 结束流 │
    │ 返回: true=还有数据(框架再调) / false=结束 │
    │ │
    └─────────────────────────────────────────────────────────────────┘


    (5) HTTP 层面数据流

    Server 发送的原始 HTTP 响应:

    HTTP/1.1 200 OK
    Content-Type: text/event-stream
    Cache-Control: no-cache
    Connection: keep-alive
    Transfer-Encoding: chunked

    1d\\r\\n
    data: {"city":"北京","temperature":28,"condition":"晴天"}\\n\\n
    1d\\r\\n
    data: {"city":"上海","temperature":32,"condition":"多云"}\\n\\n
    1f\\r\\n
    data: {"city":"广州","temperature":35,"condition":"雷阵雨"}\\n\\n
    1d\\r\\n
    data: {"city":"成都","temperature":25,"condition":"阴天"}\\n\\n
    1f\\r\\n
    data: {"city":"哈尔滨","temperature":18,"condition":"小雨"}\\n\\n
    11\\r\\n
    data: [DONE]\\n\\n
    0\\r\\n
    \\r\\n

    Transfer-Encoding: chunked 格式说明:

    1d ← 块大小(十六进制,1d=29字节)
    data: {…}\\n\\n ← 块数据
    0 ← 大小为0,表示结束


    (6) 为什么用流式?

    • 普通响应方式:

    // Server 端:必须等所有数据准备好
    server.Get("/weather/all", [](auto& req, auto& res) {
    auto data = getAllWeatherData(); // 等5秒,全部收集完
    res.set_content(data, "application/json"); // 一次性发送
    });

    客户端体验:等待 5 秒 → 一次性显示全部。

    • 流式响应方式:

    // Server 端:边生成边发送
    server.Get("/weather/stream", [](auto& req, auto& res) {
    res.set_chunked_content_provider("text/event-stream",
    [](auto& sink) {
    for (auto& city : cities) {
    sleep(1);
    sink.write(cityJson(city)); // 逐条发送
    }
    sink.done();
    });
    });

    客户端体验:每隔 1 秒显示一条,不等待。

    普通响应: [──────── 等待5秒 ────────] → 一次性显示全部
    流式响应: [显示北京] → [显示上海] → [显示广州] → …


    (7) 总结

    要素Client 端Server 端
    核心接口 client.send(req) res.set_chunked_content_provider()
    数据方向 接收数据 发送数据
    回调函数 response_handler + content_receiver provider
    数据操作 解析 data 参数 调用 sink.write()
    结束信号 收到 [DONE] 调用 sink.done()
    适用场景 消费流式 API 提供流式 API

    赞(0)
    未经允许不得转载:171主机测评 » cpp-httplib 完全指南:核心 API、普通响应与流式响应全解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址