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