2014年7月22日火曜日

httpd-2.4.10 Release

httpd-2.4.10がリリースされた
http://httpd.apache.org/

CHANGESを見ると、幾つかの脆弱性が修正され、バグ修正や機能拡張も多数ある。
CHNAGESにある脆弱性の項目を直訳してみた。

CVE-2014-0117
Connectionヘッダの処理中にクラッシュする問題を修正。
この問題は、スレッドMPMの リバースproxyに対してDoS攻撃を可能にしていた。
http://svn.apache.org/viewvc?view=revision&revision=1610737

CVE-2014-3523
Windows環境で使用される WinNT MPMにあった大量のメモリ消費によるDoSの問題が修正された。
回避策: AcceptFilter <protocol> {none|connect}
http://svn.apache.org/viewvc?view=revision&revision=1610653

CVE-2014-0226
scoreboardの操作にあった競合条件を修正。
これはヒープバッファオーバフローを引き起こす可能性があった。
http://svn.apache.org/viewvc?view=revision&revision=1610499

CVE-2014-0118
DAFLATE入力フィルタ(圧縮されたリクエストボディを伸長する)は、データ長と伸長後のリクエストボディの圧縮レート)を制限するようした。
これにより高レートで圧縮されたリクエストボディによるDoS状態を避けられる。
DeflateInflateLimitRequestBody、DeflateInflateRatioLimit、DeflateInflateRatioBurstディレクティブで指定する。
http://svn.apache.org/viewvc?view=revision&revision=1610503

CVE-2014-0231
mod_cgid:stdinを読み込まないCGIスクリプトに対するDoSの問題を修正した。
これは、長引くHTTPD子プロセスがscoreboardを一杯にしてしまい、最終的にサーバをハングさせてしまった。
スクリプトとの通信に対して、デフォルトでは、クライアントのI/Oタイムアウト(TimeOutディレクティブで指定)を適用するようにした。
スクリプトとの通信時のタイムアウトを違う値にしたい場合はCGIDScriptTimeoutディレクティブを使用する。
http://svn.apache.org/viewvc?view=revision&revision=1610512


新機能
announceを見ると新機能として次の項目が紹介されている。
http://mail-archives.us.apache.org/mod_mbox/www-announce/201407.mbox/%3CAE2C52FD-C787-4F30-82B9-A9446C13246C@apache.org%3E

*) Proxy FGI and websockets improvements
   FCGIとwebsocketのproxyの改善

*) Proxy capability via handler
   ハンドラを介したproxyの利用
   http://httpd.apache.org/docs/2.4/en/mod/mod_proxy.html#handler

*) Finer control over scoping of RewriteRules
   RewriteRuleの適用範囲のより細やかな管理

これは、RewriteOptionsにいくつか新しいオプションがあるが、2.4.8(2.4.9)からあるものだった。
http://httpd.apache.org/docs/2.4/en/mod/mod_rewrite.html#rewriteoptions
2.4.8のCHANGESに以下の項目がある。
*) mod_rewrite: Add RewriteOptions InheritDown, InheritDownBefore,
   and IgnoreInherit to allow RewriteRules to be pushed from parent scopes
   to child scopes without explicitly configuring each child scope.
   PR56153. [Edward Lu <Chaosed0 gmail com>]

*) Unix Domain Socket (UDS) support for mod_proxy backends.
   mpd_proxyのバックエンドに対する Unix Domain Socketのサポート

これは、CHANGESの2.4.7で追加になっている項目だが...
   *) mod_proxy: Added support for unix domain sockets as the
      backend server endpoint [Jim Jagielski, Blaise Tarr
      <blaise tarr gmail com>]
下記のコミットがそれなら、時期的には、2.4.8のようだ。 
http://svn.apache.org/viewvc?view=revision&revision=1560081
確認してみると、2.4.7に同梱のCHANGESにはこの記載はない。
何が起きていたのか。

*) Support for larger shared memory sizes for mod_socache_shmcb
   mod_sochache_shmcbにおけるより大容量のメモリのサポート

これも2.4.8のCHANGESにそれらしい項目がある。
*) mod_socache_shmcb.c: Remove arbitrary restriction on shared memory size
    previously limited to 64MB. [Jens Laas <jelaas gmail.com>]

