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("Hello World!", "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.Get("/hi", [](const リクエスト& req, Response& res) {
res.set_content("Hello World!", "text/plain");
}); // リクエストのパスを正規表現と照合し、 // そのキャプチャを抽出します
svr.Get(R"(/numbers/(d+))", [&](const Request& req, Response& res) { autonumbers = req.matches[1];
res.set_content(数値, "テキスト/プレーン");
}); // リクエスト パスの 2 番目のセグメントを「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 リクエスト& req, Response& res) { if (req.has_header("コンテンツ長")) { auto val = req.get_header_value("コンテンツ長" );
if (req.has_param("key")) { auto val = req.get_param_value("key");
}
res.set_content(req.body, "テキスト/プレーン");
});
svr.Get("/stop", [&](const リクエスト& 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 directoryret = svr.set_mount_point("/public", "./www");// /public を ./www1 および ./www2 ディレクトリにマウントしますret = svr.set_mount_point("/public", "./www1"); // 1番目のsearchret = svr.set_mount_point("/public", "./www2"); // 検索の 2 番目の順序 // マウントを削除 /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 | テキスト/CSV | ウェバ | オーディオ/WebM |
| TXT | テキスト/プレーン | ウェーブ | オーディオ/ウェーブ |
| うーん | テキスト/vtt | ああ | フォント/OTF |
| html、html | テキスト/html | ふふふ | フォント/ttf |
| APNG | 画像/アプリケーション | すごい | フォント/woff |
| アヴィフ | 画像/avif | ウォフ2 | フォント/woff2 |
| BMP | 画像/bmp | 7z | application/x-7z-compressed |
| gif | 画像/gif | 原子 | アプリケーション/アトム+xml |
| png | 画像/png | 申請書/PDF | |
| SVG | 画像/SVG+XML | mjs、js | アプリケーション/JavaScript |
| ウェブ | 画像/ウェブサイト | json | アプリケーション/json |
| アイコ | 画像/×アイコン | RSS | アプリケーション/rss+xml |
| ティフ | 画像/TIFF | タール | アプリケーション/x-tar |
| ティフ | 画像/TIFF | xhtml、xht | アプリケーション/xhtml+xml |
| jpeg、jpg | 画像/jpeg | xslt | アプリケーション/xslt+xml |
| mp4 | ビデオ/MP4 | XML | アプリケーション/xml |
| mpeg | ビデオ/mpeg | ガズ | アプリケーション/gzip |
| ウェブム | ビデオ/ウェブサイト | ジップ | アプリケーション/zip |
| mp3 | オーディオ/MP3 | ワズム | アプリケーション/ワズム |
警告
これらの静的ファイル サーバー メソッドはスレッドセーフではありません。
// ハンドラーは、応答がクライアントに送信される直前に呼び出されます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] ; 試してください。
catch (std::Exception &e) { snprintf(buf, sizeof(buf), fmt, e.what());
} catch (...) { // 以下の注を参照 snprintf(buf, sizeof(buf), fmt, "不明な例外");
}
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::Handled を返します。
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 ファイル。 content_reader(
[&](const MultipartFormData &file) {
files.push_back(ファイル); true を返します。
}、
[&](const char *data, size_t data_length) {
files.back().content.append(data, data_length); true を返します。
});
} それ以外 {
std::文字列本体; content_reader([&](const char *data, size_t data_length) {
body.append(データ, データ長); true を返します。
});
}
});const 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;
シンク.write(&d[オフセット], std::min(長さ, DATA_CHUNK_SIZE)); true を返します。 // 処理をキャンセルする場合は 'false' を返します。
}、
[データ](ブール成功) { データを削除します。 });
});コンテンツの長さを含まない場合:
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());
} それ以外 {
シンク.done(); // これ以上データはありません
true を返します。 // 処理をキャンセルする場合は 'false' を返します。
});
});svr.Get("/chunked", [&](const リクエスト& req, Response& res) {
res.set_chunked_content_provider( "テキスト/プレーン",
[](size_t オフセット、データシンク&シンク) {
シンク.write("123", 3);
シンク.write("345", 3);
シンク.write("789", 3);
シンク.done(); // これ以上のデータは true を返しません。 // 処理をキャンセルする場合は 'false' を返します。
}
);
});トレーラー付き:
svr.Get("/chunked", [&](const リクエスト& req, Response& res) {
res.set_header("トレーラー", "ダミー1, ダミー2");
res.set_chunked_content_provider( "テキスト/プレーン",
[](size_t オフセット、データシンク&シンク) {
シンク.write("123", 3);
シンク.write("345", 3);
シンク.write("789", 3);
シンク.done_with_trailer({
{"ダミー1", "ダミーVal1"},
{"ダミー2"、"ダミーVal2"}
}); true を返します。
}
);
});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 Expectation Failed」応答を送信します。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 (無制限) です。制限に達すると、リスナーはクライアント接続をシャットダウンします。
必要に応じて独自のスレッド プール実装を提供できます。
class YourThreadPoolTaskQueue : public TaskQueue {public: YourThreadPoolTaskQueue(size_t n) {
pool_.start_with_thread_count(n);
} virtual bool enqueue(std::function<void()> fn) override { /* タスクが実際にキューに入れられた場合は true を返し、呼び出し側が対応する接続をドロップする必要がある場合は false * を返します。 */ return pool_.enqueue(fn);
仮想 void shutdown() オーバーライド {
pool_.shutdown_graceful();
}プライベート:
YourThreadPool プール_;
};
svr.new_task_queue = [] { return new 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;
}
}ヒント
スキームホストポート文字列を含むコンストラクターがサポートされるようになりました。
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接続、
SSLLoading証明書、
SSLサーバー検証、
サポートされていないMultipartBoundaryChars、
圧縮、
接続タイムアウト、
}; httplib::Headers ヘッダー = {
{ "Accept-Encoding"、"gzip、deflate" }
};auto res = cli.Get("/hi", headers);または
auto res = cli.Get("/hi", {{"Accept-Encoding", "gzip, deflate"}});または
cli.set_default_headers({
{ "Accept-Encoding"、"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::Params パラメータ{
{ "名前", "ジョン" },
{ "メモ"、"コーダー" }
};auto res = cli.Post("/post", params); httplib::MultipartFormDataItems 項目 = {
{ "text1", "テキストのデフォルト", "", "" },
{ "text2", "aωb", "", "" },
{ "file1", "hnennlnlnon", "hello.txt", "text/plain" },
{ "file2", "{n "world", truen}n", "world.json", "application/json" },
{ "file3", "", "", "アプリケーション/オクテットストリーム" },
};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("/large-data",
[&](const char *data, size_t data_length) {
body.append(データ, データ長); true を返します。
}); std::string body;auto res = cli.Get( "/stream", Headers(),
[&](const Response &response) { EXPECT_EQ(StatusCode::OK_200, response.status); true を返します。 // リクエストをキャンセルする場合は 'false' を返します。
}、
[&](const char *data, size_t data_length) {
body.append(データ, データ長); true を返します。 // リクエストをキャンセルする場合は 'false' を返します。
});std::string body = ...;auto res = cli.Post( "/stream", body.size(),
[](size_t オフセット、size_t 長、データシンク&シンク) {
Sink.write(body.data() + オフセット、長さ); true を返します。 // リクエストをキャンセルする場合は 'false' を返します。
}, "テキスト/プレーン");auto res = cli.Post( "/stream",
[](size_t オフセット、データシンク&シンク) {
シンク.os << "チャンクデータ 1";
シンク.os << "チャンクデータ 2";
シンク.os << "チャンクデータ 3";
シンク.done(); true を返します。 // リクエストをキャンセルする場合は 'false' を返します。
}, "テキスト/プレーン");httplib::Client cli(url, port);// 印刷: 0 / 000 バイト => 50% completeauto res = cli.Get("/", [](uint64_t len, uint64_t total) { printf("%lld / %lld バイト => %d%% 完了n",
レン、合計、
(int)(len*100/合計)); true を返します。 // リクエストをキャンセルする場合は '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}}) // '範囲: bytes=1- 10'});// res->status は 206 である必要があります。// res->body は "bcdefghijk" である必要があります。 httplib::make_range_header({{1, 10}, {20, -1}}) // '範囲: bytes=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("/hello"); // "接続: close" cli.set_keep_alive(true);
cli.Get("/world");
cli.set_keep_alive(false);
cli.Get("/last-request"); // 「接続: 閉じる」の場合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 タイプのコンテンツに圧縮を適用できます。
text/event-stream を除くすべてのテキスト タイプ
画像/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"}});
解像度->本体; // 圧縮データselectの代わりにpoll使用するselectシステム コールは、より広くサポートされているため、デフォルトとして使用されます。代わりに cpp-httplib にpollを使用させたい場合は、 CPPHTTPLIB_USE_POLLを使用してこれを行うことができます。
Unix ドメイン ソケットのサポートは、Linux および macOS で利用できます。
// 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 では抽象ソケット アドレスを使用することもできます。抽象ソケット アドレスを使用するには、パスの先頭に null バイト ('x00') を追加します。
$ ./split.py -husage: Split.py [-h] [-e EXTENSION] [-o OUT]このスクリプトは httplib.h を .h と .cc の部分に分割します。オプションの引数: -h、--help showこのヘルプ メッセージを表示して終了します。 -e EXTENSION、--extension 実装ファイルの EXTENSION 拡張子 (デフォルト: cc) -o OUT、--out OUT ファイルを書き込む場所 (デフォルト: out)$ ./split.pyout/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/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/Aug/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 Hubから
> 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/Aug/2024:21:33:56 +0000] "GET / HTTP/1.1" 200 599 "-" "curl/8.7.1"192.168.65.1 - - [31/Aug/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>が壊れているため、このライブラリをビルドできません。
httplib.h Windows.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 および MinGW を含む MSYS2 はサポートもテストもされていません。
MIT ライセンス (© 2024 廣瀬裕二)
これらの人々は、このライブラリを単純なおもちゃとはまったく別のレベルに磨き上げるために多大な貢献をしてくれました。