フックから Apache の全体像を追う

DSAS開発者の部屋:[補足記事]Apache 2.0 の hook 一覧(apache module 開発事初め その3-3) はモジュールを書く際にどこにフックをしかけるかという点で非常に参考になります。

が,いまだにいまいち Apache からどのように呼び出されるか実感がわきません。ひょっとしてフックの呼び出し方を調べると Apache の処理フローを追っていけるんじゃないか,という無謀な挑戦をしてみました。

対象

前提条件
Apache のモジュールを書いたことがある人; 最低限 ap_hook_handler フックを使ったことがある人
調査対象
Apache 2.2.3 のソース*1

なお,以下に記したソースは,実際のソースの引用ではなく,おもにフックを呼び出している部分のみの抜粋となります。またロジックを損なわない程度に書き換えた部分もあります。さらには引数や戻り値についてもほぼ無視しています。つまり字面としてまったく違う(準擬似)コードになってしまったのですが,流れを追う上でかえってすっきりしたのではないかと思います。

フックの種類について(読み飛ばし可)

VOID タイプのフック

フックに登録されている callback がすべて呼び出されます。そもそもこのタイプのフックの callback は戻り値型が void なのでステータスを返しようがないのですが。

たとえば ap_hook_child_init などのように,(原則エラーの発生しない)各モジュールの初期化を行うような場合に使われます。

RUN_FIRST タイプのフック

サーバはフックを順番に呼び出していきますが,基本的に DECLINE 以外のステータス(=成功 or エラー)を返した場合にそこで呼び出しチェーンがストップするタイプのフックです。成功の場合もそれ以降のフックが呼ばれないところが特殊です。

つまり。Web サーバが「何かを知りたい」「何かをやってほしい」ときに,誰か一人がやってくれればそれでよい場合に使われるフックです。

たとえば,

誰か蕎麦作って!

ピザ屋
いや俺無理 (DECLINED)
中華料理屋
わたしも無理アルよ (DECLINED)
そば屋
できますよー (OK)
うどん屋
(聞かれてないし)
ラーメン屋
(聞かれてないし)

のような感じです。

みなさんお馴染みの ap_hook_handler フックのように,誰か一人がコンテンツを返してくれればよい場合に使われるフックです。逆に全モジュールが返してきたらおかしいですよね。

実際に自分が処理するわけではないんだけど,その時点にフックを仕込みたい,という場合,フック順序に APR_HOOK_FIRST(より極端には APR_HOOK_REALLY_FIRST)を指定してフックする必要があります。またその場合 DECLINED を返すべきです。


「基本的に」と書きましたが,DECLINED な値タイプはフックの仕様設計者が任意に定めることができます。たとえば ap_hook_create_connection フックの場合,callback の戻り値型は conn_rec * 型であり,declined は NULL になっています。いずれかのモジュールが NULL 以外の conn_rec * 型の値を返したらオッケー,ということになります。

RUN_ALL タイプのフック

サーバはフックを順番に呼び出していきますが,基本的に OKDECLINED 以外のステータス(=エラー)を返した場合にそこで呼び出しチェーンがストップするタイプのフックです。

たとえば ap_hook_pre_config のように,各モジュールの設定を行うけれども設定にエラーがある場合は httpd の起動を阻止したい,などの場合に使われるフックです。エラーがない限り全モジュールに対して呼び出す必要があるので RUN_FIRST ではだめですよね。

無理にたとえると

みんな自分の店掃除しといてね by フロアマネージャ

ピザ屋
掃除したよ (OK)
中華料理屋
本日休業アル (DECLINED)
そば屋
掃除したよ (OK)
うどん屋
すいませんゴキブリがでました (ERROR)

……レストランフロア営業停止……

みたいな感じでしょうか。


「基本的に」と書きましたが,RUN_FIRST と同様,OKDECLINED とする値は仕様設計者が任意に定めることができます。とはいうものの,Apache で使われる RUN_ALL なフックは,ほぼすべて int 型の定数値 OKDECLINED になっています。

凡例

いままで ap_hook_child_init のようにフックについて書いてきましたが,これはフックを登録する関数の名称です。