*) mod_lua and mod_ssl enhancements
   mod_luaとmod_sslの拡張

*) Support named groups and backreferences within the LocationMatch,
   DirectoryMatch, FilesMatch and ProxyMatch directives.
   LocalMatch、SirectoryMatch、FilesMatchおよびProxyMatchディレクティブ
   での名前付きグループと後方参照のサポート

これも2.4.8のCHANGESにそのままの項目がある。
*) core: Support named groups and backreferences within the LocationMatch,
    DirectoryMatch, FilesMatch and ProxyMatch directives. (Requires
    non-ancient PCRE library) [Graham Leggett]

それで、2.4.9のANNOUNCEを見てみると(2.4.8はリリースされていない)、
http://mail-archives.us.apache.org/mod_mbox/www-announce/201403.mbox/%3C6DC3A229-80BC-4F57-8420-990C40DD3244%40apache.org%3E

*) Finer control over scoping of RewriteRules
*) Unix Domain Socket (UDS) support for mod_proxy backends.
*) Support for larger shared memory sizes for mod_socache_shmcb
*) mod_lua and mod_ssl enhancements
*) Support named groups and backreferences within the LocationMatch,
   DirectoryMatch, FilesMatch and ProxyMatch directives.


2.4.7を見ると
http://mail-archives.us.apache.org/mod_mbox/www-announce/201311.mbox/%3C4E18E0C7-8E6E-417E-B36C-3A04C880E4CA%40apache.org%3E

*) Major updates to mod_proxy_fcgi
*) Higher performant event MPM
*) Enhancements to the WinNT MPM

別にマージされているわけではない。
よく分からない。


2014年7月15日火曜日

リクエストの処理の流れ

handlerフック関数の実行の続きで、ap_process_async_request()関数から呼び出し関係を遡ってみる。


(1) ap_process_http_async_connection関数

呼び出し元の関数ではリクエストを読込み(ap_read_request())、ap_process_async_request()処理を実行している。

(httpd-2.4.9/modules/http/http_core.c)

    124 static int ap_process_http_async_connection(conn_rec *c)
    125 {
    126     request_rec *r;
    127     conn_state_t *cs = c->cs;
  :
    132     while (cs->state == CONN_STATE_READ_REQUEST_LINE) {
    133         ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
    134
    135         if ((r = ap_read_request(c))) {
リクエスト行とリクエストヘッダを読み込み、request_rec情報に格納する。
ここで直接呼び出されるフック関数の実行関数には次のものがある
 * ap_run_create_request
 * ap_run_pre_read_request
 * ap_run_post_read_request
 * ap_run_log_transaction(エラー発生の場合)

     :
    141             if (r->status == HTTP_OK) {
    142                 cs->state = CONN_STATE_HANDLER;
    143                 ap_process_async_request(r);
     :
    151                 r = NULL;
    152             }
    153
    154             if (cs->state != CONN_STATE_WRITE_COMPLETION &&
    155                 cs->state != CONN_STATE_SUSPENDED) {
    156                 /* Something went wrong; close the connection */
    157                 cs->state = CONN_STATE_LINGER;
    158             }
    159         }
    160         else {   /* ap_read_request failed - client may have closed */
    161             cs->state = CONN_STATE_LINGER;
    162         }
    163     }
    164
    165     return OK;
    166 }

ここで conn_state_t という構造体が出てくる。
これは、コネクションの状態を管理している。

(httpd-2.4.9/include/httpd.h)

   1187 struct conn_state_t {
   1188     /** Current state of the connection */
   1189     conn_state_e state;
   1190     /** Whether to read instead of write, or write instead of read */
   1191     conn_sense_e sense;
   1192 };

ここにあるconn_state_e(列挙型)変数 stateは次の通り

(httpd-2.4.9/include/httpd.h)

   1167 typedef enum  {
   1168     CONN_STATE_CHECK_REQUEST_LINE_READABLE,
   1169     CONN_STATE_READ_REQUEST_LINE,
   1170     CONN_STATE_HANDLER,
   1171     CONN_STATE_WRITE_COMPLETION,
   1172     CONN_STATE_SUSPENDED,
   1173     CONN_STATE_LINGER,          /* connection may be closed with lingering */
   1174     CONN_STATE_LINGER_NORMAL,   /* MPM has started lingering close with normal timeout */
   1175     CONN_STATE_LINGER_SHORT     /* MPM has started lingering close with short timeout */
   1176 } conn_state_e;

