cpp-httplib库的基本使用及文件上传下载

0.前言

cpp-httplib 是一个 header-only 的跨平台 HTTP/HTTPS 网络库,采用 MIT 开源协议,接口为阻塞调用。虽然功能简单,但也不用一堆依赖,C++ 的接口也比 libcurl 的 C 接口用起来顺手一点,文档也提供了不少参考代码,如果是做简单的 HTTP server 或者 client 还是值得一试的。

库里有两个重要的宏:

关闭异常:CPPHTTPLIB_NO_EXCEPTIONS,默认遇到错误逻辑是抛异常,可以通过该宏来关闭,但是关了之后没有别的机制来获知这个错误。

启用 OpenSSL 支持:CPPHTTPLIB_OPENSSL_SUPPORT,需要 OpenSSL 的库可以被链接,目前仅支持 1.1.1 和 3.0 版本。

还有个问题是,该库仅支持 HTTP 1.0 和 1.1,默认 1.1,如果要用 HTTP2 或 HTTP3 用别的库吧。

库地址:https://github.com/yhirose/cpp-httplib

本文代码: https://github.com/gongjianbo/MyTestCode/tree/master/Qt/TestQt_20230913_HttpLib

(为方便操作,用 Qt 库拖了个简单的界面)

1.基本使用

cpp-httplib 提供了 RESTful 风格的接口,操作起来比 libcurl 方便不少。

服务端:

    httplib::Server svr;
    svr.set_error_handler([](const httplib::Request &req, httplib::Response &res) {
        std::cerr << "Server-error:" << req.method << "\t" << req.path << "\tstatus:" << res.status << std::endl;
        std::string json = R"({"message":"path error"})";
        res.set_content(json, "text/plain");
    });
    // 路由处理抛了异常,会被捕获,然后进入 set_error_handler 逻辑
    svr.Get("/hello", [](const httplib::Request &req, httplib::Response &res) {
        std::cerr << "Server-log:" << req.method << "\t" << req.path << "\t" << req.body << std::endl;
        // 获取 url 中的参数
        for (auto it = req.params.begin(); it != req.params.end(); it++)
        {
            std::cerr << "\t" << it->first << ": " << it->second << std::endl;
        }
        res.set_content(R"({"message":"get result"})", "appliation/json");
    });
    svr.Post("/hello", [](const httplib::Request &req, httplib::Response &res) {
        std::cerr << "Server-log:" << req.method << "\t" << req.path << "\t" << req.body << std::endl;
        res.set_content(R"({"message":"post result"})", "appliation/json");
    });
    svr.listen("localhost", 12345);

listen 之后就阻塞住循环处理请求了。

客户端:

    httplib::Client cli("localhost", 12345);
    // auto res = cli.Get("hello?argkey=agrvalue");
    auto res = cli.Post("hello", R"({"formkey":"formvalue"})", "application/json");
    if (res) {
        std::cerr << "Status Code:" << res->status << std::endl;
        std::cerr << "Header:\n";
        for (auto it = res->headers.begin(); it != res->headers.end(); it++)
        {
            std::cerr << "\t" << it->first << ": " << it->second << std::endl;
        }
        std::cerr << "Body:\n" << res->body << std::endl;
    }

接口有多个重载,可根据需求选用,作者提供了大量的示例。

2.文件上传下载

文件的上传有两种方式,小文件可以一次性读写,大文件可以用 Provider 流式读写。

服务端:

