Apache の sub request と internal redirect
極私的メモ。
通常のリクエスト処理フロー
modules/http/http_core.c
で ap_process_http_connection()
関数 を ap_HOOK_process_connection
として登録しており,これが(ap_HOOK_process_connection
をオーバーライドしていなければ)通常のリクエスト処理の関数となります。
ap_process_http_connection()
の実装について フックから Apache の全体像を追う - daily dayflower の再掲になりますが,
ap_process_http_connection() { /* * …… request_rec の準備 …… */ ap_RUN_create_request(); // RUN_ALL ap_RUN_post_read_request(); // RUN_ALL /* quick_handler が用意されてるならとっとと実行→ fast exit */ ap_RUN_quick_handler(); // RUN_FIRST /* request_rec の整備(r->filename トカ,AAA トカ) */ ap_process_request_internal(); /* レスポンスハンドラの呼び出し */ ap_invoke_handler(); /* ログ吐き(どんなリクエストでも必ず実行) */ ap_RUN_log_transaction(); // RUN_ALL }
おおまかに
request_rec
の準備request_rec
の整備 (viaap_process_request_internal()
)- レスポンスハンドラの実行
- ファイナライズ
のような行程を経ることになります。
ちなみに ap_invoke_handler()
は下記のような実装です。
ap_invoke_handler() { ap_RUN_insert_filter(); // VOID ap_RUN_handler(); // RUN_FIRST }
(今後頻出する)ap_process_request_internal()
は下記のような実装です。
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 }
レスポンスを返すことはしませんが,それ以外の request_rec にまつわる諸行(r->filename
を決定したりアクセス認可をしたり)をおこなうフェーズです。
サブリクエストの生成
サブリクエストを生成する関数には ap_sub_req_lookup_uri()
, ap_sub_req_lookup_file()
, ap_sub_req_lookup_dirent()
, ap_sub_req_method_uri()
の4種がありますが,基本的な構造はどれも一緒です。
共通部分を抜き出すと下記のようなロジックになっています。
request_rec * ap_sub_req_lookup_hogehoge(r, next_filter) { request_rec *rnew; rnew = make_sub_request(r, next_filter); ap_parse_uri(rnew, new_uri); if (ap_is_recursion_limit_exceeded(r)) { return HTTP_INTERNAL_SERVER_ERROR; } ap_process_request_internal(rnew); return rnew; }
つまり,
make_sub_request()
でサブリクエスト用request_rec
構造体を初期化するap_parse_uri()
で URI のパースをおこなうap_is_recursion_limit_exceeded()
でサブリクエスト・内部リダイレクト回数が所定よりオーバーしていないか検査するap_process_request_internal()
でrequest_rec
の整備をおこなう
というフローになっています。
なお ap_sub_req_method_uri()
や ap_sub_req_lookup_uri()
の場合,ap_process_request_internal()
する前に ap_RUN_quick_handler()
してますが,まぁあまり気にしなくてよいでしょう。
make_sub_request()
の実装
サブリクエスト用 request_rec
構造体を生成する make_sub_request()
in server/request.c
の実装(静的関数ですが)は下記のようになっています。
static request_rec *make_sub_request(r, next_filter) { request_rec *rnew; /* サブリクエスト用サブプールの作成 */ apr_pool_create(&rrp, r->pool); rnew = apr_pcalloc(rrp, sizeof(request_rec)); /* * …… rnew の整備 …… */ ap_set_sub_req_protocol(rnew, r); ap_RUN_create_request(rnew); return rnew; }
サブメモリプールを作成しているところがポイントで,あとは request_rec
構造体を用意して初期化しているだけです。また ap_HOOK_create_request
が呼び出されますので,メインリクエストであろうとサブリクエストであろうとリクエスト固有の情報の初期化に使うことができます。
上記で呼び出している ap_set_sub_req_protocol()
に限っては server/protocol.c
に実装があります。
ap_set_sub_req_protocol(request_rec *rnew, const request_rec *r) { /* * …… rnew の雑多な環境整備 …… */ rnew->main = (request_rec *) r; }
重要なところはとくにないのですが,request_rec
の main
フィールドに生成元の request_rec
を設定しているところがちょっとポイントです。
サブリクエストの実行
サブリクエストを実行する ap_run_sub_req()
の実装は下記のようになります。
int ap_run_sub_req(r) { if (!(r->filename && r->finfo.filetype)) { ap_run_quick_handler(r, 0); } if (! OK) { ap_invoke_handler(r); } ap_finalize_sub_req_protocol(r); }
こちらがわで response handler を実行していることがわかります。
以上をまとめると
ということになります。
つまり,サブリクエストの出力内容(=レスポンスハンドラの役割)が必要ないのであれば,サブリクエストの生成だけおこなっても問題はありません。じっさい添付モジュールでも,サブリクエストの lookup のみおこない,その結果(r->filename
等)をあれこれするというロジックがいくつかみうけられます。これが一番最初に「2つのステップを『基本的に』経る」と書いた理由です。
さらにまとめると,
- レスポンスに他のリクエストの処理結果を挿入したい場合,サブリクエストの lookup と
ap_sub_req_run()
を組み合わせて用いる - 他の URI のリソースの情報(実体ファイルやアクセス権など)を知りたい場合,サブリクエストの lookup のみおこなう
- 他のリソースの取得を試みて成功した場合にそのリソースを返す場合,サブリクエストの lookup をおこなってその結果に
ap_internal_fast_redirect()
をおこなう(後述します)
のような適用場面があるということになります。
内部リダイレクトについて
サブリクエストの lookup には数多くの API が存在しましたが,内部リダイレクトに用いる API は基本的に ap_internal_redirect()
と ap_internal_redirect_handler()
の2つのみです。それぞれの実装をみていきます。
まず ap_internal_redirect()
は
ap_internal_redirect(new_uri, r) { request_rec *new = internal_internal_redirect(new_uri, r); ap_RUN_quick_handler(new, 0); /* Not a look-up request */ if (DECLINED) { ap_process_request_internal(new); if (OK) { ap_invoke_handler(new); } } if (OK) { ap_finalize_request_protocol(new); } else { ap_die(); } }
のようになっています。
ap_intenal_redirect_handler()
は
ap_internal_redirect_handler(new_uri, r) { request_rec *new = internal_internal_redirect(new_uri, r); if (r->handler) ap_set_content_type(new, r->content_type); ap_process_request_internal(new); if (OK) { ap_invoke_handler(new); if (! OK) { ap_die(); return; } ap_finalize_request_protocol(new); } else { ap_die(); } }
のようになっています。
細部に違いはあるものの,大まかなフローとしては,
internal_internal_redirect()
でリダイレクト用request_rec
の用意ap_process_request_internal()
でrequest_rec
の整備ap_invoke_handler()
でレスポンスハンドラの呼び出し
のようになっています。
サブリクエストと異なり,これらの関数でレスポンスハンドラの呼び出しまでおこなっています。
internal_internal_redirect()
の実装
両者で使われている,内部リダイレクト用 request_rec
構造体の生成用関数 internal_internal_redirect()
in modules/http/http_request.c
の実装(静的関数ですが)は下記のようになります。
static request_rec *internal_internal_redirect(new_uri, r) { request_rec *new; if (ap_is_recursion_limit_exceeded(r)) { ap_die(HTTP_INTERNAL_SERVER_ERROR, r); return NULL; } new = (request_rec *) apr_pcalloc(r->pool, sizeof(request_rec)); /* * …… new の整備 (1) …… */ new->prev = r; r->next = new; ap_RUN_create_request(new); /* * …… new の整備 (2) …… */ ap_RUN_post_read_request(new); return new; }
フローは異なりますが,make_sub_request()
と似たような感じです。ただ,ap_is_recursion_limit_exceeded()
をこの関数サイドで呼んでいるところと,サブリクエストまわりでは呼ばれていない*1 ap_HOOK_post_read_request
フックを呼び出しているところが異なります。
また,
new->prev = r; r->next = new;
のように,内部リダイレクト用 request_rec
構造体の prev
フィールドに,呼び出し元の request_rec
を設定し,呼び出し元の next
フィールドに内部リダイレクト用 request_rec
を設定しています。
ちなみに本筋と関係ないですが,response handler 以前の hook で内部リダイレクト処理をおこなうとおかしな挙動(クライアントからみてレスポンスが終了しない)を示します。これは Apache 1.3 API notes - Apache HTTP Server Version 2.2 にも記述されていますが,実際にわたしも経験しました*2。このように response handler 以前に内部リダイレクトをおこないたい場合,request_rec
のどこか(notes
とか)にリダイレクト先を保存しておいて,それ用のカスタムレスポンスハンドラを登録しておく,というのが定石になります。mod_rewrite でもそのようになっていました。
おまけ: ap_internal_fast_redirect()
について
internal redirect 系で ap_internal_fast_redirect()
というのもあるのですが,こいつの実装はこんな感じです。
ap_internal_fast_redirect(rr, r) { /* memory pool の統合 */ apr_pool_join(r->pool, rr->pool); /* サブリクエスト構造体をメインリクエスト構造体にコピー */ copy rr => r; }
ここからとくにコピーされたリクエストをどうこう,というのはしていません。なので response handler hook で仕掛けるにはよろしくない感じ。(ap_process_request_internal()
で呼び出される)request_rec
整備系 hook から呼び出すべきでしょう。
添付モジュールでの使われどころは,たとえば mod_negotiation で,各言語用のリソースに対して sub request の lookup を行い,(ap_run_sub_req()
は行わず)適切なリソースがあったらそいつに ap_internal_fast_redirect()
するなど。また mod_dir で,DirectoryIndex
の各ファイルに対して sub request の lookup を行い以下略とか。
つまり
- sub request を発行する
- その sub request がよさげだなと思ったら
ap_internal_fast_redirect()
でそれをメインリクエストに昇格 - あとは通常のリクエスト処理にゆだねる
な手順で使われています。
request_rec
の next
, prev
, main
いままでちらほらとでてきましたが request_rec
構造体には next
, prev
, main
というフィールドが存在します。
struct request_rec { /* ...... snip snip snip ...... */ /** Pointer to the redirected request if this is an external redirect */ request_rec *next; /** Pointer to the previous request if this is an internal redirect */ request_rec *prev; /** Pointer to the main request if this is a sub-request * (see http_request.h) */ request_rec *main; /* ...... snip snip snip ...... */ };
サブリクエストに関する部分を図示すると以下のようになります。
root
じゃなくて main
でした。暇ができたら図を直します。
わざわざ図示するほどのことでもなかったですが,
- 自分がサブリクエストとして呼ばれている場合
r->main
がNULL
以外になる - 自分が内部リダイレクト先として呼ばれている場合
r->prev
がNULL
以外になる - おのおの親の親(や兄弟の兄弟)のように順々にたどっていける
- これを用いてリクエストのループが発生しているかどうか検出可能
ことがわかります。