2014年6月2日月曜日

入力フィルタの概要

入力フィルタについて書くが、出力フィルタの概要の記述を前提とする。

出力フィルタは、メイン処理が送り出したデータを加工、処理し、ネットワークへの出力までを行った。
入力フィルタは、ネットワークから受け取ったデータを加工、処理してメイン処理に引き渡す。
データの流れる方向は、当たり前だが出力フィルタとは逆向きになる。
出力フィルタは、ap_pass_brigade()関数によって、bucket brigadeをフィルタの階層の下流に向かって送り出していた。
入力フィルタもまた、階層をなしている。
メイン処理は、入力フィルタの階層を通して、bucket brigadeを吸い上げる処理を行う。
吸い上げると言うのは、相変わらず下にネットワークがあって、メイン処理が上にあるというイメージからの表現だ。
各入力フィルタは、下流のフィルタから吸い上げた bucket brigadeを処理し、それを上流のフィルタに引き渡している。

入力フィルタの処理の大雑把な説明


入力フィルタ関数のプロトタイプは次の通り。
出力フィルタより、引数が多くなっている。

(httpd-2.4.9/include/util_filter.h)

    136 typedef apr_status_t (*ap_in_filter_func)(ap_filter_t *f,
    137                                           apr_bucket_brigade *b,
    138                                           ap_input_mode_t mode,
    139                                           apr_read_type_e block,
    140                                           apr_off_t readbytes);


そして、入力フィルタ関数内で、次の入力フィルタ からbucket brigadeを吸い上げるために使用されるのが、ap_get_brigade()関数で、プロトタイプは次の通り。
下流のフィルタ f->nextを 第1引数に渡して、第2引数 bucketにデータを読み込む。

(httpd-2.4.9/include/util_filter.h)

    296 AP_DECLARE(apr_status_t) ap_get_brigade(ap_filter_t *filter,
    297                                         apr_bucket_brigade *bucket,
    298                                         ap_input_mode_t mode,
    299                                         apr_read_type_e block,
    300                                         apr_off_t readbytes);


入力フィルタ関数 ap_in_filter_func()では、出力フィルタと違い、引数の bucket brigadeは、出力先になる。
入力フィルタでは、ap_get_brigade()で下流のフィルタから bucket brigadeを 引数 bucket を吸い込み、これを内部で処理して、第2引数に指定されている b にセットする。
出力フィルタはイメージとしては、引数で受け取ったbucket brigadeを処理して最後に ap_pass_brigade()で下流に引き渡すが、入力フィルタは、まず、下流のフィルタからap_get_brigade()でbucket brigadeを吸い上げ、これを処理して、引数の bucket brigadeにセットする。

そして入力フィルタの最下流の処理は、 通常、CORE_IN 入力フィルタ ap_core_input_filter() が行う。

では、最初に最上流の入力フィルタを呼び出すのはどこか。
入力処理は、Apacheではリクエストヘッダの読み込みの部分と、リクエストボディの読み込み部分に大別される。
これらの処理が、最上流の入力フィルタを指定して ap_get_brigade()を実行することになる。

リクエストヘッダの読み込みは、HTTPプロトコル処理として共通で処理される。
このとき使われるのはap_rgetline()関数つまり、ap_rgetline_core()関数となる。

(httpd-2.4.9/include/http_protocol.h)

    608 #define ap_rgetline(s, n, read, r, fold, bb) \
    609         ap_rgetline_core((s), (n), (read), (r), (fold), (bb))

ここで以下のように、リクエスト読込処理での入力フィルタの処理が実行される。

(httpd-2.4.9/server/protocol.c)

    229         rv = ap_get_brigade(r->input_filters, bb, AP_MODE_GETLINE,
    230                             APR_BLOCK_READ, 0);


r->input_filterが、request_rec情報にある入力フィルタの最上位を指定している。
bbは bucket brigadeで、取得したデータが格納される。

他方、最下流のCORE_IN入力フィルタ ap_core_input_filter()内ではソケットからデータが読み込まれ、 読み込んだデータを 第2引数で指定された bbに格納する。
ただ、COER_IN入力フィルタ内ではソケットを直接は扱わず(CORE 出力フィルタとは異なる)、これをソケットbucketにして、読込用のbucket brigadeを通して処理している。読み込み済みのデータの管理などを bucket brigade内の HEAP bucket等で行わせる仕掛けになっているようだ。

CORE_IN入力フィルタの初期処理では以下が実行される