ap_process_http_async_connection()でチェックされている CONN_STATE_READ_REQUEST_LINE は、その名の通り、リクエストの読み込みということだ。
リクエストの読み込み後、CONN_STATE_HANDLERに更新される(142行目)。
そして、ap_process_async_request()が実行される。

その処理を終えると、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDかチェックされ、いずれでもなければ、CONN_STATE_LINGERで更新される(157行目)。

この時点で、cs->stateは、CONN_STATE_WRITE_COMPLETIONか、CONN_STATE_SUSPENDEDか、CONN_STATE_LINGERということになるので、132行目からのwhile (cs->state == CONN_STATE_READ_REQUEST_LINE)ループからは抜ける。

この処理だけに着目する限り、whileループを実査にループする経路はなさそうだ。

(2)process_connectionフック関数

さて、もう一段、呼び出し元を辿ると、次の関数に辿り着く。

(httpd-2.4.9/modules/http/http_core.c)

    225 static int ap_process_http_connection(conn_rec *c)
    226 {
    227     if (async_mpm && !c->clogging_input_filters) {
    228         return ap_process_http_async_connection(c);
    229     }
    230     else {
    231         return ap_process_http_sync_connection(c);
    232     }
    233 }

これはフック関数だ。
以下で登録されている。process_connectionフック関数である。

(httpd-2.4.9/modules/http/http_core.c)

    275     ap_hook_process_connection(ap_process_http_connection, NULL, NULL,
    276                                APR_HOOK_REALLY_LAST);

上記のap_process_http_connection()で使用されているフラグasync_mpmはap_mpm_query()関数で設定されている。

    263     if (ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm) != APR_SUCCESS) {
    264         async_mpm = 0;
    265     }

このMPMの情報を取得する関数自体が内部でmpm_queryフック関数を呼び出している。
MPMによって、登録されているフック関数が異なり、実際にセットされる値も変わるという訳だ。
event MPMの場合は、このmpm_queryフック関数は、event_query()だ。
この関数が、AP_MPMQ_IS_ASYNCというコードを与えられたときに、引数async_mpmにセットする値は、1である。

ちなみに、worker MPMにおける、mpm_queryフック関数はworker_query()だが、AP_MPMQ_IS_ASYNCに対して返す値の指定がない。ここでは、APR_ENOTIMPLがエラーとして返されるので、結果として、264行目によって値が0となる。
prefork MPMも worker MPMと同様だった。

clogging_input_filtersは、2.4.9では使用されていないので、初期値の0のままだ。

従って、ap_process_http_connection()関数は、event MPMでは、ap_process_http_async_connection()が実行され、worker MPM/prefork MPMでは、ap_process_http_sync_connection()が実行される。

clogging_input_filtersをセットすると、worker MPMと同様にリクエスト処理でワーカスレッドを占有する、というのは、要はこの処理の分岐の話だ。


(3)event MPM: process_socket関数


続いて、このap_process_http_connection()を実行するprocess_connectionフック関数の呼び出し箇所を見てみる。
event MPMでは process_socket()関数の処理で呼ばれている。
これは、event MPMのworkerスレッドで実行される。
listenerスレッドが受け付けたリクエストの処理を行うのが、workerスレッドのprocess_socket()関数だ。

もう少し細かく見ると、listenerスレッドは、listenソケットのacceptを監視し、既に接続済みのソケットが読込可能/書込可能となるのを監視している。
またapr_skiplistを利用したタイマー処理のタイムアップも監視しており、そのほかにも4種類のタイムアウト処理を実行する。

読込可能チェックには、以下の状態がある。
  • KeepAlive接続における次リクエストの受信待ち
  • lingeringクローズでの通信相手ソケットからのクローズ(FIN)待ち

書込可能チェックは、以下の状態だ。
  • クライアントに送ろうとしたデータがブロックされてしまった場合(送信できなかった場合)、再び、書込可能なるのを待っている状態
