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 }


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

0 件のコメント:

コメントを投稿