mod_mrubyの新機能を実装したのとmrubyではまった所

mod_mrubyに新機能を実装しました。バージョンはまだまだ0.01です。

最初のリリースでは、テストがてらmod_mruby経由でmruby上で動作するApacheクラスライブラリを作って、その中のメソッド(sleepするだけ)を実装しました。今回は、ようやくApacheモジュールらしくなってきたのと、だいぶ実装の検討ができてきたので、個人的にはだいぶ進んだように思っています。

request_rec構造体をmrubyメソッドの定義に受け渡し

これが、今回非常に困った所で、mrubyスクリプト上で動作するメソッドを定義しているライブラリファイルに、request_recの構造体をどう受け渡したらよいのだろうと考えていました。なぜなら、引数でrequest_recを直接メソッド定義の関数に渡せないからです。mod_luaではAP_DECLARE_HOOKやOPTIONAL_HOOKを駆使して、lua_boxpointerとかluaL_getmetatableとかlua_setmetatableでrequest_rec構造体をpushして、必要な時にメソッドの定義箇所からgetしています。

多分、これが美しいんだろうと思いますが、mrubyのソースを解読してこれをやるのは時間がかかりそうだったのと、Apacheのマクロ展開がかなり複雑で分かりにくかったので、今回は遺憾ながらrequest_recのmrb_request_rec_stateというグローバル変数を使うことにしました。

具体的な実装としては、ap_mbr_string.h内に以下のように、ap_mbr_push_requestを定義します。

request_rec *mrb_request_rec_state = NULL;

static int ap_mrb_push_request(request_rec *r)
{
    mrb_request_rec_state = (request_rec *)apr_pcalloc(r->pool, sizeof (*mrb_request_rec_state));
    mrb_request_rec_state = r;
    return OK;
}

そして、これをmod_mruby内のmrubyで有効なクラスやメソッドの定義箇所であるap_mbr_class_initの前に、Apacheのap_hook_handlerのタイミングでap_mbr_push_requestを実行します。それによって、グローバル変数であるmrb_request_rec_stateに現在のrequest_recを保存しておいて、それを各種メソッド定義内で呼び出すようにしました。

static int mruby_handler(request_rec *r)
{

    mruby_config_t *conf = ap_get_module_config(r->server->module_config, &mruby_module);

    ap_mrb_push_request(r);

    mrb_state *mrb = mrb_open();
    ap_mruby_class_init(mrb);

    return ap_mruby_run(mrb, conf->mruby_code_file);
}

これによって、例えばmruby上でApacheにアクセスしてきたクライアントに文字列を返したい場合は、ap_rputs(char *s, reqeust_rec *r)を使って、以下のようにApache.rputsを実装しました。

mrb_value
ap_mrb_rputs(mrb_state *mrb, mrb_value str)
{
    mrb_value msg;

    mrb_get_args(mrb, "o", &msg);
    ap_rputs(RSTRING_PTR(msg), ap_mrb_get_request());

    return str;
}

これで、ようやくApacheモジュールらしく、mrubyスクリプト内でごにょごにょした文字列をクライアントに返すことができました。詳しくはREADME等を読んでみてください。

だいぶ先が見えてきた

request_recをmrubyのメソッド定義内で受け取れたことにより、だいぶ先に進むことができそうです。例えば、error_logへの書き込みも実装できそうです。その辺を追加しながら、フック箇所を増やしていって、さらにはrequest_rec構造体をmrubyスクリプトないで書き換えるようにするのが直近の目標になります。

はまったところ

  • request_rec構造体の受け渡し

まずは、上で述べたrequest_rec構造体の受け渡しです。多分、もっと効率的で美しい渡し方があると思うのですが、時間がかかりそうなのでとりあえずこういう実装にしました。こういうのあるよー、というのがあれば、コードを見せてもらえるとうれしいです。

  • mrb_str_ptrとRSTRING_PTR

基本的な所で間違っているかもしれませんが、以下のようにするとap_rputs箇所で「dereferencing pointer to incomplete type」になってエラーになりました。

追記:2012/0424

と、思っていたけど、今改めてやり直してみると問題なくコンパイルが通りました。まつもとゆきひろさんにもお手数をおかけしてしまって、すみませんでした。以降のソースは同義になります。

mrb_value
ap_mrb_rputs(mrb_state *mrb, mrb_value str)
{
    mrb_value msg;
    struct RString *s;
    char *c;

    mrb_get_args(mrb, "o", &msg);
    s = mrb_str_ptr(msg);
    c = s->buf;

    ap_rputs(c, ap_mrb_get_request());

    return str;
}

そこで、print.cとmruby/string.hをにらむと以下のようにマクロが定義されています。

#define mrb_str_ptr(s)    ((struct RString*)((s).value.p))
#define RSTRING(s)        ((struct RString*)((s).value.p))
#define RSTRING_PTR(s)    (RSTRING(s)->buf)
#define RSTRING_LEN(s)    (RSTRING(s)->len)
#define RSTRING_CAPA(s)   (RSTRING(s)->aux.capa)
#define RSTRING_SHARED(s) (RSTRING(s)->aux.shared)
#define RSTRING_END(s)    (RSTRING(s)->buf + RSTRING(s)->len)

どうやら、mrb_str_ptr(mrb_value str)の->bufは、RSTRING_PTR(str)と同義のようなので、RSTRING_PTR(str)を使って以下のように書き換えてみました。

mrb_value
ap_mrb_rputs(mrb_state *mrb, mrb_value str)
{
    mrb_value msg;

    mrb_get_args(mrb, "o", &msg);
    ap_rputs(RSTRING_PTR(msg), ap_mrb_get_request());

    return str;
}

するとすんなりとコンパイルが通りました。定義は同義のように見えるのですが、いまいちなんでエラーになったのか差が分かりません。ということで、まつもとゆきひろさんにも聞いてみました。

すると、

と返ってきたので、

と状況を説明すると

と返ってきました。

多分上のコードで何かがおかしいと思うのですが、もしわかる人がいれば教えて頂けるとうれしいです。今のmod_mrubyの現状はこんな感じです。今日明日は、論文と発表資料等を作らないといけないので、時間があまりさけないかもしれませんが、睡眠削ってでもやりたいくらい面白いですね。

ちなみに、海外の人も少し見てくれてるようで、モチベーションが上がってきました。

うれしいですね。頑張ります。

「mod_mrubyの新機能を実装したのとmrubyではまった所」への2件のフィードバック

コメントは受け付けていません。