これらのうち、lingeringクローズソケットのクローズ処理以外は、workerスレッドで処理される。
そして、acceptされたソケットの処理、KeepAlive接続の読込処理、書込み可能になったソケットの書込処理がprocess_socket関数で行われる。
タイマー処理は、process_socket関数では処理されない。

新たなソケットの処理とKeepAlive接続の読込処理では、conn_state_tがCONN_STATE_CHECK_REQUEST_LINE_READABLE状態になる。
書込み可能となったソケットの場合は、conn_state_tがCONN_STATE_WRITE_COMPLETION状態だ。

ここで、process_socket関数でap_run_process_connection()を実行するのは、CONN_STATE_CHECK_REQUEST_LINE_READABLE状態の場合の処理となる。

    881 static void process_socket(apr_thread_t *thd, apr_pool_t * p, apr_socket_t * sock,
    882                           event_conn_state_t * cs, int my_child_num,
    883                           int my_thread_num)
    884 {
    :

    893     if (cs == NULL) {           /* This is a new connection */

新しくacceptされたソケットの処理の場合、csはNULLとなっている。
KeepAlive接続の処理の場合はcsは作成済みなので、ここの処理を経由しない。
ここでコネクション情報の初期設定やevent_conn_state_t情報の初期設定
などの初期処理が実行される。
    897         c = ap_run_create_connection(p, ap_server_conf, sock,
    898                                      conn_id, sbh, cs->bucket_alloc);
     :
    920         ap_update_vhost_given_ip(c);
    921
    922         rc = ap_run_pre_connection(c, sock);


    942         cs->pub.state = CONN_STATE_READ_REQUEST_LINE;
    943
    944         cs->pub.sense = CONN_SENSE_DEFAULT;
    945     }
     :
    952     if (c->clogging_input_filters && !c->aborted) {

clogging_input_filtersフラグが立っている場合の処理。
実行されるのはap_run_process_connection()で、フラグが立っていない場合と
同じだが、終了時のconn_state_tに設定される状態が、CONN_STATE_LINGER状態
に限定されている(SUSPENDでなければ)。
     :
    959         apr_atomic_inc32(&clogged_count);
    960         ap_run_process_connection(c);
    961         if (cs->pub.state != CONN_STATE_SUSPENDED) {
    962             cs->pub.state = CONN_STATE_LINGER;
    963         }
    964         apr_atomic_dec32(&clogged_count);
    965     }
     :


    967 read_request:
    968     if (cs->pub.state == CONN_STATE_READ_REQUEST_LINE) {

1件のリクエスト処理はここから始まる。
    969         if (!c->aborted) {
    970             ap_run_process_connection(c);

これこそ、今まで見てきた処理を実行する process_connectionフック関数の
実行だ。
     :
    976         }
    977         else {
    978             cs->pub.state = CONN_STATE_LINGER;
    979         }
    980     }
    981
    982     if (cs->pub.state == CONN_STATE_WRITE_COMPLETION) {

process_connectionフック関数からreturnすると、通常、conn_state_tは
CONN_STATE_WRITE_COMPLETION状態となっている。
ap_process_async_request()関数の最後に実行される 1件のリクエスト処理の
終了処理であるap_process_request_after_handler関数内で、
CONN_STATE_WRITE_COMPLETIONがセットされる。
そして、ここでは bucket brigadeが NULLで出力フィルタに引き渡されている。
内部に保持されているbucket brigadeが処理対象ということだ。
    983         ap_filter_t *output_filter = c->output_filters;
     :

    989         rv = output_filter->frec->filter_func.out_func(output_filter, NULL);

     :
   1065     return;
   1066 }


この処理は、listenerスレッドと相互に関係しながら処理を進めるので、少し分かりにくい気がする。
が、リクエスト処理の流れを追うという意味では、ここが最上流、源流にあたる。
そういうわけで、上流から下流に簡単な図を書いてみた。


フック関数を中心に追ってみた。今日は終わり。

2014年7月7日月曜日

handlerフック関数の実行処理

handlerフック関数の例の続きだ。

(1)ap_invoke_handler関数

フック関数は以下のap_run_handler()処理で実行される。

(httpd-2.4.9/server/config.c)
    385 AP_CORE_DECLARE(int) ap_invoke_handler(request_rec *r)
    386 {
     :
    401     ap_run_insert_filter(r);
ap_run_insert_filterもフック関数の実行処理だ。
基本的には、リクエスト処理に用いる入力フィルタや出力フィルタがセットアッ
プされる。

     :

    439     result = ap_run_handler(r);

ここでhandlerフック関数を実行する

     :
    465     return result == DECLINED ? HTTP_INTERNAL_SERVER_ERROR : result;
    466 }


(2)ap_process_async_request関数

これを行っている、ap_invoke_handler()関数は、内部リダイレクトの処理や、サブリクエストの処理などを含め数か所で呼ばれている。
そのうち、外部から受け取ったリクエストをそのまま処理するのは、次の箇所だった。
受信したリクエストの情報が引数のrequest_rec情報に保存されている。
この関数がリクエスト処理の本体だと言えそうだ。

(httpd-2.4.9/modules/http/http_request.c)

    272 void ap_process_async_request(request_rec *r)
    273 {
     :
    313     access_status = ap_run_quick_handler(r, 0);  /* Not a look-up request */
    314     if (access_status == DECLINED) {

ap_run_quick_hanlder()もフック関数の実行処理だ。
その戻り値がDECLINEDの場合にこのif文内の処理が行われる。
quick_handlerフック関数は、AP_IMPLEMENT_HOOK_RUN_FIRSTタイプなので、
handlerフック関数と同じだ。
フック関数の戻り値がDECLINE以外の場合に処理を終了する。
フック関数は処理を行うと、エラーステータスやOKを返す。このタイプでDECLINEDを
返すのは、次のフック関数に処理を継続させるためだ。
この戻り値がDECLINEDということは、登録されたフック関数はすべて実行されたが、
結局、エラーやOKを返すような処理は行われなかったことになる。
これが何を意味するかだが、例えば、特定の条件でキャッシュがヒットした場合には
ここの処理には入らない(キャッシュが返される)、というようなことだ。キャッ
シュについては、あとで少し細かく見たい。

    315         access_status = ap_process_request_internal(r);

この関数内で、アクセス権限チェックを含む、多くの処理が実行される。
フック関数の実行も、直接呼び出されるだけで以下のものがある。
* ap_run_post_perdir_config * ap_run_translate_name * ap_run_map_to_storage * ap_run_header_parser * ap_run_access_checker * ap_run_access_checker_ex * ap_run_check_user_id * ap_run_auth_checker * ap_run_type_checker * ap_run_fixups
このチェックに合格すると、OKが返される
316 if (access_status == OK) { 317 access_status = ap_invoke_handler(r); ap_invoke_handle()はap_process_request_internal()がOKを返した場合にのみ実行 される。 ここから、ap_run_handler()が実行された。 318 } 319 } : リクエストの処理結果はaccess_statusにセットされている。 結果に応じて(OKか否か)処理が振り分けられている。 345 if (access_status == OK) { 346 ap_finalize_request_protocol(r); 347 } 348 else { 349 r->status = HTTP_OK; 350 ap_die(access_status, r); 351 } 352 353 ap_process_request_after_handler(r); 最後のこの処理は、リクエスト処理1件分の終了処理だ。 EOR(END of Request)バケットを生成し、これを収めたbucket brigadeを出力フィル タにap_pass_brigadeする。 default_handler等リクエスト処理中は、bucket brigadeを引き渡す出力フィルタが request_recのoutput_filters だったが、ここでは、conn_recのoutput_filtersに なっていた。 354 }


これだけでは、ap_run_handler()がどういうタイミングで処理されるのか分からない。
そこで、このあと少し、リクエスト処理の流れを追ってみる。
その際には、再度、ap_process_async_request()やap_invoke_handler()の処理内容も確認したい。
行ったり来たりだが、リクエスト処理の流れを追いながらフック関数の呼出し位置を確認し、その後また、フック関数を幾つか確認するという作業に戻る予定とする。

2014年7月1日火曜日

ハンドラ(handler)フック関数の例: send-as-is ハンドラ

send-as-isハンドラ

default_handerの続き
もう一つ、asisモジュール(mod_asis.so)の提供するハンドラを見てみる。
まず簡単に、使い方を確認する。
default_handlerは何もしない場合に使われるが、send-as-isは適用条件を指定してやる必要がある。

(1)使い方

send-as-isハンドラを利用するには、AddHandlerディレクティブを使って、拡張子.asisのファイルと結びつけるなどして、ハンドラとリソースを結び付けておく必要がある。

AddHandler send-as-is asis

send-as-isハンドラの処理するファイルは、特定の書式に従って書かれている。
と言っても、内容はほぼ、HTTPレスポンスをそのまま書いたものだ。
ただ1行目のステータス行だけはHTTPヘッダ風(Status:値)で、実際のレスポンスと異なる書式で定義している。
そして、Apacheは、この内容をほぼそのままクライアントへのレスポンスとする。
プロジェクトのドキュメントには以下のような例が書かれている。

Status: 301 Now where did I leave that URL
Location: http://xyz.example.com/foo/bar.html
Content-type: text/html

<html>
<head>
<title>Lame excuses'R'us</title>
</head>
<body>
<h1>Fred's exceptionally wonderful page has moved to
<a href="http://xyz.example.com/foo/bar.html">Joe's</a> site.
</h1>
</body>
</html>
</pre>

このファイルは301応答を生成し、Locationヘッダにここに書かれているURLを渡す。
上記の内容をsample.asisというファイルに保存して、curlコマンドでアクセスしてみる。

$ curl -v http://localhost:8080/sample.asis
* About to connect() to localhost port 8080 (#0)
*   Trying ::1... connected
* Connected to localhost (::1) port 8080 (#0)
> GET /sample.asis HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.14.3.0 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 301 Now where did I leave that URL
ステータス行のReason-Phraseも、ファイルに書かれたものが使われている
< Date: Wed, 25 Jun 2014 08:24:38 GMT
Dateヘッダは自動的に追加
< Server: Apache/2.4.9 (Unix)
Serverヘッダは自動的に追加
< Location: http://xyz.example.com/foo/bar.html < Content-Length: 199
Content-Lengthも自動的に追加
< Content-Type: text/html < <html> <head> <title>Lame excuses'R'us</title> </head> <body> <h1>Fred's exceptionally wonderful page has moved to <a href="http://xyz.example.com/foo/bar.html">Joe's</a> site. </h1> </body> </html> * Connection #0 to host localhost left intact * Closing connection #0

この結果、一般的なブラウザは http://xyz.example.com/foo/bar.html へのリクエストを自動的に実行するはずだ。

参考: http://httpd.apache.org/docs/2.4/en/mod/mod_asis.html

(2)フック関数の登録

フック関数の登録は以下の処理で行われている。


(httpd-2.4.9/modules/generators/mod_asis.c)

    114 static void register_hooks(apr_pool_t *p)
    115 {
    116     ap_hook_handler(asis_handler,NULL,NULL,APR_HOOK_MIDDLE);
    117 }


タイミングはAPR_HOOK_MIDDLEになっている。
つまり、default_handlerより先に実行される。

(3)ハンドラフック関数の処理


処理の流れを追ってみる。
処理対象のリクエストかどうかの判定を行い、対象でなければ、DECLINEで終了する処理が開始時に行われる。
あとは、リクエストのファイルを読み込み、解析し、必要なレスポンスを生成し、出力フィルタに引き渡す処理が行われている。
以下の処理を眺めるだけでは、Dateヘッダや、Serverヘッダや、Content-Lengthヘッダが追加されている様子はない。これは出力フィルタで行われる。


(httpd-2.4.9/modules/generators/mod_asis.c)
     31 static int asis_handler(request_rec *r)
     32 {
     33     apr_file_t *f;
     :
     37     if (strcmp(r->handler, ASIS_MAGIC_TYPE) && strcmp(r->handler, "send-as-is")) {
リクエストが、send-as-isハンドラで処理されるべきかどうかを判定している 箇所。ASIS_MAGIC_TYPEは "httpd/send-as-is"で、仮想的なMIME Typeになって いる。これでも指定できるということだ。 AddHandler send-as-is asis と指定しているなら、r->handlerに"send-as-is" が設定されている場合に処理される。
     38         return DECLINED;
DECLINEDで返るのは、次のhandlerフック関数の処理を継続するためだ
     39     }
     :
     42     if (r->method_number != M_GET) {
send-as-isハンドラはGETリクエストだけを処理する。 POSTリクエストなどの場合は、処理対象としない。
     43         return DECLINED;
     44     }
     45
     46     if (r->finfo.filetype == APR_NOFILE) {
     47         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(01233)
     48                     "File does not exist: %s", r->filename);
     49         return HTTP_NOT_FOUND;
指定したファイルが存在しない場合は 404 not foundを返す。
     50     }
     51
     52     if ((rv = apr_file_open(&f, r->filename, APR_READ,
     53                 APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) {
     54         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01234)
     55                     "file permissions deny server access: %s", r->filename);
     56         return HTTP_FORBIDDEN;
指定したファイルをオープンする。 失敗した場合には、 403 Forbidden を返す
     57     }
     58
     59     ap_scan_script_header_err_ex(r, f, NULL, APLOG_MODULE_INDEX);
ファイルのHTTPレスポンスヘッダ部分(空行までの部分)の処理を実行している。 Status: XXX の場合は、ステータス行にセットされ、 その他、適宜、レスポンス用のヘッダ情報にセットされる。
     60     location = apr_table_get(r->headers_out, "Location");
     61
     62     if (location && location[0] == '/' &&
     63         ((r->status == HTTP_OK) || ap_is_HTTP_REDIRECT(r->status))) {
これは、指定したファイルにLocationヘッダが指定されており、 リクエストがリダイレクトされるように指定されていたことを意味している。 しかもリダイレクト先が "/"で始まるので、自サーバ上のパスと考えられる。 ここではこのlocationへのリクエスト(内部リダイレクトリクエスト)を実行 している。
     64
     65         apr_file_close(f);
     66
     67         /* Internal redirect -- fake-up a pseudo-request */
     68         r->status = HTTP_OK;
     69
     70         /* This redirect needs to be a GET no matter what the original
     71          * method was.
     72          */
     73         r->method = "GET";
     74         r->method_number = M_GET;
     75
     76         ap_internal_redirect_handler(location, r);
     77         return OK;
     78     }
     79
     80     if (!r->header_only) {
HEADではない場合の処理となる。 先に指定されたファイルのHTTPレスポンスヘッダ部分の処理を終えている。 ここは、引き続きボディ部の処理を実行する。
     81         conn_rec *c = r->connection;
     82         apr_bucket_brigade *bb;
     83         apr_bucket *b;
     84         apr_off_t pos = 0;
     85
     86         rv = apr_file_seek(f, APR_CUR, &pos);
     87         if (rv != APR_SUCCESS) {
     88             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01235)
     89                           "mod_asis: failed to find end-of-headers position "
     90                           "for %s", r->filename);
     91             apr_file_close(f);
     92             return HTTP_INTERNAL_SERVER_ERROR;
     93         }
     94
     95         bb = apr_brigade_create(r->pool, c->bucket_alloc);
bucket brigade bbを作成する
     96         apr_brigade_insert_file(bb, f, pos, r->finfo.size - pos, r->pool);
指定したファイルの現在位置(ボディ部の開始)~終了位置(ファイルサイ ズから、開始位置を引いた値)でファイルbucketを生成し それを bucket brigade bb に挿入する。
     97
     98         b = apr_bucket_eos_create(c->bucket_alloc);
     99         APR_BRIGADE_INSERT_TAIL(bb, b);
EOS bucket(メタデータ)を作成し、これを bucket brigade bb の末尾に 追加する
    100         rv = ap_pass_brigade(r->output_filters, bb);
そして bucket brigade bb を出力フィルタに引き渡す。
    101         if (rv != APR_SUCCESS) {
    102             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(01236)
    103                           "mod_asis: ap_pass_brigade failed for file %s", r->filename);
    104             return HTTP_INTERNAL_SERVER_ERROR;
    105         }
    106     }
    107     else {

HEADリクエストの場合は、ボディ部の処理はないので、終了する。
ヘッダ情報は、request_rec情報のheaders_out配列に登録されているが、
まだ送信はされていない。
空のbucket brigadeを出力フィルタに渡すなどの処理も行われていない。


    108         apr_file_close(f);
    109     }
    110
    111     return OK;
    112 }


その他のハンドラフック関数も順に調査していきたいが、とりあえず、今回はここまで。