HN 日本語サマリー

← 一覧へ戻る
プログラミング

Vermell – epoll を使用した最小限で依存関係のない C++ Web フレームワーク

Vermell – Minimal, dependency-free C++ web framework using epoll (github.com)

19 pointsby standar-donkey8 コメント

要約

Vermell は、現代の C++ 環境向けに設計された、最小限で依存関係のない C++ Web フレームワークです。epoll を利用したイベント駆動型エンジンとワーカー スレッド プールを組み合わせることで、高速かつ堅牢な動作を実現します。Linux の基本 API のみを使用し、単一の g++ コマンドでビルド可能で、幅広い Linux 環境で動作します。

全文翻訳

Vermell は、現代の C++ 環境向けの、最小限で、ゼロブロートな Web フレームワークです。高速、構造化、厳密に型付けされています。 Vermell は現代の C++ 環境向けの Web フレームワークです。インクルードするのは1つのヘッダー、リンクするのは1つの静的ライブラリ、それ以外はありません。ランタイムなし、ガベージコレクタなし、フレームワーク固有の DSL なし、ベンダー依存の依存関係なし — あなたが書くのは C++ であり、実行されるのも C++ です。 内部ではイベント駆動型エンジンです。ノンブロッキング epoll ループがリクエストを読み取り、ワーカー スレッドのプールに作業を渡します。この分割が、Vermell を負荷下で高速にし、遅いクライアントに対して回復力を持たせます。 依存関係ゼロ — 基本的な Linux API (ソケット、epoll、pthreads、fork/exec) のみです。 ビルドは1つのコマンド — g++ -std=c++20 server.cpp -o exe -lvermell。 g++ が動作するあらゆる Linux — x86_64、ARM (aarch64、armv7)、Termux 経由の Android、WSL、Raspberry Pi、コンテナ。 デフォルトで強化 — タイムアウト、リクエスト キャップ、接続制限、レンダリング ジャイルが標準でオンです。 インツリー JSON DOM — 厳密な RFC 8259 パーサーとシリアライザー、型付きパラメータ、生のボディ、マルチパートアップロード。 C++ テンプレート — compose() モジュールと render() 変数。 流れるような設定 — 1回の configure({...}) 呼び出しまたはチェイン可能なセッター、実行時に読み取り可能。 📚 完全なドキュメント: vermell.cc — この README の各セクションを例と図でカバーするバイリンガル (EN/ES) マニュアル。 目次 インストール CMake npx Docker クイックスタート コンパイル ルーティングとハンドラ ラムダキャプチャ サーバー設定 MIME タイプとファイルレンダリング 静的ディレクトリ テンプレート: compose & render レンダリング セキュリティ プロセスと環境 例 サポート テスト 貢献 ライセンス インストール CMake $ git clone https://github.com/vermellcc/vermell.git $ cd Vermell $ cmake . $ cmake --build . $ make install npx すぐに使えるスキャフォールド: $ npx create-vermell-static Docker $ docker pull vermellcc/vermell APT (Debian/Ubuntu) GitHub Pages に、署名済みの amd64、arm64、armhf 用パッケージがあり、追加の準備ができています: $ sudo install -d -m 0755 /etc/apt/keyrings $ curl -fsSL https://vermellcc.github.io/vermell/vermell-apt-key.asc | sudo gpg --dearmor --yes -o /etc/apt/keyrings/vermell.gpg $ echo "deb [signed-by=/etc/apt/keyrings/vermell.gpg] https://vermellcc.github.io/vermell stable main" | sudo tee /etc/apt/sources.list.d/vermell.list $ sudo apt-get update $ sudo apt-get install -y libvermell キーフィンガープリント: 022D 56AA 7A6B 2028 B005 3629 F616 54D8 8AD1 C323 クイックスタート Vermell サーバーはルーターです。 ルートにハンドラを登録し、ポートを選択し、listen() を呼び出します。 #include <vermell/vermell.h> int main() { Router router; router.setPort(8080); router.get("/", { [](Query &http) { http.send("Hello from Vermell"); } }); router.listen(); } ハンドラを囲む { ... } が重要です。 router.get(...) の2番目の引数は MiddlewareList なので、ハンドラは常に波括弧のリストとして渡されます: router.get("/", { [](Query &http) { ... } }). コンパイルして実行: $ g++ -std=c++20 server.cpp -o exe -lvermell $ ./exe 次にブラウザまたは curl でアクセスします: $ curl http://localhost:8080/ Hello from Vermell router.listen() はブロックし、永続的にサービスを提供します。 listenOne() は単一のリクエストを処理して返します — テストやワンショットサーバーに便利です。 コンパイル 単一の g++ 呼び出しで全てをコンパイル・リンクします — 追加フラグなし、リンク順序のゲームなし: $ g++ -std=c++20 server.cpp -o exe -lvermell 大規模なプロジェクトには CMake を使用しますが、サーバーは常に1つのコマンドで作成できます。 Portability。 Vermell には依存関係がないため、Linux から派生し C++20 g++ を持つものであれば何でもビルドできます: x86_64、ARM (aarch64、armv7)、Termux 経由の Android、WSL、Raspberry Pi、コンテナ。 macOS と Windows はサポート対象外です (epoll のため)。 ルート権限は不要です。問題ありません。 Termux (またはルート権限のないシステム) では、/usr/local へのインストールはできません。 相対パスでヘッダーをインクルードし (#include "../include/vermell/vermell.h")、静的ライブラリを直接リンクします — libvermell.a をソースの隣にコピーし、-L でコンパイルします。 -lvermell: // Termux / no-root build: // ヘッダーは相対パスで参照 #include "../include/vermell/vermell.h" int main() { Router router; router.setPort(8080); router.get("/", { [](Query &http) { http.send("hi from termux"); } }); router.listen(); } $ cp libvermell.a . # ソースの隣に静的ライブラリを配置 $ g++ -std=c++20 server.cpp -o exe -L. -lvermell $ ./exe ルーティングとハンドラ ルーターは、HTTP 動詞ごとに1つの登録メソッドを提供します。 静的ルートは、透過的なハッシュ ルート マップを通じて O(1) でディスパッチされます。 router.get("/users", { [](Query &web) { web.send("list"); } }); router.post("/users", { [](Query &web) { web.send("create"); } }); router.put("/users/:id", { [](Query &web) { web.send("update"); } }); router.deleteX("/users/:id", { [](Query &web) { web.send("delete"); } }); router.patch("/users/:id", { [](Query &web) { web.send("patch"); } }); router.head("/status", { [](Query &web) { web.send("head"); } }); router.options("/ping", { [](Query &web) { web.send("options"); } }); router.link("/rel", { [](Query &web) { web.send("link"); } }); router.unlink("/unlink", { [](Query &web) { web.send("unlink"); } }); router.purge("/cache", { [](Query &web) { web.send("purge"); } }); deleteX() 名に注意してください: delete は C++ のキーワードです。 大規模なアプリケーションの場合、ルートを個別に宣言し、router.use() でマウントします: // routes.cpp — 分離された宣言 Route_t users_routes("/users/:id", { [](Query &web) { web.json(R"({\"op\":\"get\"})"); } }, GET_TYPE); // main.cpp — マウント router.use(users_routes); router.use(admin_routes); ラムダキャプチャ 各ハンドラは C++ ラムダ void(Query&) です。 [ と ] の間のキャプチャリストは、外部の状態がどのように渡されるかを決定します: string app_name = "vermell-demo"; int port = 8080; // [] — キャプチャなし: ハンドラは Query のみ認識します router.get("/ping", { [](Query &web) { web.json(R"({\"pong\":true})"); }}); // [=] — 外部値はコピーで渡されます: プライベート スナップショット router.get("/name", { [=](Query &web) { web.send(app_name); // 登録時に作成されたコピーを読み取ります } }); // [&] — 外部変数は参照で渡されます: ライブ ビュー router.get("/info", { [&](Query &web) { web.send(app_name + ":" + std::to_string(port)); } }); // 名前付きキャプチャ — 必要なものだけ: // [port] -> port のコピー // [&port] -> port への参照 // [this] -> エンクロージング オブジェクト // [=, &port] -> 全てコピー、port は参照 キャプチャの意味 [] キャプチャなし — ハンドラは Query のみを受け取ります。 [=] 使用される全ての外部変数はコピーで (作成時のスナップショット)。 [&] 使用される全ての外部変数は参照で (ライブ エイリアス)。 [x] / [&x] 名前付きキャプチャ: x のコピー、または x への参照。 [this] エンクロージング クラスをキャプチャします (メンバーは参照)。 [=, &x] 全てコピー、x は参照。 スレッドセーフティ。 ハンドラはワーカー スレッドで実行され、サーバーの全ライフタイムにわたって存続します。 [&] キャプチャは登録スコープへの参照です: listen() より長く存続する変数には問題ありませんが、それより早く消滅するスタック ローカル変数をキャプチャしないでください — それはダングリング参照になります。 リクエストは並行して実行されるため、参照でキャプチャされた共有ミュータブル状態にはミューテックスが必要です。イミュータブル スナップショットには [=] を優先してください。 サーバー設定 リクエスト/レスポンス パイプラインの全てのノブは vermell::Config (include/vermell/config.hpp) にあります。 router.configure({...}) で全体を渡します (デフォルトはレガシー動作を維持します): router.configure({ // network .backlog = SOMAXCONN, // listen() の保留接続キュー .reuse_port = false, // SO_REUSEPORT: デフォルトで OFF (同じ UID のプロセスがポートをバインドしてトラフィックを共有するのを防ぐため) // request reading .read_timeout = std::chrono::seconds{30}, // チャンク間の非アクティブ時間 .request_timeout = std::chrono::seconds{60}, // リクエスト全体が到着するまでの合計デッドライン (スローロリス対策) .write_timeout = std::chrono::seconds{10}, // レスポンス中の非アクティブ時間 .max_request_size = 16UL * 1024UL * 1024UL, // 大きい => 413 Payload Too Large .read_chunk = 32UL * 1024UL, // recv() 呼び出しごとの読み取りバイト数 // concurrency / epoll .threads = 4, // ワーカー スレッド; 0 = 自動 (hardware_concurrency) .max_events = 1024, // epoll イベント バッチ サイズ .max_queue_size = 512, // キューイングされたタ