lighttpd-1.4.15 + FastCGI な環境で、HTTP リクエストヘッダ中の任意のレコードを参照して、その内容によって環境変数を設定しておきたい、という状況。apache であれば SetEnvIf ディレクティブを使うことで、
SetEnvIf X-Hoge ".*" bar=yes
とかで設定できるが、lighttpdではこういう事ができないらしい。lighttpdの設定でSetEnvIfのような条件分岐処理を書こうとすると、マニュアルのConditional Configurationによれば、
<field> <operator> <value> {
…
}
てな記述ができるのだが、<field> に指定できるのは $HTTP や $SERVER で参照できる、固定の変数だけのようだ。$HTTP["X-Hoge"] とか試しに指定してみたけど、syntax error で無視されてしまう。
デフォルト提供されている mod_xx や設定方法のマニュアルを当たってみたり、あと、騙し騙し使えそうな設定方法を試行してみたが、どれもダメだった。mod_setenv を使えば環境変数の設定まではできるんだが、その手前の条件記法を拡張する事ができないみたいだ。
ということで、lighttpd の拡張プラグインを書いてみようと思い立った。条件記法の拡張まではできなさそう(うまくやればできるかも??)だが、デフォルト指定できない特定のHTTPリクエストヘッダを参照し、環境変数を設定する、というだけであれば、プラグインで書けそうだ。ということで、プラグイン作成の作業メモ。
Writing Plugins のページに簡単なチュートリアルがあるので、これに従ってプラグインを書いてみた。プラグインの作成ステップは、大きく以下のような感じ。
- mod_skeleton.c からスケルトンコードのコピー
- スケルトンコードを書き換える形でプラグインを記述
- プラグインの Makefile.am の修正
- プラグインの make .. Makefile.in の生成と作成したプラグインのプリコンパイル
- lighttpd の configure .. –enable-maintainer-mode で Makefile を書き換える
- lighttpd の make && make install
本家のチュートリアルには上記 (4) のステップが抜けているので注意((6) も抜けてるけど)。あと、プラグインの作成には lighttpd の動作モデルと、プラグインがフックできるインタフェース(エントリポイント)の概要くらいは見ておく必要があると思う。
プラグインソースコードの構成(レイアウト)は、上記のチュートリアルを参照。意訳すると、
- プラグイン処理中で使用する構造体の定義
- 構造体の初期化処理
- configuration file の parse 処理 .. set_defaults
- プラグインが条件分岐中で適用された場合の処理 .. patch-function
- プラグインのメイン処理
- プラグインの初期化処理(プラグイン中で定義した関数と、プラグインインタフェースとの対応はここで定義する) .. plugin_init
こんなかんじの構成になっている。
あとは、既存のプラグインソースなどを参考に、プラグインを書いていく。今回書いたプラグイン mod_addenv.c は、こんなかんじ。
#include#include #include "base.h" #include "log.h" #include "buffer.h" #include "plugin.h" /* plugin config for all request/connections */ typedef struct { int handled; } handler_ctx; typedef struct { array *match; } plugin_config; typedef struct { PLUGIN_DATA; plugin_config **config_storage; plugin_config conf; } plugin_data; static handler_ctx * handler_ctx_init() { handler_ctx * hctx; hctx = calloc(1, sizeof(*hctx)); hctx->handled = 0; return hctx; } static void handler_ctx_free(handler_ctx *hctx) { free(hctx); } /* init the plugin data */ INIT_FUNC(mod_addenv_init) { plugin_data *p; p = calloc(1, sizeof(*p)); return p; } /* detroy the plugin data */ FREE_FUNC(mod_addenv_free) { plugin_data *p = p_d; UNUSED(srv); if (!p) return HANDLER_GO_ON; if (p->config_storage) { size_t i; for (i = 0; i < srv->config_context->used; i++) { plugin_config *s = p->config_storage[i]; array_free(s->match); free(s); } free(p->config_storage); } free(p); return HANDLER_GO_ON; } /* handle plugin config and check values */ SETDEFAULTS_FUNC(mod_addenv_set_defaults) { plugin_data *p = p_d; size_t i = 0; config_values_t cv[] = { { "addenv.env", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; if (!p) return HANDLER_ERROR; p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *)); for (i = 0; i < srv->config_context->used; i++) { plugin_config *s; s = calloc(1, sizeof(plugin_config)); s->match = array_init(); cv[0].destination = s->match; p->config_storage[i] = s; if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) { return HANDLER_ERROR; } } return HANDLER_GO_ON; } #define PATCH(x) p->conf.x = s->x; static int mod_addenv_patch_connection(server *srv, connection *con, plugin_data *p) { size_t i, j; plugin_config *s = p->config_storage[0]; PATCH(match); /* skip the first, the global context */ for (i = 1; i < srv->config_context->used; i++) { data_config *dc = (data_config *)srv->config_context->data[i]; s = p->config_storage[i]; /* condition didn't match */ if (!config_check_cond(srv, con, dc)) continue; /* merge config */ for (j = 0; j < dc->value->used; j++) { data_unset *du = dc->value->data[j]; if (buffer_is_equal_string(du->key, CONST_STR_LEN("addenv.env"))) { PATCH(match); } } } return 0; } #undef PATCH URIHANDLER_FUNC(mod_addenv_uri_handler) { plugin_data *p = p_d; size_t i, j; handler_ctx *hctx; if (con->plugin_ctx[p->id]) { hctx = con->plugin_ctx[p->id]; } else { hctx = handler_ctx_init(); con->plugin_ctx[p->id] = hctx; } if (hctx->handled) { return HANDLER_GO_ON; } hctx->handled = 1; mod_addenv_patch_connection(srv, con, p); for (i = 0; i < con->request.headers->used; i++) { data_string *dsh; dsh = (data_string *)con->request.headers->data[i]; if(dsh->key->used && dsh->value->used && (0 == strcasecmp(dsh->key->ptr, "X-HOGE"))) { for (j = 0; j < p->conf.match->used; j++) { data_string *ds = (data_string *)p->conf.match->data[j]; data_string *ds_dst; if (NULL == (ds_dst = (data_string *)array_get_unused_element(con->environment, TYPE_STRING))) { ds_dst = data_string_init(); } buffer_copy_string_buffer(ds_dst->key, ds->key); buffer_copy_string_buffer(ds_dst->value, ds->value); array_insert_unique(con->environment, (data_unset *)ds_dst); } } } /* not found */ return HANDLER_GO_ON; } REQUESTDONE_FUNC(mod_addenv_reset) { plugin_data *p = p_d; UNUSED(srv); if (con->plugin_ctx[p->id]) { handler_ctx_free(con->plugin_ctx[p->id]); con->plugin_ctx[p->id] = NULL; } return HANDLER_GO_ON; } /* this function is called at dlopen() time and inits the callbacks */ int mod_addenv_plugin_init(plugin *p) { p->version = LIGHTTPD_VERSION_ID; p->name = buffer_init_string("addenv"); p->init = mod_addenv_init; p->handle_uri_clean = mod_addenv_uri_handler; p->set_defaults = mod_addenv_set_defaults; p->cleanup = mod_addenv_free; p->handle_request_done = mod_addenv_reset; p->data = NULL; return 0; }
このソースを mod_addenv.c として保存し、lighttpd-1.4.15/src 以下で make したあと、./configure –enable-maintainer-mode && make && make install する。既存の環境がある場合は、テスト用に –prefix 指定して違うところに入れた方がいいかも。あとは lighttpd.conf で、
server.modules = ( "mod_addenv" )
addenv.env = (
"bar" => "yes"
)
とか設定しておけば、lighttpd でも上述の SetEnvIf のような効果が得られる。X-HOGE をソース中に書いているのがダサいな。なんとかしたい。
ところで、プラグインの記述方法は、ちょっと癖があるようにも思えたが、こういうモノなのだろうか。まあ、一度やってみておいて損は無いかな、とはおもった。他にもいろいろ使い道ありそうだし。
最近のコメント