一个 C++11 单文件头跨平台 HTTP/HTTPS 库。
设置起来非常简单。只需在代码中包含httplib.h文件即可!
重要的
该库使用“阻塞”套接字 I/O。如果您正在寻找一个具有“非阻塞”套接字 I/O 的库,那么这不是您想要的。
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// HTTPhttplib::Server svr;// HTTPShttplib::SSLServer svr;
svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) {
res.set_content("你好世界!", "text/plain");
});
svr.listen("0.0.0.0", 8080);#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// HTTPhttplib::Client cli("http://cpp-httplib-server.yhirose.repl.co");// HTTPShttplib::Client cli(" https://cpp-httplib-server.yhirose.repl.co");auto res = cli.Get("/hi");
资源->状态;
资源->主体;SSL 支持可通过CPPHTTPLIB_OPENSSL_SUPPORT获得。 libssl和libcrypto应该链接。
笔记
cpp-httplib 目前仅支持 3.0 或更高版本。请参阅此页面以获取更多信息。
提示
对于 macOS:cpp-httplib 现在可以通过CPPHTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN使用系统证书。 CoreFoundation和Security应与-framework链接。
#define CPPHTTPLIB_OPENSSL_SUPPORT#include "path/to/httplib.h"// Serverhttplib::SSLServer svr("./cert.pem", "./key.pem");// Clienthttplib::Client cli("https: //本地主机:1234"); // 方案 + hosthttplib::SSLClient cli("localhost:1234"); // hosthttplib::SSLClient cli("localhost", 1234); // 主机、端口 // 使用您的 CA 包cli.set_ca_cert_path("./ca-bundle.crt");// 禁用证书验证 cli.enable_server_certificate_verification(false);// 禁用主机验证 cli.enable_server_host_verification(false);笔记
使用 SSL 时,似乎不可能在所有情况下避免 SIGPIPE,因为在某些操作系统上,只能在每条消息的基础上抑制 SIGPIPE,但没有办法让 OpenSSL 库对其内部通信这样做。如果您的程序需要避免在 SIGPIPE 上终止,唯一完全通用的方法可能是为 SIGPIPE 设置一个信号处理程序来自行处理或忽略它。
#include <httplib.h>int main(void)
{ 使用命名空间 httplib;
服务器svr;
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("你好世界!", "text/plain");
}); // 将请求路径与正则表达式匹配 // 并提取其捕获
svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { auto number = req.matches[1];
res.set_content(数字,“文本/纯文本”);
}); // 捕获请求路径的第二段作为“id”路径参数
svr.Get("/users/:id", [&](const Request& req, Response& res) { auto user_id = req.path_params.at("id");
res.set_content(user_id, "text/plain");
}); // 从 HTTP 标头和 URL 查询参数中提取值
svr.Get("/body-header-param", [](const Request& req, Response& res) { if (req.has_header("Content-Length")) { auto val = req.get_header_value("Content-Length" );
} if (req.has_param("key")) { auto val = req.get_param_value("key");
}
res.set_content(req.body, "text/plain");
});
svr.Get("/stop", [&](const Request& req, Response& res) {
svr.stop();
});
svr.listen("本地主机", 1234);
}还支持Post 、 Put 、 Delete和Options方法。
int port = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind();// 挂载 / 到 ./www 目录auto ret = svr.set_mount_point("/", "./www");if (!ret) { // 指定的基目录不存在...}// 挂载 / public to ./www目录ret = svr.set_mount_point("/public", "./www");//挂载/public到./www1和./www2目录ret = svr.set_mount_point("/public", "./www1"); // 第一个顺序 searchret = svr.set_mount_point("/public", "./www2"); // 第二个搜索顺序 // 删除挂载 /ret = svr.remove_mount_point("/");// 删除挂载 /publicret = svr.remove_mount_point("/public"); // 用户定义的文件扩展名和 MIME 类型映射ssvr.set_file_extension_and_mimetype_mapping("cc", "text/xc");
svr.set_file_extension_and_mimetype_mapping("cpp", "text/xc");
svr.set_file_extension_and_mimetype_mapping("hh", "text/xh");以下是内置映射:
| 扩大 | MIME 类型 | 扩大 | MIME 类型 |
|---|---|---|---|
| CSS | 文本/CSS | mpga | 音频/mpeg |
| 数据集 | 文本/csv | 韦巴 | 音频/网络管理 |
| TXT | 文本/纯文本 | 声音 | 音频/波 |
| 瓦特 | 文本/vtt | 奥特夫 | 字体/otf |
| html, html | 文本/html | ttf | 字体/ttf |
| apng | 图片/apng | 沃夫 | 字体/woff |
| 阿维夫 | 图片/avif | 沃夫2 | 字体/woff2 |
| 图像格式 | 图像/bmp | 7z | 应用程序/x-7z-压缩 |
| 动图 | 图片/gif | 原子 | 应用程序/原子+xml |
| PNG | 图片/png | 申请/pdf | |
| svg | 图像/svg+xml | js, js | 应用程序/javascript |
| 网页文件 | 图片/网页 | json | 应用程序/json |
| ICO | 图像/x-图标 | RSS | 应用程序/RSS+XML |
| tif | 图像/tiff | 焦油 | 应用程序/x-tar |
| 蒂夫 | 图像/tiff | xhtml、xht | 应用程序/xhtml+xml |
| jpg、jpg | 图片/jpeg | xslt | 应用程序/xslt+xml |
| mp4 | 视频/mp4 | XML | 应用程序/xml |
| MPEG | 视频/mpeg | 广州 | 应用程序/gzip |
| 网络管理 | 视频/网络管理 | 拉链 | 应用程序/zip |
| mp3 | 音频/mp3 | 瓦斯姆 | 应用程序/wasm |
警告
这些静态文件服务器方法不是线程安全的。
// 在响应发送到客户端之前调用处理程序vr.set_file_request_handler([](const Request &req, Response &res) {
...
});svr.set_logger([](const auto& req, const auto& res) { your_logger(req, res);
});svr.set_error_handler([](const auto& req, auto& res) { auto fmt = "<p>错误状态: <span style='color:red;'>%d</span></p>"; char buf [BUFSIZ] snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});如果用户路由处理程序抛出错误,则调用异常处理程序。
svr.set_exception_handler([](const auto& req, auto& res, std::exception_ptr ep) { auto fmt = "<h1>错误 500</h1><p>%s</p>"; char buf[BUFSIZ] ; 尝试 { std::rethrow_exception(ep);
} catch (std::Exception &e) { snprintf(buf, sizeof(buf), fmt, e.what());
} catch (...) { // 请参阅以下注释 snprintf(buf, sizeof(buf), fmt, "Unknown Exception");
}
res.set_content(buf, "text/html");
res.status = StatusCode::InternalServerError_500;
});警告
如果您不为重新抛出的异常指针提供catch (...)块,则未捕获的异常最终将导致服务器崩溃。当心!
svr.set_pre_routing_handler([](const auto& req, auto& res) { if (req.path == "/hello") {
res.set_content("世界", "文本/html"); 返回服务器::处理程序响应::处理;
返回 Server::HandlerResponse::Unhandled;
});svr.set_post_routing_handler([](const auto& req, auto& res) {
res.set_header("ADDITIONAL_HEADER", "值");
});svr.Post("/multipart", [&](const auto& req, auto& res) { auto size = req.files.size(); auto ret = req.has_file("name1"); const auto& file = req. get_file_value("name1"); // 文件名; // 文件内容类型;});svr.Post("/content_receiver",
[&](const Request &req, Response &res, const ContentReader &content_reader) { if (req.is_multipart_form_data()) { // 注意:`content_reader` 会阻塞,直到读取每个表单数据字段为止
MultipartFormDataItems 文件; 内容阅读器(
[&](const MultipartFormData &file) {
文件.push_back(文件); 返回真;
},
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length); 返回真;
});
} 别的 {
std::字符串体; content_reader([&](const char *data, size_t data_length) {
body.append(数据, data_length); 返回真;
});
}
});常量 size_t DATA_CHUNK_SIZE = 4;
svr.Get("/stream", [&](const 请求 &req, 响应 &res) { auto data = new std::string("abcdefg");
res.set_content_provider(
data->size(), // 内容长度 "text/plain", // 内容类型
[&, data](size_t 偏移量, size_t 长度, DataSink &sink) { const auto &d = *data;
sink.write(&d[偏移], std::min(长度, DATA_CHUNK_SIZE)); 返回真; // 如果要取消该过程,则返回“false”。
},
[数据](bool success) { 删除数据; });
});无内容长度:
svr.Get("/stream", [&](const 请求 &req, 响应 &res) {
res.set_content_provider( "text/plain", // 内容类型
[&](size_t offset, DataSink &sink) { if (/* 还有数据 */) {
std::vector<char> 数据; // 准备数据...
接收器.write(data.data(), data.size());
} 别的 {
接收器完成(); // 没有更多数据了
返回真; // 如果要取消该过程,则返回“false”。
});
});svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_chunked_content_provider(“文本/纯文本”,
[](size_t 偏移量,DataSink &sink) {
接收器.write("123", 3);
接收器.write("345", 3);
接收器.write("789", 3);
接收器完成(); // 没有更多数据返回 true; // 如果要取消该过程,则返回“false”。
}
);
});带拖车:
svr.Get("/chunked", [&](const Request& req, Response& res) {
res.set_header("预告片", "Dummy1, Dummy2");
res.set_chunked_content_provider(“文本/纯文本”,
[](size_t 偏移量,DataSink &sink) {
接收器.write("123", 3);
接收器.write("345", 3);
接收器.write("789", 3);
水槽.done_with_trailer({
{"Dummy1", "DummyVal1"},
{“Dummy2”,“DummyVal2”}
}); 返回真;
}
);
});svr.Get("/content", [&](const 请求 &req, 响应 &res) {
res.set_file_content("./path/to/conent.html");
});
svr.Get("/content", [&](const 请求 &req, 响应 &res) {
res.set_file_content("./path/to/conent", "text/html");
});默认情况下,服务器发送Expect: 100-continue标头的100 Continue响应。
// 发送“417 期望失败” response.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return StatusCode::ExpectationFailed_417;
}); // 发送最终状态而不读取消息 body.svr.set_expect_100_continue_handler([](const Request &req, Response &res) { return res.status = StatusCode::Unauthorized_401;
});svr.set_keep_alive_max_count(2); // 默认为 5svr.set_keep_alive_timeout(10); // 默认为 5
svr.set_read_timeout(5, 0); // 5 秒svr.set_write_timeout(5, 0); // 5 秒svr.set_idle_interval(0, 100000); // 100 毫秒
svr.set_payload_max_length(1024 * 1024 * 512); // 512MB
笔记
当请求正文内容类型为 'www-form-urlencoded' 时,实际负载长度不应超过CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 。
请参阅服务器示例和客户端示例。
ThreadPool用作默认任务队列,默认线程数为 8,即std::thread::hardware_concurrency() 。您可以使用CPPHTTPLIB_THREAD_POOL_COUNT更改它。
如果你想在运行时设置线程数,没有方便的方法......但这里是如何。
svr.new_task_queue = [] { 返回新ThreadPool(12); };您还可以提供一个可选参数来限制待处理请求的最大数量,即由侦听器accept()但仍在等待工作线程服务的请求。
svr.new_task_queue = [] { return new ThreadPool(/*num_threads=*/12, /*max_queued_requests=*/18); };默认限制为 0(无限制)。一旦达到限制,侦听器将关闭客户端连接。
您可以根据需要提供自己的线程池实现。
类 YourThreadPoolTaskQueue :公共 TaskQueue {public:YourThreadPoolTaskQueue(size_t n){
pool_.start_with_thread_count(n);
} virtual bool enqueue(std::function<void()> fn) override { /* 如果任务实际已入队,则返回 true;如果调用者必须删除相应的连接,则返回 false *。 */ 返回 pool_.enqueue(fn);
} 虚拟无效 shutdown() 覆盖 {
pool_.shutdown_graceively();
}私人的:
你的线程池 pool_;
};
svr.new_task_queue = [] { 返回新的 YourThreadPoolTaskQueue(12);
};#include <httplib.h>#include <iostream>int main(void)
{
httplib::Client cli("localhost", 1234); if (auto res = cli.Get("/hi")) { if (res->status == StatusCode::OK_200) {
std::cout << res->body << std::endl;
}
} else { 自动错误 = res.error();
std::cout << "HTTP 错误:" << httplib::to_string(err) << std::endl;
}
}提示
现在支持带有 schema-host-port 字符串的构造函数!
httplib::Client cli("localhost");
httplib::Client cli("localhost:8080");
httplib::Client cli("http://localhost");
httplib::Client cli("http://localhost:8080");
httplib::Client cli("https://localhost");
httplib::SSLClient cli("localhost");以下是Result::error()中的错误列表。
枚举错误{
成功=0,
未知,
联系,
绑定IP地址,
读,
写,
超过重定向计数,
取消,
SSL 连接,
SSL加载证书,
SSL服务器验证,
不支持的MultipartBoundaryChars,
压缩,
连接超时,
};httplib::Headers 标头 = {
{ "接受编码", "gzip, deflate" }
};auto res = cli.Get("/hi", headers);或者
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});或者
cli.set_default_headers({
{ "接受编码", "gzip, deflate" }
});auto res = cli.Get("/hi");res = cli.Post("/post", "text", "text/plain");
res = cli.Post("/person", "name=john1¬e=coder", "application/x-www-form-urlencoded"); httplib::Params 参数;
params.emplace("姓名", "约翰");
params.emplace("note", "coder");auto res = cli.Post("/post", params);或者
httplib::参数参数{
{“姓名”,“约翰”},
{“注意”,“编码器”}
};auto res = cli.Post("/post", params); httplib::MultipartFormDataItems items = {
{ "text1", "默认文本", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "hnennlnlnon", "hello.txt", "text/plain" },
{ "file2", "{n "world", truen}n", "world.json", "application/json" },
{ "file3", "", "", "application/octet-stream" },
};auto res = cli.Post("/multipart", items);res = cli.Put("/resource/foo", "text", "text/plain");res = cli.Delete("/resource/foo");res = cli.Options("*");
res = cli.Options("/resource/foo");cli.set_connection_timeout(0, 300000); // 300 毫秒cli.set_read_timeout(5, 0); // 5 秒cli.set_write_timeout(5, 0); // 5 秒
std::string body;auto res = cli.Get("/大数据",
[&](const char *data, size_t data_length) {
body.append(数据, data_length); 返回真;
}); std::string body;auto res = cli.Get( "/stream", Headers(),
[&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); 返回真; // 如果要取消请求,则返回“false”。
},
[&](const char *data, size_t data_length) {
body.append(数据, data_length); 返回真; // 如果要取消请求,则返回“false”。
});std::string body = ...;auto res = cli.Post( "/stream", body.size(),
[](size_t 偏移量, size_t 长度, DataSink &sink) {
sink.write(body.data() + 偏移量, 长度); 返回真; // 如果要取消请求,则返回“false”。
}, “文本/纯文本”);自动 res = cli.Post( "/stream",
[](size_t 偏移量,DataSink &sink) {
sink.os <<“分块数据1”;
sink.os <<“分块数据2”;
sink.os <<“分块数据3”;
接收器完成(); 返回真; // 如果要取消请求,则返回“false”。
}, “文本/纯文本”);httplib::Client cli(url, port);// 打印:0 / 000 字节 => 50% 完成auto res = cli.Get("/", [](uint64_t len, uint64_t Total) { printf("%lld / %lld 字节 => %d%% 已完成n",
长度,总计,
(int)(len*100/总计)); 返回真; // 如果您想取消请求,则返回“false”。}
); 
// 基本身份验证cli.set_basic_auth("user", "pass");// 摘要身份验证cli.set_digest_auth("user", "pass");// 承载令牌身份验证cli.set_bearer_token_auth("token");笔记
摘要式身份验证需要 OpenSSL。
cli.set_proxy("host", port);// 基本身份验证cli.set_proxy_basic_auth("user", "pass");// 摘要身份验证cli.set_proxy_digest_auth("user", "pass");// 承载令牌身份验证cli.set_proxy_bearer_token_auth (“经过”);笔记
摘要式身份验证需要 OpenSSL。
httplib::Client cli("httpbin.org");auto res = cli.Get("/range/32", { httplib::make_range_header({{1, 10}}) // '范围:字节=1- 10'});// res->status 应为 206。// res->body 应为“bcdefghijk”。 httplib::make_range_header({{1, 10}, {20, -1}}) // '范围:字节=1-10, 20-'httplib::make_range_header({{100, 199}, {500, 599 }}) // '范围:字节=100-199, 500-599'httplib::make_range_header({{0, 0}, {-1, 1}}) // '范围:字节=0-0, -1'httplib::Client cli("localhost", 1234);
cli.Get("/你好"); // 与“连接:关闭”cli.set_keep_alive(true);
cli.Get("/world");
cli.set_keep_alive(假);
cli.Get("/最后请求"); // 与“连接:关闭”httplib::Client cli("yahoo.com");auto res = cli.Get("/");
资源->状态; // 301cli.set_follow_location(true);
res = cli.Get("/");
资源->状态; // 200笔记
此功能在 Windows 上尚不可用。
cli.set_interface("eth0"); // 接口名称、IP地址或主机名服务器可以对以下 MIME 类型内容应用压缩:
除文本/事件流之外的所有文本类型
图像/svg+xml
应用程序/javascript
应用程序/json
应用程序/xml
应用程序/xhtml+xml
“gzip”压缩可用于CPPHTTPLIB_ZLIB_SUPPORT 。 libz应该链接。
Brotli 压缩可通过CPPHTTPLIB_BROTLI_SUPPORT实现。应链接必要的库。请参阅 https://github.com/google/brotli 了解更多详细信息。
cli.set_compress(true);
res = cli.Post("/resource/foo", "...", "text/plain");cli.set_decompress(false);
res = cli.Get("/resource/foo", {{"Accept-Encoding", "gzip, deflate, br"}});
资源->主体; // 压缩数据poll而不是select select系统调用被用作默认值,因为它得到了更广泛的支持。如果你想让 cpp-httplib 使用poll来代替,你可以使用CPPHTTPLIB_USE_POLL来实现。
Linux 和 macOS 上提供 Unix 域套接字支持。
// Serverhttplib::Server svr("./my-socket.sock");
svr.set_address_family(AF_UNIX).listen("./my-socket.sock", 80);// Clienthttplib::Client cli("./my-socket.sock");
cli.set_address_family(AF_UNIX);“my-socket.sock”可以是相对路径或绝对路径。您的应用程序必须具有该路径的适当权限。您还可以在 Linux 上使用抽象套接字地址。要使用抽象套接字地址,请在路径前面添加一个空字节 ('x00')。
$ ./split.py -husage: split.py [-h] [-e EXTENSION] [-o OUT]此脚本将 httplib.h 拆分为 .h 和 .cc 部分。可选参数: -h, --help show此帮助消息并退出 -e EXTENSION, --extension 实施文件的扩展名(默认值:cc) -o OUT, --out OUT 写入文件的位置(默认值:out)$ ./split.py写出/httplib.h和out/httplib.cc
静态 HTTP 服务器的 Dockerfile 可用。此 HTTP 服务器的端口号为 80,它提供容器中/html目录中的静态文件。
> docker build -t cpp-httplib-server ....> docker run --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server 在 0.0.0.0 端口 80 上提供 HTTP 服务... 192.168.65.1 - - [31/8/2024:21:33:56 +0000]“GET / HTTP/1.1”200 599“-”“curl/8.7.1”192.168.65.1 - - [31/8/2024 :21:34:26 +0000]“获取/ HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -”“Mozilla/5.0 ...”
来自 Docker 中心
> docker run --rm -it -p 8080:80 -v ./docker/html:/html yhirose4dockerhub/cpp-httplib-server ...> docker run --init --rm -it -p 8080:80 -v ./docker/html:/html cpp-httplib-server 在 0.0.0.0 端口 80 上提供 HTTP 服务... 192.168.65.1 - - [31/8/2024:21:33:56 +0000]“GET / HTTP/1.1”200 599“-”“curl/8.7.1”192.168.65.1 - - [31/8/2024 :21:34:26 +0000]“获取/ HTTP/1.1" 200 599 "-" "Mozilla/5.0 ..."192.168.65.1 - - [31/Aug/2024:21:34:26 +0000] "GET /favicon.ico HTTP/1.1" 404 152 " -”“Mozilla/5.0 ...”
g++ 4.8 及更低版本无法构建此库,因为版本中的<regex>已损坏。
在Windows.h之前包含httplib.h或通过预先定义WIN32_LEAN_AND_MEAN来包含Windows.h 。
#include <httplib.h>#include <Windows.h>
#define WIN32_LEAN_AND_MEAN#include <Windows.h>#include <httplib.h>
笔记
cpp-httplib 官方仅支持最新的 Visual Studio。它可能适用于以前版本的 Visual Studio,但我无法再验证它。旧版本的 Visual Studio 始终欢迎拉取请求,除非它们破坏了 C++11 一致性。
笔记
Windows 8 或更低版本、Visual Studio 2013 或更低版本以及 Cygwin 和 MSYS2(包括 MinGW)既不受支持,也不受测试。
麻省理工学院许可证(© 2024 Yuji Hirose)
这些人做出了巨大的贡献,将这个库从一个简单的玩具提升到了另一个水平!