(httpd-2.4.9/server/core_filters.c)

     94 apr_status_t ap_core_input_filter(ap_filter_t *f, apr_bucket_brigade *b,
     95                                   ap_input_mode_t mode, apr_read_type_e block,
     96                                   apr_off_t readbytes)
     97 {
     :

     99     core_net_rec *net = f->ctx;
    100     core_ctx_t *ctx = net->in_ctx;
     :

    118     if (!ctx)
    119     {
    120         net->in_ctx = ctx = apr_palloc(f->c->pool, sizeof(*ctx));
    121         ctx->b = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
    122         ctx->tmpbb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
    123         /* seed the brigade with the client socket. */
    124         rv = ap_run_insert_network_bucket(f->c, ctx->b, net->client_socket);
    125         if (rv != APR_SUCCESS)
    126             return rv;
    127     }


この処理の 124行目、ap_run_insert_network_bucket()はフック関数の実行処理だ。
フック関数も Apacheのコードの重要な仕掛けなので、後でこれもまとめよう。
ここにあるのは、insert_network_bucket のフック関数の実行処理だ。

簡単に書くと、いろいろな名前のフック関数が、Apache httpdのライフサイクルのいろいろなタイミングで実行されている。
それぞれに名前があって、"hogehoge"のフック関数を実行するのは ap_run_hogehoge()という処理になる。
そして、このap_run_hogehoge()で実行されるのは、 ap_hook_hogehoge()という登録関数で登録されたフック関数の実体だ。
実体は1つも登録されないことも、2つ以上が登録されていることもある。
なお、ap_run_hogehoge()やap_hook_hogehoge()の関数の実体はマクロで定義されるので、ソースコード内をその名前で探しても関数定義は見つからない。

さて、この ap_run_insert_network_bucket()で実際に実行されるフック関数は、core_insert_network_bucket()で
この関数では、

  • 通信用のソケットを持つ client_socket からソケットbucketが作成され、
  • ctx->b の bucket brigadeの末尾に追加される。


ちなみに、このinsert_network_bucketフック関数は 2.2系にはない。2.4系で追加されたようだ。

引き続き、 CORE_INフィルタap_core_input_filter() を見ていくと、第3引数のmodeによって処理が分岐する。
mode==AP_MODE_GETLINE の場合は次の処理が行われる。

(httpd-2.4.9/server/core_filters.c)

    145     if (mode == AP_MODE_GETLINE) {
    146         /* we are reading a single LF line, e.g. the HTTP headers */
    147         rv = apr_brigade_split_line(b, ctx->b, block, HUGE_STRING_LEN);

    148         /* We should treat EAGAIN here the same as we do for EOF (brigade is
    149          * empty).  We do this by returning whatever we have read.  This may
    150          * or may not be bogus, but is consistent (for now) with EOF logic.
    151          */
    152         if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) {
    153             rv = APR_SUCCESS;
    154         }
    155         return rv;
    156     }


この、apr_brigade_split_line()で1行分(LFまで)のデータが bucket brigade b に追加され、未処理分のデータはctx->bに残された状態で処理を終えている。

最上流の呼び出しと、最下流のCORE_INフィルタについて書いたが、入力フィルタの処理のイメージを描くのにはよい例ではない。
中間階層の入力フィルタの例を別途書きたい。

入力フィルタ階層の作成


出力フィルタと同様に、入力フィルタも、Apache httpdプロセスへの入力フィルタの登録と、各リクエストに適用するためのフィルタの追加を行うことで、利用される。

Apache httpdプロセスへの入力フィルタの登録に使用されるのは、ap_register_input_filter()関数だ。
この関数内で、フィルタ名をキーにしたトライ木(filter_trie_node型) registered_input_filters に登録され、利用可能になる。
そして、リクエストで適用するには、ap_add_input_filter()関数やap_add_input_filter_handle()関数を使って、リクエスト処理用の入力フィルタの階層に登録する。

入力フィルタもフィルタタイプを持ち、そのタイプに応じて、階層の登録位置が決まるのも出力フィルタと同様だ。

  • コネクションタイプフィルタはコネクション情報(conn_rec)のinput_filters に登録される。
  • プロトコルフィルタはリクエスト情報(request_rec)の proto_input_filters に登録される。
  • リソースフィルタは、リクエスト情報(request_rec)の input_filters に登録される。

リソースフィルタのリストの末尾は、プロトコルフィルタに連結し、プロトコルフィルタの末尾はコネクションフィルタに連結しており、入力フィルタの階層は、
    rquest_rec.input_filters -> input_filters.proto_input_filters -> conn_rec.input_filters
で構成されることになる。

設定による入力フィルタの追加


入力フィルタにはプロトコルの処理や、上述のCORE_IN入力フィルタのように、自動的に組み込まれるフィルタのほかに、設定などにより組み込まれるものがある。

ap_register_input_filter()でソースコードを検索すると入力フィルタ関数を見つけられる。
例えば、dumpioモジュールで提供されているDUMPIO_IN入力フィルタ(dumpio_input_filter)は、dumpioモジュールをロードし、DumpIOInputを指定した場合には自動的に組み込まれる。
このフィルタの場合、データをエラーログに出力するので、出力するかしないかを、LogLevelでもコントロールできる。Loglevelをtrace7以上にしなければ、ログには出力されない。

また、出力フィルタで紹介した、SetOutputFilter/AddOutputFilterと同様に、SetInputFilter/AddInputFilter ディレクティブも存在している。

sedモジュールには次のような利用例が書かれている。

http://httpd.apache.org/docs/2.4/en/mod/mod_sed.html#sampleconf

 
    AddInputFilter Sed php 
    InputSed "s/monday/MON/g" 
    InputSed "s/sunday/SUN/g" 


この AddInputFilterは 拡張子が php のファイルを処理する際に、Sed 入力フィルタを入力フィルタの階層に追加する。

ひとまず、以上

2014/12/15 見出しに誤字(出力→入力)

0 件のコメント:

コメントを投稿