Apache の provider 機構 - 他モジュールに移譲するしくみ
2008-11-13 追記: タイトルを変更しました
用語定義をしておきます。
- consumer
- provider の提供する情報を取得する役割をになう
- provider
- consumer の要求する情報を提供する役割をになう
「情報」というのは const void *
型の値1つ,なのでなんでもよいです。文字列定数でもいいですし*1関数ポインタでもいいです。たいていは(関数ポインタを内包した)構造体を登録/取得します。
関数ポインタなり関数ポインタを内包した構造体なりを「情報」としてうけわたすことができると何がうれしいかというと,「移譲」ができることです。あるモジュールで大枠のロジックをインプリメントし,他のモジュールで具象的な関数を登録してもらう,などすると,実装を切り替えることができます。
もちろん,同一のモジュールで consumer と provider を実装しても構いません。利用する関数の決定に一手間かかってしまいますが,のちのち他のモジュールで provider を置き換えることができるなど柔軟性がまします。
Apache で使われている providers
Apache 付属のモジュールの中で,以下のモジュールが provider 機構を利用しています。
Provider Group | consumer | provider |
---|---|---|
authn |
mod_auth_basic, mod_auth_digest, mod_authn_alias | mod_authn_file, mod_authn_dbm, mod_authn_ldap, mod_authn_dbd, mod_authn_anon, mod_authn_alias |
cache |
mod_cache | mod_disk_cache, mod_mem_cache |
proxylbmethod |
mod_proxy, mod_proxy_balancer | mod_proxy_balancer |
dav |
mod_dav | mod_dav_fs (, mod_dav_svn) |
dav-lock |
(mod_dav_svn) | mod_dav_lock |
コードを読んでいないので憶測まじりになりますが。
authn
provider は,ユーザ名とパスワードをもとに認証を行う provider です。consumer がクライアントとユーザ名を agent とやりとりし,認証自体を provider になげている恰好です。provider にはたとえば生 file や DBM から認証情報を読み取って認証するものがあります。
cache
provider は,要求されたドキュメントをキャッシュから取り出す provider です。Disk や Memory から取り出す provider が用意されており,切り替えることができます。
proxylbmethod
provider は proxy のロードバランスを行う provider です。独自の provider を実装することでロードバランシングのアルゴリズムなどを自作して適用することができます。詳しくは mod_proxy_balancerに独自振り分けロジックを追加できる気がする | 眠る開発屋blog に譲ります。
dav
provider や dav-lock
についてはコードを読むのがめんどうなので省略します。付属モジュールだけだとファイルシステム上のリソースを DAV で提供することしかできませんが,mod_dav_svn モジュールを使うとバックエンドとして Subversion レポジトリ上のリソースを DAV で提供できるようになります。この切り替えを provider-consumer アーキテクチャで実現しているのですね。
インタフェース定義
インタフェース定義は include/ap_provider.h
にあります。また実装は server/provider.c
にあります。たいして行数もないですし,ロジックも難しくないので読んでみるのもいいでしょう。
3つの関数のみ定義されています。
ap_register_provider()
- provider の定義
ap_lookup_provider()
- provider の取得
ap_list_provider_names()
- provider の列挙
いちおう各関数の仕様をみていきます。
/** * This function is used to register a provider with the global * provider pool. * @param pool The pool to create any storage from * @param provider_group The group to store the provider in * @param provider_name The name for this provider * @param provider_version The version for this provider * @param provider Opaque structure for this provider * @return APR_SUCCESS if all went well */ AP_DECLARE(apr_status_t) ap_register_provider(apr_pool_t *pool, const char *provider_group, const char *provider_name, const char *provider_version, const void *provider);
group
, name
, version
の3つのキーで一意となるように provider を登録します。provider を提供するモジュールで呼び出します。
基本的に group
でインタフェースの「機能」を一意に定め,各モジュールがユニークな name
で登録します。そして httpd.conf
の設定子で name
を指定する,という使われ方が多いようです。
たとえば,cache provider-consumer インタフェースだと,
- mod_mem_cache が
mem
とfd
という name の provider を登録 - mod_disk_cache が
disk
という name の provider を登録
して,[http://httpd.apache.org/docs/2.2/mod/mod_cache.html#cacheenable:title=CacheEnable]
設定子で provider を指定する形になっています。
version というとバージョンコントロール(上位バージョンは下位バージョンに対して上位互換性をもつなど)がありそうなイメージですが,とくにそのような機構は用意されていません。必要ならば consumer と provider でそのような機構をインタフェース仕様を規定・実装することになるでしょう。
version は 0 スタートの数値文字列("0"
等)を利用するのが慣例のようです。特に決まりはありませんが。
/** * This function is used to retrieve a provider from the global * provider pool. * @param provider_group The group to look for this provider in * @param provider_name The name for the provider * @param provider_version The version for the provider * @return provider pointer to provider if found, NULL otherwise */ AP_DECLARE(void *) ap_lookup_provider(const char *provider_group, const char *provider_name, const char *provider_version);
group, name, version の3つのキーで一意となる provider を取得します。consumer モジュールで呼び出します。
/** * This function is used to retrieve a list (array) of provider * names from the specified group with the specified version. * @param pool The pool to create any storage from * @param provider_group The group to look for this provider in * @param provider_version The version for the provider * @return pointer to array of ap_list_provider_names_t of provider names (could be empty) */ AP_DECLARE(apr_array_header_t *) ap_list_provider_names(apr_pool_t *pool, const char *provider_group, const char *provider_version);
指定した group
, version
にマッチする provider の name
を列挙します。
Apache 付属モジュールでまともに使われている例はありませんが,Hooks 機構のように group に登録された provider をすべて呼び出すようなメカニズムを利用してみてもおもしろいかもしれませんね。
サンプルモジュール
とまあ座学だけでは退屈なので,実際に provider 機構を利用したモジュールを作ってみましょう。
- consumer は2つの数値を「演算」する大枠のアルゴリズムを提供する
- provider として具体的な「演算」(add, subtract など)を実装する
という役割分担です。
consumer の実装
まずは consumer(mod_consumer)の実装から。
/* ** mod_consumer.c */ #include "httpd.h" #include "http_config.h" #include "http_protocol.h" #include "ap_config.h" #include "ap_provider.h" #include "apr_strings.h" /* Provider Definitions */ typedef int (*arithmetic_method)(int arg1, int arg2); /* Configuration */ typedef struct consumer_conf { const char *method_name; } consumer_conf; static void * create_dir_config(apr_pool_t *p, char *dir) { consumer_conf *conf = (consumer_conf *) apr_pcalloc(p, sizeof(consumer_conf)); return conf; } static const char * set_arithmetic_method(cmd_parms *cmd, void *mconfig, const char *arg) { consumer_conf *conf = (consumer_conf *) mconfig; conf->method_name = apr_pstrdup(cmd->pool, arg); return NULL; /* NO ERROR */ } static const command_rec consumer_commands[] = { AP_INIT_TAKE1("ArithmeticMethod", set_arithmetic_method, NULL, ACCESS_CONF | RSRC_CONF, "Set method name for calculation"), { NULL } }; /* Content Handler */ module AP_MODULE_DECLARE_DATA consumer_module; /* declaration */ static int consumer_handler(request_rec *r) { consumer_conf *conf; arithmetic_method provider; int result; if (strcmp(r->handler, "consumer")) return DECLINED; conf = (consumer_conf *) ap_get_module_config(r->per_dir_config, &consumer_module); if (! conf || ! conf->method_name) return HTTP_INTERNAL_SERVER_ERROR; provider = ap_lookup_provider("arithmetic", conf->method_name, "0"); if (! provider) return HTTP_INTERNAL_SERVER_ERROR; result = (provider)(6, 2); r->content_type = "text/plain"; ap_rprintf(r, "%s(6, 2) = %d\n", conf->method_name, result); return OK; } /* Hook Registration and Module Settings */ static void register_hooks(apr_pool_t *p) { ap_hook_handler(consumer_handler, NULL, NULL, APR_HOOK_MIDDLE); } module AP_MODULE_DECLARE_DATA consumer_module = { STANDARD20_MODULE_STUFF, create_dir_config, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ consumer_commands, /* table of config file commands */ register_hooks /* register hooks */ };
最初にも書いたように通常 provider data としては構造体へのポインタをうけわたすことが多いのですが,今回は演算を行う関数ポインタ自身を provider data としています。
provider = ap_lookup_provider("arithmetic", conf->method_name, "0");
のように,ArithmeticMethod
設定子で設定された name の provider を取得しています。group は arithmetic
,version は 0
です。
これで provider
は関数ポインタ自身ですから,
result = (provider)(6, 2);
のようにして演算を provider におこなってもらいます。演算の引数は 6 と 2 の決め打ちw
provider の実装
まずは加算をおこなう provider,mod_arith_add の実装です。
/* ** mod_arith_add.c */ #include "httpd.h" #include "http_config.h" #include "ap_provider.h" /* Provider Definitions */ typedef int (*arithmetic_method)(int arg1, int arg2); /* Arithmetic Operation */ static int arith_add(int arg1, int arg2) { return arg1 + arg2; } /* Hook Registration and Module Settings */ static void register_hooks(apr_pool_t *p) { ap_register_provider(p, "arithmetic", "add", "0", &arith_add); } module AP_MODULE_DECLARE_DATA arith_add_module = { STANDARD20_MODULE_STUFF, NULL, /* create per-dir config structures */ NULL, /* merge per-dir config structures */ NULL, /* create per-server config structures */ NULL, /* merge per-server config structures */ NULL, /* table of config file commands */ register_hooks /* register hooks */ };
先ほどの consumer の実装では,config を処理する必要があったのでどうしてもあるていどのコード量になってしまいましたが,こちらはとても短くおさめることができました。
add
という name で arith_add()
という関数ポインタを provider data として登録しています。
register_hooks
で provider を登録するのが慣例のようです。登録された provider を管理するためにメモリプールが必要となります。このメモリプールは system wide に共通のものを利用しなくてはなりません((さもないと運が悪いと ap_lookup_provider()
時にセグフォってしまうはずです。))。ap_register_provider()
ではこれを第一引数で与えているのですが,register_hooks
が呼び出されたときの pool は server/main.c::main()
で用意された global pool なのでたしかに適しているのでしょう。まぁ config stage でも global pool がわたされているのでそこで登録しても構いませんが。
さて同じように他の演算を行う provider を実装していきましょう。といっても実質下記の部分だけ書き換えれば OK です。
/* Arithmetic Operation */ static int arith_sub(int arg1, int arg2) { return arg1 - arg2; } /* Hook Registration and Module Settings */ static void register_hooks(apr_pool_t *p) { ap_register_provider(p, "arithmetic", "sub", "0", &arith_sub); }
実行する
モジュールをビルドしてインストールしたうえで,下記のような設定*2でウェブサーバと立ち上げます。
$ cat /etc/httpd/conf/httpd.conf ...... snip snip snip ...... LoadModule consumer_module modules/mod_consumer.so LoadModule arith_add_module modules/mod_arith_add.so LoadModule arith_sub_module modules/mod_arith_sub.so LoadModule arith_mul_module modules/mod_arith_mul.so LoadModule arith_div_module modules/mod_arith_div.so <Location /arith> SetHandler consumer ArithmeticMethod add </Location> ...... snip snip snip ......
これでリクエストを発行してみると……
$ wget -nv -O - http://localhost/arith add(6, 2) = 8 11:25:28 URL:http://localhost/arith [14/14] -> "-" [1]
おお,きちんと 6 + 2
を計算することができました。
このままだとつまらないので,httpd.conf
を書き換えて,subtract 演算をおこなうようにしてみます。
$ sudo vi /etc/httpd/conf/httpd.conf ...... snip snip snip ...... <Location /arith> SetHandler consumer ArithmeticMethod sub </Location> ...... snip snip snip ...... $ sudo /sbin/service httpd restart Stopping httpd: [ OK ] Starting httpd: [ OK ]
これで実行すると,
$ wget -nv -O - http://localhost/arith sub(6, 2) = 4 11:26:07 URL:http://localhost/arith [14/14] -> "-" [1]
こんどは 6 - 2
を計算することができました。
consumer のロジックを修正することなく provider を選択することで任意の演算をおこなうことができるようになりました。