一个 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 设置一个信号处理程序来自行处理或忽略它。
#includeint 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 |
jpeg、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 = "错误状态: %d
"; 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 = "错误 500
%s
"; 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数据; // 准备数据... 接收器.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::functionfn) override { /* 如果任务实际已入队,则返回 true;如果调用者必须删除相应的连接,则返回 false *。 */ 返回 pool_.enqueue(fn); } 虚拟无效 shutdown() 覆盖 { pool_.shutdown_graceively(); }私人的: 你的线程池 pool_; }; svr.new_task_queue = [] { 返回新的 YourThreadPoolTaskQueue(12); };
#include#include 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 }}) // '范围: bytes=100-199, 500-599'httplib::make_range_header({{0, 0}, {-1, 1}}) // '范围: bytes=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.pyWrote out/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]“GET / 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]“GET / 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 及更低版本无法构建此库,因为版本中的
已损坏。
在Windows.h
之前包含httplib.h
或通过预先定义WIN32_LEAN_AND_MEAN
来包含Windows.h
。
#include#include
#define WIN32_LEAN_AND_MEAN#include#include
笔记
cpp-httplib 官方仅支持最新的 Visual Studio。它可能适用于以前版本的 Visual Studio,但我无法再验证它。旧版本的 Visual Studio 始终欢迎拉取请求,除非它们破坏了 C++11 一致性。
笔记
Windows 8 或更低版本、Visual Studio 2013 或更低版本以及 Cygwin 和 MSYS2(包括 MinGW)既不受支持,也不受测试。
麻省理工学院许可证(© 2024 Yuji Hirose)
这些人做出了巨大的贡献,将这个库从一个简单的玩具提升到了另一个水平!