Apache/設定ファイル読み込みを読むではserver/main.cのap_mpm_run関数を呼び出す前までを読みました。それでは、ap_mpm_run関数に進んでリクエストの処理にモジュールがどう絡んでくるのかを見ていくことにしましょう。
なお、MPMはpreforkを対象とします。
実はap_mpm_run関数が呼ばれた時点ですでに受信ソケットの設定が行われています。いつの間に行われたのかを見てみましょう。
受信ポートの指定はListenディレクティブで行いますがListenディレクティブはpreforkモジュールで提供されています。ListenはどのMPMでも共通のため、include/ap_listen.hに書かれているマクロで指定されています。
static const command_rec prefork_cmds[] = { ... LISTEN_COMMANDS,
#define LISTEN_COMMANDS \ ... AP_INIT_TAKE_ARGV("Listen", ap_set_listener, NULL, RSRC_CONF, \ "A port number or a numeric IP address and a port number, and an optional protocol"), \
ap_set_listener関数はserver/listen.cに書かれています。ap_set_listener関数では引数を解析した上でalloc_listener関数が呼び出されています。
alloc_listener関数ではまず指定されたアドレスとポートの組がすでにあるか調べ、あればソケットを作らずに再利用しています。old_listenersがいつ設定されるかというとpre_configフックで設定されます。
static int prefork_pre_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp) { ... ap_listen_pre_config();
AP_DECLARE(void) ap_listen_pre_config(void) { old_listeners = ap_listeners; ap_listeners = NULL;
pre_configフックはap_mpm_run関数が呼ばれるまでに2回呼ばれるので2回目に呼ばれたときはソケットを作らないで再利用されることになります。1回目の場合はソケットを作ってap_listenersにつないでいます。一応、ソケット周りの関数に詳しくない方のためにaprの該当関数へのリンクを提供しておきます。
次にopen_logsフックで呼ばれるprefork_open_logs関数を眺めましょう。引数で渡されるserver_recをap_server_confにキャッシュした後、ap_setup_listeners関数を呼び出しています。
ap_setup_listeners関数に移ります。まず、サーバのプロトコルが設定されています。server_rec側のポートは0*1なので"http"が設定されることになります。次に、open_listeners関数が呼び出されています。
open_listeners関数では、まだ各ソケットはアクティブではないのでmake_sock関数が呼び出されることになります。make_sock関数ではいろいろなソケットオプションを設定した上でbindとlistenを行っています。また、accept_funcとしてunixd_accept関数(MPM_ACCEPT_FUNC)が設定されています。unixd_accept関数はos/unix/unixd.cに書かれています。
それではap_mpm_run関数を見ていくことにしましょう。preforkのap_mpm_run関数はserver/mpm/prefork/prefork.cに書かれています。
初めの方は飛ばして、one_processが1かどうかで処理が分かれていますが真の方はデバッグ用途なようなので無視します。elseのすぐ下からインデントが戻ってますがまだ当分else内なので注意してください。
最初はis_gracefulは0なのでstartup_children関数が呼び出されます。startup_children関数ではap_scoreboard_imageとスコアボードが利用されています。いつの間に作られたかというと、coreモジュールのregister_hooks関数にて、
ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE);
とpre_mpmのフックとしてap_create_scoreboard関数が呼び出されています。 startup_children関数は引数分のmake_child関数を呼ぶだけ*2なのでmake_child関数に進みましょう。
make_child関数ではその名の通り子プロセスを作っています。子プロセス側の処理のRAISE_SIGSTOPはgdb用のハンドラでinclude/httpd.hに、AP_MONCONTROLはgprof用の処理でinclude/ap_mpm.hに定義されています。子プロセスはその後、child_main関数を実行することになります。
child_main関数ではいろいろ初期化を行った後、リクエスト待ち状態に入っています。80(http)と443(https)のように複数のポートがある場合はポーリングとラウンドロビンによりどのポートのリクエストを処理するかを決定しています。その後、accept_funcを呼び出し、create_connectionフックを実行し、ap_process_connection関数を呼び出しています。
include/http_connection.hに書いてありますがcreate_connectionフックはRUN_FIRSTフックで普通はこのフックにフックを登録するべきではないとのことです。create_connectionフックの処理はcoreモジュールのcore_create_conn関数が行っています。core_create_conn関数ではconn_rec構造体にリクエストを処理するのに必要な情報を格納したものを返しています。
ap_process_connection関数はserver/connection.cに書かれていますが非常に単純です。pre_connectionフック(RUN_ALL)を呼んだ後、process_connectionフック(RUN_FIRST)を呼ぶだけです。
pre_connectionフックをかけているモジュールを探してみるといろいろ見つかります。coreモジュールはインプットフィルタとアウトプットフィルタを登録しています。また、sslモジュールはSSL接続の初期化を行っているようです。
process_connectionフックの処理はhttpモジュール(modules/http/http_core.c)が行っています。同期版と非同期版がありますが同期版のap_process_http_connection関数を眺めましょう。大雑把に言うと、ap_read_request関数を呼び出してリクエストを取得し、ap_process_request関数を呼び出してリクエストを処理しています。
ap_read_request関数はまたserverディレクトリに戻ってきて、protocol.cに書かれています。request_rec構造体を設定し、途中でcreate_requestフックを呼び出しています。create_requestフックはcoreモジュールとhttpモジュールがかけているようです。httpモジュールではアウトプットフィルタが登録されています。
次にread_request_line関数を呼び出してリクエストを取得しています。リクエスト取得のメイン処理はap_rgetline関数で行われていますがASCII環境ではap_rgetline関数はap_rgetline_core関数を呼び出すだけのマクロなようです。ap_rgetline_core関数はap_get_brigade関数を呼び出しています。ap_get_brigade関数はserver/util_filter.cに書かれていますが、インプットフィルタを呼び出しているだけです。登録されているインプットフィルタはserver/core_filter.cに書かれているap_core_input_filter関数です。モードがAP_MODE_GETLINEの場合は一行読み込むだけなようです。その後、ap_parse_uri関数を呼び出して受け取ったパスを解析しています。
ap_read_request関数に戻ります。assbackwardsはinclude/httpd.hに書かれているrequest_rec構造体の説明によるとHTTP/0.9のときにセットされるようなので、普通はap_get_mime_headers_core関数が呼ばれるでしょう。その後、インプットフィルタに関数が追加されています。最後にpost_read_requestフックを呼び出しています。
ap_process_request関数はmodules/http/http_request.cに書かれています。まず、RUN_FIRSTなquick_handlerフックが呼び出されています。普通はフックが提供されてないようなのでap_process_request_internal関数が呼ばれてap_invoke_handler関数が呼ばれることになるようです。
ap_process_request_internal関数はserver/request.cに書かれています。初めのfile_reqはサブリクエストではないのでmainがNULLで0になります。というわけでap_location_walk関数が呼ばれて<Location>の設定がマージされ、translate_nameフックが実行されます。translate_nameフックはなんとなく想像ができるようにaliasモジュール(modules/mappers/mod_alias.c)、rewriteモジュール(modules/mappers/mod_rewrite.c)、userdirモジュール(modules/mapper/mod_usedir.c)などがフックをかけているようです。
次にmap_to_storageフックが呼ばれています。coreモジュールのcore_map_to_storage関数が実行されap_directory_walk関数とap_file_walk関数が呼び出すことで<Directory>と.htaccess、<Files>の設定がマージされるようです。その後、もう一度ap_location_walk関数を実行しています。
次にheader_parserフックが呼ばれています。setenvifモジュールがフックをかけてます。
次にアクセス権限のチェックが行われています。ap_satisfies関数(server/core.cに書かれています)を呼び出して権限チェックの種類を取得した後、access_checkerフックを呼び出しています。authz_hostモジュールがフックをかけてホストベースでの権限チェックを行っています。続いてap_some_auth_required関数を呼び出してユーザ認証が必要とされているかを確認後、check_user_idフックを呼び出してユーザの認証を行い、auth_checkerフックを呼び出してユーザの承認を行っています。check_user_idフックはauth_basicモジュールやauth_digestモジュールが、auth_checkerフックはauthz_userモジュールやauthnz_ldapモジュールがフックをかけています。
次にtype_checkerフックが呼ばれています。mimeモジュールやnegotiationモジュールがフックをかけています。mimeモジュールでは拡張子に応じたハンドラの設定などが行われています。
最後にfixupsフックを呼び出しています。いろいろなモジュールがフックをかけています。dirモジュールではDirectoryIndexディレクティブで指定されたファイルリストから返すファイルを決定する処理が行われています。
ap_invoke_handler関数はserver/config.cに書かれています。
まずinsert_filterフックが呼び出されています。その後、handlerフックが呼び出されリクエストが処理されています。各モジュールのhandlerフックにより、ファイルであったりプログラムの実行結果であったりディレクトリの内容であったりが生成されます。
各ハンドラは生成されたデータをアウトプットフィルタに渡しています。アウトプットフィルタとして以下のものが登録されています*3。
include/util_filter.hやserver/util_filter.cを眺めるとフィルタの適用順はPROTOCOLが先でNETWORKが後となるのでハンドラが生成したデータに適切なHTTPヘッダが付加されてリクエスト元に返されることになります。
今回はApacheのリクエスト処理を読んでみました。わかったこととしては、
といったところです。それではみなさんもよいコードリーディングを。