HNで公開: C Webサーバーを使ったWebサイトホスティング
(github.com/cozis)- BlogTechは、小規模から中規模のWebサイトを管理するためのCベースのツールキットで、HTTPS、バーチャルホスト、ACMEベースの自動証明書管理、リモートサーバー管理クライアントを提供する
- 現在はテスト段階で、バージョン
0.4.xはhttps://coz.is/を数週間運用するのに使われたが、安定版リリースとして提示されているわけではない - サーバーはLinuxとWindowsで動作するが、HTTPSはLinuxのみ対応で、Windowsではクライアントとサーバーの両方がHTTPのみ使用可能
- リモートファイル管理は
--put,--get,--deleteで行い、リソースを変更するリクエストは共有秘密鍵ベースの HMAC/SHA256 署名で認証する - 本番環境ではポート80のHTTPサーバーとドメインのDNSレコードが必要で、ACMEを有効にすると証明書と秘密鍵ファイルを自動生成し、エラーは
acme.logに記録される
BlogTechの目的と現在の状態
- BlogTechは、小規模から中規模のWebサイト管理のためのツールキット
- 対応機能は次のとおり
-
HTTPS
-
バーチャルホスト
- ACMEによる自動証明書管理
- サーバーのリモート管理のためのクライアント
- まだテスト段階
- バージョン
0.4.xでhttps://coz.is/を数週間にわたり正常に提供した実績がある - 以前の単一ファイル版は
single_fileブランチにある
-
ビルドと基本実行
- BlogTechはLinuxとWindowsで動作する
- LinuxビルドにはOpenSSL開発ライブラリと
gccが必要 - Windowsビルドには
clangが必要
- LinuxビルドにはOpenSSL開発ライブラリと
- ビルドはプラットフォーム別スクリプトで行う
- Linux:
./build.sh - Windows:
.\build.bat
- Linux:
- ビルド成果物は次のとおり
- Linux:
blogtech - Windows:
blogtech.exe
- Linux:
- Linuxでは
./install.shでインストール可能 - 基本的なサーバー実行例では
docrootディレクトリを作成し、--serve,--document-root=docroot,--skip-auth-checkを指定する - 基本のHTTPサーバーは
127.0.0.1:8080で待ち受け、docrootのコンテンツを提供する - 空の
docroot状態ではhttp://127.0.0.1:8080/にアクセスすると404が返る
リモートファイル管理と認証
- BlogTechはクライアントモードでファイルをサーバーにアップロードできる
--putでファイルをアップロード--getでリモートファイルをダウンロード--deleteでリモートファイルを削除
- 同じマシン上でクライアントとサーバーを実行しても、リモートマシン上のサーバーに対しても同じように動作する
- リソースを変更するリクエストは HMAC/SHA256 署名でデジタル署名される
- クライアントとサーバーは同じ秘密鍵を共有する必要があり、慣例として
admin.pwdファイルに保存する - パスワードファイルは
--auth-password-fileオプションで指定する - パスワードファイルが存在しない場合、認証が必要なすべてのリクエストは拒否される
- 空のパスワードも拒否される
- 開発中は
--skip-auth-checkですべてのリクエストを認証済みとして扱える- このオプションを使うと、
--auth-password-fileが指定されていても無視される - 注意して使う必要がある
- このオプションを使うと、
-
認証ヘッダーとリプレイ条件
- 認証済みHTTPリクエストには
X-BlogTech-ヘッダーが含まれる X-BlogTech-Nonce: クライアントがランダムに選んだトークンX-BlogTech-Timestamp: リクエストが最初に署名された時点のUNIXタイムスタンプX-BlogTech-Expire: 署名時点からリクエストが有効な秒数X-BlogTech-Signature: リクエスト情報のHMACをBase64エンコードした値- 署名は正規化されたリクエスト文字列を作成したあと、認証パスワードを鍵として HMAC/SHA256 を計算し、その結果をパディング付きBase64でエンコードして得る
- サーバーがすでに見た nonce はメモリに保存される
- サーバーが再読み込みされると以前の nonce を忘れるため、期限切れでないリクエストは再送される可能性がある
- 認証済みHTTPリクエストには
バーチャルホストとディレクトリマッピング
- BlogTechは同じサーバーで複数のWebサイトをホストできる
- ドメインは
--domainオプションを複数回指定して設定する - BlogTechはドキュメントルート内にドメイン別ディレクトリを作成する
defaultwebsiteA.comwebsiteB.com
- 特定ホスト宛てのリクエストは、そのホストに対応するディレクトリを参照する
- 特定フォルダに結び付けられていないリクエストは default ディレクトリを参照する
- 例では
websiteA.com,websiteB.com,other.comにアップロードしたファイルは、それぞれドメインディレクトリまたはdefaultディレクトリに保存される
HTTPSと証明書設定
- HTTPSはLinuxでのみサポートされる
- 基盤となるHTTPライブラリ cHTTP がOpenSSLでHTTPSを実装しているため
- Windowsではクライアントとサーバーの両方でHTTPしか使えない
- HTTPSを有効にするには次のオプションが必要
--https-enabled--cert-file--cert-key-file
- デフォルトのHTTPS待受アドレスは
127.0.0.1:8443 - 待受アドレスとポートは次のオプションで変更できる
--https-addr=<addr>--https-port=<port>
- 開発中はOpenSSLコマンドで自己署名証明書を作成できる
- ブラウザは自己署名証明書を使うHTTPSサーバーの閲覧を許可しない
- cURLでは
--insecureフラグを使わないと自己署名証明書サーバーにアクセスできない - 複数の証明書が必要な場合は
--extra-certを追加で渡せる- 基本証明書は
--cert-file,--cert-key-fileで提供する - 追加証明書はクライアントが特定ドメインをリクエストしたときに使われる
- 基本証明書は
- 長いコマンドラインオプションは設定ファイルへ移せる
ACMEベースの自動証明書発行
- ACME プロトコルは、Webサーバーが認証局に証明書発行を自動で要求できるようにする
- BlogTechは、証明書なしでHTTPSモードを開始し、その後証明書が生成される流れをサポートする
- 本番環境では次の条件が必要
- HTTPサーバーをポート80で実行する
- サーバーが動作するマシンとドメインを結び付けるDNSレコードを作成する
- ACMEは
--acme-enabledをHTTPSオプションと一緒に指定して有効にする - 通常のHTTPSモードと異なり、ACMEモードでは証明書が存在しないことを前提とする
- 証明書生成には次の値が必要
--acme-domain--acme-email--acme-country--acme-organization--acme-agree-tos
- 正常に処理されると次のファイルが生成される
acme_key.pem: ACMEアカウントに紐付いた秘密鍵cert.pem: 発行された証明書、または--cert-fileで指定したファイルkey.pem: 証明書に対応する秘密鍵、または--cert-key-fileで指定したファイル
- エラーが発生した場合、メッセージは
acme.logに記録される --acme-domainを複数回指定すると複数ドメインをACMEで処理できる- 結果の証明書には指定したすべてのドメインが含まれる
ACMEクライアントのテスト
- LinuxでACMEクライアントをテストするには Docker をインストールし、Pebble ACME server をクローンする必要がある
docker-compose.ymlにはテスト用ドメインをhost-gatewayに接続するextra_hosts項目を追加する/etc/hostsにはテスト用ドメインを127.0.0.1にマッピングする- Pebbleは
docker compose upで起動する - BlogTechは
./blogtech -s --config=misc/pebble_blogtech.confでテストインスタンスを実行する - ACMEサーバーとの相互作用は stdout に出力される
- 成功すると
acme_key.pem,cert.pem,key.pemが生成される - 証明書更新テストは
pebble/test/config/pebble-config.jsonのvalidityPeriod値を変更して行える --acme-force-renewal-period=<maximum duration in ms>オプションを使うと、証明書がまだ期限切れでなくても更新するよう指示できる
設定ファイルと自動ロード
- BlogTechは任意個のコマンドライン引数を設定ファイルに移せる
- たとえばHTTPSやACME関連オプションを
blogtech_server.confに書いたうえで--config=blogtech_server.confで実行できる - 設定ファイル名が正確に
blogtech.confなら、BlogTechが自動でロードする - この場合
./blogtech --serveのように実行できる - 暗黙の設定ファイル読み込みを無視するには
--no-configを使う
クラッシュログとsystemdデーモン
- サーバーモードでBlogTechがクラッシュすると
crash.binファイルが生成される - 次回のサーバー起動時に
crash.binは人が読めるスタックトレースであるcrash.logに変換される - アドレスをシンボル名や行番号に変換する処理はやや不安定なことがある
- BlogTechは systemdデーモン としてインストールできる
- 例の
blogtech.serviceは/root/blogtech/blogtech -sを実行し、失敗時に再起動し、作業ディレクトリを/root/blogtech/に設定する - サーバーオプションは
/root/blogtech/blogtech.confから自動ロードされる - サービスファイルを
/etc/systemd/system/にコピーしたあと、次のコマンドで有効化して起動するsystemctl daemon-reloadsystemctl enable blogtechsystemctl start blogtech
- サービス管理は
systemctl start,systemctl stop,systemctl restart,journalctl -u blogtechで行う
1件のコメント
Hacker News の意見
「リバースプロキシ不要」という部分はいつも不思議に思っていた
リバースプロキシが特に役立つ理由がなければ、組み込み Jetty アプリを前段なしでインターネットに直接公開していたし、問題はなかった
インフラやセキュリティ担当者は、なぜ前に nginx を置かないのかと聞いてくるが、理由を尋ねるとセキュリティや性能について漠然と言うだけで具体性に欠ける。最も具体的だった答えは slow loris だったが、それはもうずいぶん前から大きな問題ではなくなっている
リバースプロキシは集団的にカーゴカルトになっているのか、それとも一般的に当てはまる良い理由を自分が見落としているのか気になる
全体としてオリジンサーバーを保護し、関連するトラフィックだけを受けるようにできる。顧客にカスタムドメインを提供する場合でも、顧客の DNS はオリジンサーバーではなくリバースプロキシを指すので、必要に応じてオリジンサーバーを変更しても顧客側の DNS 変更を心配しなくてよい
それぞれ別のポートで動いているが、全部を別々の URL で公開アクセス可能にしたいし、インターネットには443 番ポートだけを公開したい
各ドメインの TLS 証明書の自動更新もしたい。前者の要件にはリバースプロキシが必要で、Caddy は両方をやってくれる
単一サーバーだけを運用し、そのサーバーが TLS 終端まで行うなら、リバースプロキシは必須ではない
後からTLS 終端、URL 書き換え、その他の作業を大きな手間なく追加できるので、習慣のように使うようになり、今のところその習慣は報われている
セキュリティ面では、システム全体で可能な限りコードが少なく堅牢なオペレーティングシステムを使いたい。プロキシ型アプリは Web/アプリケーションサーバーよりはるかに単純にでき、受信トラフィックをフィルタリングしたり、入力を検証したり、より安全かつ高速にパースできる形式へ変換したりもできる。OpenBSD、GenodeOS、INTEGRITY-178B のような攻撃しにくいオペレーティングシステム上で動かすこともできる
可用性の面でも、負荷分散、監視、復旧をこうしたシステムに置くほうが安全な場合が多い。アプリケーションサーバーのほうがより頻繁に落ちる可能性があるためだ
性能面では、単純で集中したアプリを高度に最適化できるという利点がまずある。その後は CPU や PCI ハードウェアアクセラレーターで圧縮や暗号化を高速化でき、これはよくオフロードと呼ばれる。費用対効果が最も高い構成は、多数の汎用サーバーが少数の高価なオフロードサーバーの恩恵を受ける形だ。一部は負荷分散によって、受信トラフィックを最も適切に処理できるサーバーへ送り、高価なリソースの使用を減らす
最小構成では不要だが、単一ホストの運用環境でもローリングリリース、圧縮、TLS、高速な静的ファイル配信、潜在的な A/B テストを可能にしてくれる
リクエストとサーバーの間にある間接層はかなり有用になり得る
すばらしい。私も以前、直接 C の Web サーバーを書いたことがあり、下にソースがある。しばらく商用 Web サイトも運用していた
HTTP/1.1 Web サーバーをどれほど小さく軽量に作れるかには驚く。その商用サイトは RAM 128MB、CPU 1 個のマシンで動いていて、クローズドソースの対話型 Web ベースのチャットシステムとして英国の学校のかなりの数に定期的にサービスを提供していた。ただし、それはインターネットが今ほど敵対的ではなかった 20 年前の話だ
記事ではボットが優れたファザーの役割を果たすと言っていたが、それでも実際のファジングも少しはやってみるのがよいと思う
http://git.annexia.org/?p=rws.git;a=tree
必要なもの:
http://git.annexia.org/?p=c2lib.git;a=tree
http://git.annexia.org/?p=pthrlib.git;a=tree
私の Web サイト https://blessed.rs は見つけられる中で最小の VM なので RAM 256MB のもの上で動いているが、普段は約 60MB しか使っていない
本当に驚いたのは、メモリマップが 4KB ページ 5 枚だけだと Linux の fork が非常に速くなることだった
趣味で余暇に始めた小さなプロジェクトだが、ここなら気に入ってもらえそうだと思った :)
すごい。こういうふうに最も低いレベルのシステム APIを使って最小限のサービスを立ち上げるのは本当に満足感があると昔から思っていたし、今もそう見ている。
ほとんど魔法みたいで、実際のトラフィックをさばいているのを見るとなおさらだ。普通の
poll()があれほどの数値を出せるのは少し驚きだったが、そのレベルでイベントやベンチマークを扱ってからずいぶん経っているからかもしれない。接続ごとの関数と関連する構造体、配列で接続を管理するやり方、
pollファイルディスクリプタ配列も良い。nginx、Redis、memcached のように高スループットで知られるいくつものオープンソースパッケージで採られている方式を思い出す。誰もが、可能な限りあらゆる言語――プログラミング言語であれ自然言語であれ――を知り、試してみるべきだと思うようになった。ある言語で考えるというのは独特の体験だ。異なる文脈はすべてを違って感じさせ、結局は似たようなことをしていても視点が変わる。
たとえば Linux や Git の本質を本当に理解するには、その言語を話し、翻訳ではたいてい失われるニュアンスを理解しなければならない。ロシア語で「森」という単語の本当の主観的な意味を理解するには、ロシア語を話し理解する必要があるのと似ている。
文脈は視点を変え、時にはすべてを変える。
私も普通の
poll()があれほど耐えるのには驚いた。いつかはepollに移行することになると思っていたが、poll()はとてもよく動いている。日の下に新しきものなし。
これも興味深いかもしれない: https://news.ycombinator.com/item?id=27431910
2024年時点で sqlite.org の althttpd インスタンスは、1日50万件以上の HTTP リクエスト、秒間約5〜6件を処理し、1日約200GBのコンテンツ、約18Mbit/sを月40ドルの Linode で提供している。このマシンのロードアベレージは通常 0.5 付近にとどまっている。HTTP リクエストの約19%は、複数の Fossil ソースコードリポジトリに向かう CGI である。
C アプリを書きたいが、公開インターネットに直接触れる部分を自分で書くのは不安なら、Kore は良いフレームワークだ。
ACME 証明書管理、Pgsql、curl、WebSocket などの便利な組み込み機能がある。
基本的にモジュールをビルドして実行し、組み合わせることができ、Lua/Python と C を混ぜることも可能だ。
https://kore.io/
ついにトップページに載っても死なないウェブサイトが出てきた。
このプロジェクトが素晴らしくないという意味ではないが、運用環境でこういう点を本当に重視し、ほとんど静的コンテンツを配信するなら、単に CDN を使えばいい。自分で書くほぼ何よりも、常に高い性能を出すはずだ。ただ、面白くないだけだ。
プロジェクトはすっきりしていて素晴らしいが、ほとんどの人は GitHub に行くのであって、ウェブページを表示するリンクには行かない気がする。何か見落としているのだろうか?
「自分の道具を自分で作るのが好きだし、すべてが実戦検証済みでなければならないと言われるのに少し疲れた。だから落ちたら何だというのか? バグは直せばいい :^)」という部分が好きだ。
C コード3.4k 行だけで完全な HTTP と HTTPS サーバーだって? 仕様に完全準拠するには、正直もっとずっと多く必要だと思っていた。
GET リクエストだけを受け取り、レスポンスに
Content-Lengthを設定すれば、ユーザーエージェントの99%には十分だ。Transfer-Encodingとバイト範囲ヘッダーを扱っても、コードは大して増えない。HTTPS は TLS ソケット上の HTTP にすぎず、自分で暗号を実装しないなら、まさにその程度の抽象化レベルであるべきだ。面白いし、実際それほど悪くない。
httpd(8)[1] も、ドキュメント込みで現在15,000行弱だ。いくつかの機能を外し、いくつかの仮定を置けば、このプロジェクトのように5,000行台に収まっても驚かない。
$ wc -l *の結果は合計14815行。[1]: https://man.openbsd.org/httpd.8
もちろん公開インターネットには載せないし、HTTP/1.1 のごく小さな部分しか実装していないが、動作し、初期化時にだけ
mallocが必要だ。C で書かれたブログ/Web サーバーに関する Chaos Communication Congress の発表を思い出す。
イミュータブルストレージ、権限降格、ブログが TLS 証明書にアクセスできないようにするなど、多くのセキュリティ機能を盛り込んだ内容だった: https://www.youtube.com/watch?v=TaE28fJVPTk