2007-02-18

最近読んだ本: The Apache Module Book

現実逃避で少し Apache のソースを読んでいた. その資料探しにぐぐっていて発見. なかなかよく書けていた. 満足. (表紙のぞく.) 500 ページくらいあって身構えるけど, なぜか巻末に HTTPの RFC やら ASF ライセンスやらが付いていて 150 ページくらい水増しされていた. 実際は 350 ページくらい. コードも多く, 手軽に読める.

まず Apache のアーキテクチャを概観し, APR, モジュール基本, コンテンツ生成, ヘッダ書き換え, 認証, フィルタ, 設定, デバッグ技法...とつづく. 新しい本だけあって Apache 2.2 の話題もある. けっこう網羅的な気がする. (気のせいかも知れない. 網羅されてない話があってもわからないし...)

実のところモジュール用にどんな API があるかはソースを持ってきて ヘッダや実際のモジュールを眺めればだいたいわかる. 実装も見ればまあまあわかる. だからこの本の有難味は API 自体の解説よりも, 著者の持つ Apache 内部に関する見識にあると思う. たとえばある API の説明をするとき, それを使っているモジュールを(半分くらいはコードつきで)紹介してくれる. 中にはサードパーティ製のモジュールもある. そういう cross cutting な話題はコードを読んでいてもわからない. 実装の詳細だけに Web にも説明はない. 本を読む甲斐がある.

モジュールの実装方法にしても, コードを読むだけだとわかりにくい部分は多い. たとえば Apache 2.x には "フィルタ" という仕組みがある. イメージとしては Java の InputStrem, OutputStream のデコレータ・パターンを HTTP のフレームワークにしたようなもの. (モジュールからデコレータを追加できる.) フィルタには不変条件がある. 具体的には HTTP ヘッダとデータ本体の整合性を 保っておく必要がある. けっこうしんどい. だってフィルタしたらふつう Content-Length が変わるでしょ. それをいちいち面倒みるのかよ...と思うわけ. そう煽ってから, mod_filter の支援をうけると 手間を軽減できますよ, と話が続く. この手の "開発者の常識" の話がまとまっており, いちいち感心しながら読んだ.

もっとも本の有難味が際立つのは Apache 公式の開発者文書が少ないせいかもしれない. オープンソースで文書が手薄になるのは仕方がないけれど, Apache は知名度と文書の量が釣り合っていない気はする. ユーザ向けマニュアルAPR の文書 が充実しているだけ にギャップを感じる. まあモジュールを書く人の総数を考えれば妥当なのかもしれない. この本が継続的に改版されていけばいいなあ. 日本語では "Apacheモジュール プログラミングガイド" が けっこう良く書かれていた気がする(売ってしまい確認できず)けれど, 今のところアップデートされる様子はない. (書籍サイト.)

Apache 実装所感

Apache のコード読み解説は 先達 がいる. しかもかなり詳しい. 私は読んだと言ってもつまみ食いっぽく眺めただけで, こう本格的には見てない. そんな程度ではあれど, 印象をすこし.

その由来から, Apache のコードはさぞやグダグダの魔窟なのだろうと想像してしまう. 実際は, (すくなくとも Apache 2.2 に限れば) かなりクリーンな部類に入ると思う. 1.3 から 2.x で大改造があったのかもしれない.

行数を数えると全部でおよそ 30 万行あった. でかいけれど, これも実際の感触とは距離がある. ディレクトリ構成はこんなかんじ:

      496 /build
     8697 /include
   111891 /modules
     3496 /os
    38533 /server
   140793 /srclib
     8944 /support
     1840 /test
   314690

いちばんでかい srclib ディレクトリ(15万行)には APR と PCRE が入っている. 外部ライブラリとみなしていい. (APR はほとんど Apache の一部だけれど, モジュールとしての独立性が高いおかげで複雑さへのインパクトはすくない.) os, support, test あたりも割とどうでもいい. module はプラグインだからとりあえず無視すると, 結局本体は server と include だ. あわせて 5 万行弱. 眺めるだけなら許容範囲のサイズに思える.

ただし実際に読んでみると module の一部は本体から切り離せないことがわかる. 具体的には modules/http 以下がほぼ必須. 5000 行くらいある. 一方で server/mpm は 1.5 万行あるが, 複数の排他なモジュールからなる. 個々の MPM 実装は 数千行と小さい. 素朴な prefork MPM は 1500 行くらい.