そこで以降では,ap_HOOK_child_init のように hook を大文字にすることで,フックそれ自身(の型)を示すことにしました。

また,フック群を呼び出す場合には ap_run_child_init() のような関数を呼ぶことになるのですが,他の関数との違いが少なく目立ちづらいので,下記では ap_RUN_child_init() のようにあえて run の部分を大文字にしてあります。実際のソースを読むときには気をつけてください。

まずはメインルーチン(server/main.c

/* server/main.c:: */

main() {
    ap_RUN_pre_config();        // RUN_ALL

    if (configtestonly)
        ap_RUN_test_config();   // VOID

    ap_RUN_open_logs();         // RUN_ALL

    ap_RUN_post_config();       // RUN_ALL

    for (;;) {
        ap_RUN_pre_config();    // RUN_ALL

        ap_RUN_open_logs();     // RUN_ALL

        ap_RUN_post_config();   // RUN_ALL

        ap_RUN_optional_fn_retrieve();  // VOID

        if (ap_mpm_run())
            break;
    }
}

設定やログまわりのフックが呼び出されています。これらのフックはマスタプロセスにおいてのみ呼び出されています。各フックの詳細や使用例についてここでは説明しません。ap_run_pre_config() 等が2回行われている理由は mod_perl 2.0 の Server Life Cycle - daily dayflower あたりを参照してください。

最終的に各 MPM の ap_mpm_run() が呼び出されます。エラーが発生したり shutdown 指示がだされたりした場合に ap_mpm_run() が 0 以外で戻ってきますので,結果的に while ループを抜け main() が終了します。restart などの際には ap_mpm_run() が 0 を返すので,再度 while ループが実行されます(よって再度セッティング処理が行われ ap_run_pre_config() 等が呼び出される)。

各 MPM における ap_mpm_run()(Prefork の場合)

Prefork MPM での ap_mpm_run() の実装をみてみます。

/* server/mpm/prefork/prefork.c:: */

ap_mpm_run() {
    ap_RUN_pre_mpm();   // RUN_ALL

    startup_children();

    while (! restart && ! shutdown) {
        ap_wait_or_timeout();

        ap_process_child_status();

        if (needed) {
            make_child();
        }

        perform_idle_server_maintenance();
    }
}

startup_children() {
    for (childrens) {
        make_child();
    }
}

perform_idle_server_maintenance() {
    if (too_many_free_children) {
        kill_child;
    }

    if (needed) {
        make_child();
    }
}

全体的な流れは,

  1. まず startup_children() で子プロセスを用意し
  2. restart や shutdown しない限り以下を繰り返し行う
    1. ap_wait_or_timeout()(後述) で子プロセス終了を待つ
    2. 終了した子プロセスのマネージメントを行い(ap_process_child_status()
    3. 必要に応じて子プロセスを作成し(make_child()
    4. 暇なら perform_idle_server_maintenance() で子プロセス数を調整する

となります。Prefork の挙動をご存知の方なら「ああなるほど」と思われるのではないでしょうか。

ということで make_child() が子プロセス生成に関わっており重要っぽいので引き続き読み進めていきます。

/* server/mpm/prefork/prefork.c:: */

make_child() {
    if (fork() == 0) {
        /* I am child */
        child_main();
    }
}

child_main() {
    ap_RUN_child_init(p, s);                    // VOID

    while (! die_now) {
        listener->accept_func();

        ap_RUN_create_connection(p, s, ...);    // RUN_FIRST

        ap_process_connection(c, ...);
    }
}

fork() して自身が子プロセスの場合 child_main() に移行します。

child_main() ではまず ap_run_child_init()ap_HOOK_child_init フックを呼び出します。各子プロセス生成後に一度だけ呼び出されるフックであり,数多くのモジュールで内部変数の初期化などに使われています。

親プロセスから「終了してね」といわれない限り

  1. accept() して
  2. ap_HOOK_create_connection フックを呼び出し
  3. ap_process_connection() でコネクションを使った処理を行う

ということを繰り返します。

なお,server/core.c において ap_HOOK_create_connection フックとして core_create_conn() という関数を登録しています。他のモジュールによってフック・処理されていない限り,ここで conn_rec 構造体のセットアップをすることになります。

余談: accept() はいいとして listen() はいつ呼び出されているの?

Prefork MPM の場合,ap_HOOK_open_logs フックにおいて ap_setup_listeners() を呼び出しています(名前からすると,なぜそこで,と直感に反しますが)。つまりマスタープロセスにおいてのみリスナをセットアップしています。ap_setup_listener() の実装じたいは server/listen.c に存在します。この文書では触れません。

各コネクションに対する処理(server/connection.c

さきほどの処理ループの最後にでてきた ap_process_connection() という関数は server/connection.c にあります。

/* server/connection.c:: */

ap_process_connection(c, ...) {
    ap_RUN_pre_connection(c, ...);      // RUN_ALL

    ap_RUN_process_connection(c);       // RUN_FIRST
}
  1. ap_HOOK_pre_connection フックを呼び出し
  2. ap_HOOK_process_connection フックを呼び出す

という単純な関数です。

ap_HOOK_process_connection フックは RUN_FIRST フックですので,「誰かこの connection を処理してお願い」ということになるかと思います。トップダウンに読み込んでくると,ここで次にどこにいけばいいのかわからなくなります。


実は modules/http/http_core.c において

ap_hook_process_connection(ap_process_http_connection, NULL, NULL,
                           APR_HOOK_REALLY_LAST);

のように ap_HOOK_process_connection フックを登録しています。よって他のモジュールによってフック・処理されていない限り,ap_process_http_connection() でコネクションごとの処理が行われることになります。

HTTP コネクションの処理(modules/http/http_core.c

では ap_process_http_connection() の実装をみてみましょう。

/* modules/http/http_core.c:: */

ap_process_http_connection(c) {
    while (ap_read_request(c)) {
        if (HTTP_OK)
            ap_process_request(r);
    }
}
  1. ap_read_request() でリクエストを読み込んで
  2. エラーがなければ ap_process_request() でリクエストを処理する

ということをリクエストが続く限り行います。

いうなれば,この関数で単一コネクションから各リクエストを分離していることになります。もちろん旧来の HTTP であれば 1 コネクションにつき 1 リクエストなのですが,Keep-Alive の場合 1 コネクションに複数のリクエストが到達するのでこのようになっています。

まずはリクエストを読む(server/protocol.c

まずは ap_read_request() のほうからみていきます。

/* server/protocol.c:: */

ap_read_request(c) {
    ap_RUN_create_request(r);           // RUN_ALL

    try {
        /* 実際にリクエストを読み込んだり */

        ap_RUN_post_read_request(r);    // RUN_ALL
    }
    catch (any_error) {
        ap_RUN_log_transaction(r);      // RUN_ALL
    }
}

最初に ap_HOOK_create_request フックが呼び出されます。リクエストごとに一番最初に呼び出されるのでリクエストごとにモジュールのステートを(request_rec->notes などを使って)初期化したい場合,このフックをもちいるとよいでしょう。

次にリクエストを実際に読んでから ap_HOOK_post_read_request フックを呼び出します。

もしこれらの一連の流れでエラーが発生した場合,ap_HOOK_log_transaction フックを呼び出して上位にエラーとして戻ります。つまり,一旦リクエストの処理が始まれば,エラーがおきようと最後に必ず ap_HOOK_log_transaction フックが呼び出されることになります。リクエストごとのファイナライズ処理を行いたい場合(名前と裏腹ですが)このフックを利用することができます。

おっと,もしこれらのシーケンスが成功裏に終わった場合 ap_HOOK_log_transaction が呼び出されない感じがします。が,その場合でもちゃんと呼び出されます(後述)。

次にリクエストを処理する(modules/http/http_request.c

ap_read_request() でリクエストを読んだ後(そして成功した場合)は,ap_process_request() でリクエストを処理します。処理しますというより,HTTP リクエスト処理の全体的なフレームワークなのですが。

/* modules/http/http_request.c:: */

ap_process_request(r) {
    ap_RUN_quick_handler(r, ...);       // RUN_FIRST

    if (DECLINED) {
        ap_process_request_internal(r);

        if (OK)
            ap_invoke_handler(r);
    }

    if (OK) {
        ap_finalize_request_protocol(r);
    }
    else {
        ap_die(status, r);
    }

    ap_RUN_log_transaction(r);          // RUN_ALL
}

まずは ap_HOOK_quick_handler フックを呼び出します。いずれかのモジュールがこのフックで処理を行った場合は,即終了となります。

ap_HOOK_quick_handlder フックが何の処理も行わなかった場合,ap_process_request_internal() 関数でリクエストの処理(リクエストの加工やアクセス制限やハンドラの決定など)を行います。ここがうまくいった場合,ap_invoke_handler() でほんとのほんとに実際の処理を行います。

処理するのが quick だろうが internal process だろうが,最終的にはさきほど述べた ap_HOOK_log_transaction フックをキッチリ呼び出します。

2008-11-05 追記: ap_finalize_request_protocol() 等を追加しました。あんまり重要じゃなかったですけど,自分用。

余談: ap_HOOK_quick_handler フックについて

文字通り quick にレスポンスを返すことのできる場合に使うべきフックです。たとえば DB サーバに強く依存しているウェブアプリサーバの場合,DB サーバが落ちていたときにすぐさま Status 500 系エラーを返す,などのモジュールが考えられます*2。このようなモジュールを作ると Apache 自体の負荷を軽くすることができます。

ただし,後述するアクセス制限(AAA)などが行われないことに注意してください。実際,付属モジュールで唯一 ap_HOOK_quick_handler を利用している [http://httpd.apache.org/docs/2.2/mod/mod_cache.html:title=mod_cache] のドキュメントには以下のような注意書きがあります。

使用方法については注意する必要があり、 AllowDeny ディレクティブを迂回する設定もできてしまいます。 ホスト名やアドレスや環境変数に基づいてクライアントからの アクセスを制限したい場合は、キャッシュ機能を有効にすべきでは ありません。

mod_cache - Apache HTTP Server Version 2.2

quick じゃないリクエスト処理の端緒(server/request.c

リクエスト処理で一番大掛かりな関数である ap_process_request_internal() を見ていきましょう。対象リソースの決定やアクセス制限など,多くの処理が詰まっています。

/* server/request.c:: */

ap_process_request_internal(r) {
    if (! file_req) {
        ap_RUN_translate_name(r);       // RUN_FIRST
    }

    ap_RUN_map_to_storage(r);           // RUN_FIRST

    if (i_am_main_request) {
        ap_RUN_header_parser(r);        // RUN_ALL
    }

    if (not_authed_yet_in_any_ancestor_requests) {
        if (Satisfy in [ ALL, NOSPEC ]) {
            ap_RUN_access_checker(r);           // RUN_ALL

            if (some_auth_required) {
                ap_RUN_check_user_id(r);        // RUN_FIRST
                ap_RUN_auth_checker(r);         // RUN_FIRST
            }
        }
        else if (Satisfy == ANY) {
            ap_RUN_access_checker(r);           // RUN_ALL

            if (FAILED) {
                if (some_auth_required) {
                    ap_RUN_check_user_id(r);    // RUN_FIRST
                    ap_RUN_auth_checker(r);     // RUN_FIRST
                }
            }
        }
    }

    ap_RUN_type_checker(r);     // RUN_FIRST

    ap_RUN_fixups(r);           // RUN_ALL
}

まず

  1. ap_HOOK_translate_name
  2. ap_HOOK_map_to_storage
  3. ap_HOOK_header_parser

を呼び出しています。

ap_HOOK_translate_name フックは,URI からリソースのファイル名を決定するフックで,たとえば [http://httpd.apache.org/docs/2.2/mod/mod_userdir.html:title=mod_userdir] で用いられています([]http://example.com/~foo/bar.html[]/home/foo/public_html/bar.html)。つまり request_rec->filename を設定するフックです。

ap_HOOK_map_to_storage フックはあまり利用されていません。core モジュールではリソースの実在(たとえばさきほど設定した request_rec->filename)を確認したりしています(存在しない場合にエラーを返すなど)。

ap_HOOK_header_parser フックはリクエストヘッダを使った処理を行いたい場合に使うフックです。といっても [http://httpd.apache.org/docs/2.2/mod/mod_setenvif.html:title=mod_setenvif] くらいでしか使われていません。もちろんこのステージじゃなくてもヘッダ情報を取得できるのでここに仕掛ける必要はないのかもしれませんが,逆に「ここのステージに至った時点でリクエストヘッダを利用できるよ」と考えればよいでしょう。


アクセス制限処理については次のセクションで説明します。

アクセス制限処理(AAA; 認証,承認,アクセス制御 - Authentication and Authorization - Apache HTTP Server Version 2.2

アクセス制限処理では以下の3つのフックを使用しています。

  • ap_HOOK_access_checker
  • ap_HOOK_check_user_id
  • ap_HOOK_auth_checker

ap_HOOK_access_checker フックは,アクセス制御(Access Control)処理を行うフックです。アクセス元(ドメインや IP アドレス)に基づく制御([http://httpd.apache.org/docs/2.2/mod/mod_authz_host.html#allow:title=Allow][http://httpd.apache.org/docs/2.2/mod/mod_authz_host.html#deny:title=Deny])が一番有名なアクセス制御です。

ap_HOOK_check_user_id フックは,認証(Authentication)処理を行うフックです。「認証」とは「あなたは誰?」ということを決定することです。「あなたは誰だかわからないからアクセスさせない」という意味でアクセス制限がおこなわれる可能性もあります。たとえば [http://httpd.apache.org/docs/2.2/mod/mod_auth_basic.html:title=mod_auth_basic] がこのフックを利用しています。

ap_HOOK_auth_checker フックは,承認(Authorization)処理を行うフックです。「承認」とは「あなたは誰それだからアクセスしてよい/よくない」ということを決定することです。たとえば [http://httpd.apache.org/docs/2.2/mod/mod_authz_user.html:title=mod_authz_user] によってアクセス可能なユーザや valid-user を指定することができます。またファイルの owner に基づいた制限を行う [http://httpd.apache.org/docs/2.2/mod/mod_authz_owner.html:title=mod_authz_owner] もこのフェーズでフックを行っています。

Satisfy ディレクティブに対応するため,フックの呼び出しがちょっとややこしくなっています。この部分を再掲します(ロジックをなるべく保ったまま元のコードから書き換えてあります)。

        if (Satisfy in [ ALL, NOSPEC ]) {
            ap_RUN_access_checker(r);           // RUN_ALL

            if (some_auth_required) {
                ap_RUN_check_user_id(r);        // RUN_FIRST
                ap_RUN_auth_checker(r);         // RUN_FIRST
            }
        }
        else if (Satisfy == ANY) {
            ap_RUN_access_checker(r);           // RUN_ALL

            if (FAILED) {
                if (some_auth_required) {
                    ap_RUN_check_user_id(r);    // RUN_FIRST
                    ap_RUN_auth_checker(r);     // RUN_FIRST
                }
            }
        }

SatisfyAll か指定されていない場合,AAA の3つのフックが呼び出されます。ただし失敗した時点で呼び出し元に帰るので,たとえば ap_RUN_access_checker() が「拒絶」した場合 ap_RUN_check_user_id() 以降は呼び出されません。SatisfyAny の場合は,ap_RUN_access_checker() が成功した場合にはそのまま次のステートに以降しますが,失敗した場合に ap_RUN_check_user_id(r) 等を実行してアクセス可否を決定します。

最後に私感ですが。認証と承認という2つのフェーズに分割されているため結果的にごちゃごちゃした構成になっているように感じます。さらに [http://httpd.apache.org/docs/2.2/mod/mod_auth_basic.html:title=mod_auth_basic] の場合,[http://httpd.apache.org/docs/2.2/mod/mod_auth_basic.html#authbasicprovider:title=AuthBasicProvider] ディレクティブによって認証 DB バックエンドに処理が流れるため,処理を追うのが大変です。もっとシンプルな枠組みだったらよかったのにと思います。

2008-11-05 追記: https://www.codeblog.org/blog/inoue/20060112.html あたりを参照するとそうそう枠組みを変えられないかなぁとも思いました。

リクエストの最終準備

アクセス制限処理が無事おわった場合(アクセス許可の場合),以下の2つのフックが呼び出されます。

  1. ap_HOOK_type_checker
  2. ap_HOOK_fixups

ap_HOOK_type_checker フックは,リクエストの内容やリソースの種別に応じてレスポンスを変えたい場合に用います。たとえば [http://httpd.apache.org/docs/2.2/mod/mod_mime.html:title=mod_mime] はコンテンツの種別に応じて Content-Type レスポンスヘッダを調整しますし,[http://httpd.apache.org/docs/2.2/mod/mod_negotiation.html:title=mod_negotiation] はリクエストヘッダで指定された言語に応じて返すべきコンテンツを決定します(コンテントネゴシエーション)。この2つの例はまったく異なる方向性のモジュールですが,このことからわかるようにだいたい「リクエストやリソースの種類」にまつわることならなんでもいいみたいです(そのわりに RUN_FIRST なんだよなぁ)。

ap_HOOK_fixups フックは,まさにリクエスト構造体の最終調整を行うフェーズです。[http://httpd.apache.org/docs/2.2/mod/mod_dir.html:title=mod_dir]((URL の末尾の / が欠損したディレクトリへのリクエストの場合,/ をつけた URL にリダイレクトする。)) や [http://httpd.apache.org/docs/2.2/mod/mod_speling.html:title=mod_spelling]*3 などはいかにもここでやるのにふさわしい作業ですが,リクエスト準備の最終段階で呼び出されることから,それ以外にも数多くの標準モジュールで利用されています。


セクション冒頭でも書きましたが,これらのフックはアクセス制限フェーズを無事通過しないと呼ばれません。つまりファイナライズのためにこれらのフックを利用することはできないということです((前述しましたが ap_HOOK_log_transaction フックを利用します。))。

コンテンツハンドラを呼び出す(server/config.c

いよいよコンテンツに応じたハンドラを呼び出すフェーズです。

/* server/config.c:: */

ap_invoke_handler() {
    ap_RUN_insert_filter(r);    // VOID

    ap_invoke_filter_init(input_filters);
    ap_invoke_filter_init(output_filters);

    ap_RUN_handler(r);          // RUN_FIRST
}

まず ap_HOOK_insert_filter フックを呼び出し,そののちに input_filteroutput_filter の初期化を行います。リクエストに応じたカスタムフィルタを挿入するにはわりと適したフックのように感じますが,一般的にフィルタは [http://httpd.apache.org/docs/2.2/mod/core.html#setinputfilter:title=SetInputFilter][http://httpd.apache.org/docs/2.2/mod/core.html#setoutputfilter:title=SetOutputFilter]
ディレクティブによって明示的に組み込まれることと,モジュール内部でプライベートフィルタを挿入する場合でもより以前のフェーズで組み込まれることが多いことから,このフックはあまり使われていません。

ap_HOOK_handler フックについてはもはや説明の必要はないでしょう。モジュールを作ったことのある方なら,ほぼ間違いなく ap_hook_handler() でハンドラを登録したことがあるはずです。

落ち穂拾い

ここまでのステージに出現しなかったフックについて説明します。

/* modules/http/http_protocol.c:: */

ap_send_error_response() {
    ap_RUN_insert_error_filter(r);      // VOID

    /* エラー用コンテンツの生成と送信 */
}

ap_send_error_response() 関数はエラーが発生した時にエラー用コンテンツ(404 などみなさんお馴染みのあの画面)を生成するために Apache が呼び出す関数です。ap_HOOK_insert_error_filter フックはこの最初の段階で呼び出されます。

ap_HOOK_insert_filter フックの内部エラー画面版という位置づけであり,あまり使われていません。[http://httpd.apache.org/docs/2.2/mod/mod_headers.html:title=mod_headers][http://httpd.apache.org/docs/2.2/mod/mod_expires.html:title=mod_expires] で使われていますが,ap_hook_insert_filter() するからおまけにこいつもやっとくか,みたいな感じです。

/* server/log.c:: */

log_error_core() {
    /* ... */

    ap_RUN_error_log(...);      // VOID
}

ap_HOOK_error_log フックはロギング処理共通関数において最後に呼び出されます。つまりすでに stderr や syslog やログファイルに出力された後に呼び出されるので,ログ出力内容を加工するなどの用途に用いることはできません。

/* server/mpm_common.c:: */

sig_coredump() {
#if AP_ENABLE_EXCEPTION_HOOK
    run_fatal_exception_hook();
#endif
}

run_fatal_exception_hook() {
    ap_RUN_fatal_exception(...);    // RUN_ALL
}

ap_HOOK_fatal_exception フックは coredump を吐くような致命的エラーが発生した場合に呼び出されます。が,--enable-exception-hookconfigure してビルドした場合のみ利用されるようです。まぁデバッグ時の最終手段ですよねー。

/* server/mpm_common.c:: */

ap_wait_or_timeout() {
    if (some_interval) {
        ap_RUN_monitor(p);          // RUN_ALL
    }

    /* タイムアウトつき子プロセス終了まち */
}

ap_wait_or_timeout() 関数は ap_mpm_run() 関数の while ループで呼び出されていました。そのコードと上記コードを読むとおわかりのとおり,ap_HOOK_monitor フックはマスタープロセスにおいて,あるていどの時間間隔で呼び出されるフックです。リソース監視などにどうぞ。

/* include/httpd.h:: */

#define ap_default_port(r)  ap_run_default_port(r)
#define ap_http_scheme(r)   ap_run_http_scheme(r)

ap_HOOK_default_port フックと ap_HOOK_http_scheme*4 フックは,上記のように #define になってます。つまりサーバのライフサイクルのどこかのフェーズで呼び出されるというより,コード内で ap_default_port() を呼び出すたびにフックが走って default port を算出する,というイメージです。

フックのまとめ

順を追って説明してきたので見通しが悪くなりました。ハンドラに絞って擬似コードでまとめておきます。

main() {
    ap_RUN_pre_config();    // RUN_ALL
    ap_RUN_test_config();   // VOID
    ap_RUN_open_logs();     // RUN_ALL
    ap_RUN_post_config();   // RUN_ALL

    for (;;) {
        ap_RUN_pre_config();    // RUN_ALL
        ap_RUN_open_logs();     // RUN_ALL
        ap_RUN_post_config();   // RUN_ALL
        ap_RUN_optional_fn_retrieve();  // VOID

        ap_mpm_run() {
            startup_children() {
                make_child(child_main);
            }

            ap_RUN_pre_mpm();

            ap_wait_or_timeout() {
                ap_RUN_monitor();   // RUN_ALL
            }

            make_child(child_main);
        }
    }
}

child_main() {
    ap_RUN_child_init();                        // VOID

    ap_RUN_create_connection();                 // RUN_FIRST

    ap_process_connection() {
        ap_RUN_pre_connection();                // RUN_ALL

        ap_RUN_process_connection();            // RUN_FIRST
        {
            ap_process_http_connection() {
                ap_read_request() {
                    ap_RUN_create_request();        // RUN_ALL
                    ap_RUN_post_read_request();     // RUN_ALL
                }

                ap_process_request() {
                    ap_RUN_quick_handler();     // RUN_FIRST

                    ap_process_request_internal() {
                        ap_RUN_translate_name();    // RUN_FIRST
                        ap_RUN_map_to_storage();    // RUN_FIRST
                        ap_RUN_header_parser();     // RUN_ALL

                        ap_RUN_access_checker();    // RUN_ALL
                        ap_RUN_check_user_id();     // RUN_FIRST
                        ap_RUN_auth_checker();      // RUN_FIRST

                        ap_RUN_type_checker();      // RUN_FIRST
                        ap_RUN_fixups();            // RUN_ALL
                    }

                    ap_invoke_handler() {
                        ap_RUN_insert_filter();     // VOID

                        ap_RUN_handler();           // RUN_FIRST
                    }
                }

                ap_RUN_log_transaction();       // RUN_ALL
            }
        }
    }
}

*1:RHEL 5 で使われているので。最新版じゃなくてごめんなさい。

*2:ただし DB サーバの生存確認が軽い処理である必要がありますが。

*3:リソースが見つからない場合,URL のスペルミスや大文字小文字違いを疑い,リソースを探索する。

*4:2.0 系列から名前が変わったんですかね。