// 文件上传
svr.Post("/upload", [](const httplib::Request &req, httplib::Response &res, const httplib::ContentReader &content_reader) {
    std::cerr << "Server-log: upload\t" << req.get_header_value("Content-Type") << std::endl;
    // 二进制数据可以用:multipart/form-data 和 application/octet-stream
    if (req.is_multipart_form_data()) {
        httplib::MultipartFormDataItems files;
        // 先拿到 file 信息,再流式读取
        content_reader(
            [&](const httplib::MultipartFormData &file) {
                files.push_back(file);
                std::cerr << "\tupload read " << file.filename << "\t" << file.content << std::endl;
                return true;
            },
            [&](const char *data, size_t data_length) {
                files.back().content.append(data, data_length);
                std::cerr << "\tupload read:" << data_length << std::endl;
                return true;
            });
    } else {
        std::string body;
        content_reader([&](const char *data, size_t data_length) {
            body.append(data, data_length);
            std::cerr << "\tupload read:" << data_length << std::endl;
            return true;
        });
        std::cerr << "\tupload read " << body << std::endl;
    }
    res.set_content(R"({"message":"upload result"})", "appliation/json");
});
// 文件下载
svr.Get("/download/:id", [](const httplib::Request &req, httplib::Response &res) {
    std::cerr << "Server-log: download\t" << req.path_params.at("id") << "\t" << req.get_header_value("Content-Type") << std::endl;
    res.set_header("Cache-Control", "no-cache");
    res.set_header("Content-Disposition", "attachment; filename=hello.txt");
    res.set_chunked_content_provider(
        "multipart/form-data", [](size_t offset, httplib::DataSink &sink) {
            const char arr[] = "hello world";
            auto ret = sink.write(arr + offset, sizeof(arr));
            sink.done();
            std::cerr << "\tdownload write:" << sizeof(arr) << std::endl;
            return !!ret;
        });
});

 客户端:

void doUp()
{
    httplib::Client cli = httplib::Client{"localhost", 12345};

#if 0
    // 小文件一次性发送
    httplib::MultipartFormData form;
    form.name = "myfile";
    form.content = "hello world";
    form.filename = "hello.txt";
    form.content_type = "multipart/form-data";
    httplib::MultipartFormDataItems form_items;
    form_items.push_back(form);
    httplib::Result res = cli.Post("/upload", form_items);
#else
    // 大文件用流式接口
    httplib::MultipartFormDataProvider provider;
    provider.name = "myfile";
    provider.filename = "hello.txt";
    provider.provider = [&](size_t offset, httplib::DataSink &sink){
        // offset 是已发送的偏移量
        const char arr[] = "hello world";
        auto ret = sink.write(arr + offset, sizeof(arr));
        std::cerr << "Client write:" << arr << std::endl;
        // 发送完成
        sink.done();
        return !!ret;
    };
    provider.content_type = "multipart/form-data";
    httplib::MultipartFormDataProviderItems provider_items;
    provider_items.push_back(provider);
    // header 的 Content-Type 会默认设置为 multipart/form-data,且自动加上 boundary
    httplib::Result res = cli.Post("/upload", {}, {}, provider_items);
#endif
    std::cerr << __FUNCTION__ << "\tpath:/upload\tres:" << (!!res) << std::endl;
    // printResult(res);
}

void doDown()
{
    httplib::Client cli = httplib::Client{"localhost", 12345};
#if 0
    // 小文件一次性读取
    httplib::Result res = cli.Get("/download/0");
    if (res) {
        // Content-Disposition: attachment; filename=hello.txt
        std::string dispsition = res->get_header_value("Content-Disposition");
        std::cerr << dispsition << std::endl;
    }
#else
    // 大文件用流式接口
    httplib::Result res = cli.Get(
        "/download/0",
        [&](const httplib::Response &response) {
            std::cerr << "Client read:" << response.status << std::endl;
            return true;
        },
        [&](const char *data, size_t data_length) {
            std::cerr << "Client read:" << std::string(data, data_length) << std::endl;
            return true;
        });
#endif
    std::cerr << __FUNCTION__ << "\tpath:/download/0\tres:" << (!!res) << std::endl;
    // printResult(res);
}

为了方便演示,我把文件读写省去了,固定用的 "hello world" 字符串当作文件内容。实测改为文件读写也是可用的,测试用 1G 的文件可以正常收发且未损坏。

  • 5
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龚建波

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值