プログラミング
Vermell – epoll を使用した最小限で依存関係のない C++ Web フレームワーク
Vermell – Minimal, dependency-free C++ web framework using epoll (github.com)
要約
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, // キューイングされたタ