このくらいの規模だから, main から順に眺めていっても路頭に迷う感じはしない. 何か実用的なサーバのコードが読みたい欲求を満たすには手頃だと思う. まあ, これより小さいサーバは色々ありそうだけど.

複雑さとしょぼさ

Apache のコードはいいかんじにしょぼい. "しょぼい" というと聞こえが悪いけれど, 要は複雑でない. とはいえシンプルと言うには少し抵抗がある. もう少し色々抽象化したり, モジュールを書くにもフレームワーク(本体)で肩代りして欲しいと感じるものがある. マイクロカーネル的なミニマルさとは少し違う.

たとえば, Apache のモジュールはフックを実装する. このフックは "すべての" HTTP リクエストに対して毎回呼ばれる. URL に応じて配送するような仕組みはない. かわりに各フックの実装が呼び出しの冒頭で文脈情報をチェックし, 自分と無関係な処理ならすぐに return する. こんなかんじ.

/* mod_cgi.c */
static int cgi_handler(request_rec *r)
{
   ....
   if(strcmp(r->handler, CGI_MAGIC_TYPE) && strcmp(r->handler, "cgi-script"))
       return DECLINED;
   ....
   /* ここで本体の処理 */
   ....
   return OK;              /* NOT r->status, even if it has changed. */
}

しょぼい. Rails の Route は無理にしても, web.xml くらいはないものかと思ってしまう.

他には設定ファイルのコンテナもしょぼい. コンテナはスコープをあらわす設定ファイルの記述のこと. XML っぽく < と > でくくるやつね. こんなの:

<IfModule mod_userdir.c>
    UserDir Sites
</IfModule>

コンテナの語彙 (この例だと "IfModule") は, モジュールで拡張できる. どんな仕組みがあるのかと思ったら, 設定ファイルでの命令定義の特殊バージョン扱いらしい. 命令というのは, DocumentRoot とか AllowOverride とか, ああいうの. こういう命令を定義するのと同じ仕組みでスコープの語彙も定義している.

なので, たとえば DocumentRoot 命令を登録するコードがこんなかんじだとすると...

/* core.c */
AP_INIT_TAKE1("DocumentRoot", set_document_root, NULL, RSRC_CONF,
  "Root directory of the document tree"),

IfModule はこうなる:

AP_INIT_TAKE1("<IfModule", start_ifmod, NULL, EXEC_ON_READ | OR_ALL,
  "Container for directives based on existance of specified modules"),

"<IfModule" の先頭の "<" がいかにもハックくさい. start_ifmod() ではこのコンテナの終わりまで読み進むような処理をする. しょぼい. SAX パーサみたいなのはないのかと思ってしまう.

Apache のコードは全体にこうしたしょぼさが漂っている. とはいえ, このしょぼさは悪くない.

ふつう他人のコードを読んでいると, オーバエンジニアリングといいたい複雑な仕組みが何かしら目につく. 仕方ない. オーバーエンジニアリングの欲求を抑えるのは難しい. でも, Apache のコードからはそういう過剰な複雑さをほとんど感じない. 素朴にしょぼい. 意表を突かれるのはいつも想像を超えたしょぼさであって, 想像を超えた複雑さではない. 一定以上の規模を持つソフトウェアから複雑さが匂わないとしたら, それは大きな勝利だと思う. Apache は今のところその勝利を手にしているように見える. 実際にコードベースで作業をしてみると印象は違うかもしれないけどね.

しょぼさが複雑さに勝つのは C 言語固有の現象かもしれない. C 言語には大した抽象化の仕組みがない. だから C 言語プログラマはそれぞれ独自な方法で他の言語をエミュレートする. Cでオブジェクト指向 はその典型. 具体例としては Glib がわかりやすい.

やってみるとこの路線はしんどい. 抽象化のなかには言語の支援がないとオーバーヘッドの割に合わないものがある. モダンな高級プログラミング言語をさわるとその感覚を見失いがちだ. 一方で何も抽象化をせず力技を頼っていると, あっという間に惨状に至る. Apache のコードは抽象化のドグマに陥ることなく, かといってハッカー的な勢いと力技で押し切ることもない. うまいトレードオフをしている気がする.

C 言語なのがそもそも...なんて大人気ない発言は自粛いたしますです.