一個 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。 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 部分。 -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 及更低版本無法建置此程式庫,因為版本中的<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)
這些人做出了巨大的貢獻,將這個庫從一個簡單的玩具提升到了另一個層次!