mod_mrubyの拡張ライブラリ開発のススメ

先日mod_mrubyでredisクラスを扱えるようにしました。

今回は、こういった新たなクラスを扱うためのmod_mruby用拡張ライブラリの作り方を紹介しようと思います。「mrubyで何か作ってみたいけど、どこから手出していいかわからない。」という人は、是非この記事を参考にmod_mrubyの拡張ライブラリを作って、Apacheに実行させて遊んでみてはどうでしょうか。

拡張ライブラリの実装

まずは、作りたいクラスの実装をします。

mod_mrubyをgithubからcloneするとレポジトリ内にlibディレクトリがあると思います。今回は、例のごとくhelloクラスを作る場合はどういう実装の仕方になるかを紹介します。

まず、以下のようにlibディレクトリ以下にライブラリ(クラス)名のディレクトリを掘ります。

[program lang=’bash’ escaped=’true’]

git clone git://github.com/matsumoto-r/mod_mruby.git

cd mod_mruby/lib

mkdir hello

[/program]

そして、helloディレクトリ以下にhello.cを作成し、そこに以下のような実装を書きます。これがmrubyでの基本的なクラスの実装方法になっています。

[program lang=’c’ escaped=’true’]

/*
// hello.c - hello name class for mod_mruby
//
// See Copyright Notice in mod_mruby.h
*/

#include "mod_mruby.h"
#include "ap_mrb_request.h"

#ifdef ENABLE_HELLO

mrb_value ap_mrb_hello_name(mrb_state *mrb, mrb_value self)
{
    mrb_value name;
    request_rec *r = ap_mrb_get_request();

    mrb_get_args(mrb, "o", &name);
    char *hello_string = apr_psprintf(r->pool, "hello %s-san", RSTRING_PTR(name));

    return mrb_str_new2(mrb, hello_string);
}

// Add init function to ap_mruby_ext_calss_init() in ap_mrb_init.c
// and prototype to ap_mrb_init.c
void ap_mruby_hello_init(mrb_state *mrb, struct RClass *class_core)
{
    struct RClass *class_hello;

    class_hello = mrb_define_class_under(mrb, class_core, "Hello", mrb->object_class);
    mrb_define_method(mrb, class_hello, "hello", ap_mrb_hello_name, ARGS_ANY());
}

#endif

[/program]

上記の実装において、拡張クラスのひな形となる初期化関数をap_mruby_hello_initのような形で作成し、mrb_define_class_underでApacheのHelloクラスを作成します。これによって、Rubyコード上はApache::Helloというクラスになります。

そして、Apache::Helloクラスの中にhelloメソッドを作ります。細かい説明はこの記事で紹介されているので、引数の説明等は省きます。そして、helloメソッドで実際に動作するC上の関数ap_mrb_hello_nameを実装します。処理としては、メソッドで渡された引数を元に文字列を生成するだけの処理です。

以上で、クラスとメソッドの実装は終わりです。簡単ですね。

拡張クラスの初期化関数をmod_mrubyに登録

上記で実装した拡張クラスの初期化関数ap_mruby_hello_initをmod_mrubyの初期化フェイズに登録します。これによって、mod_mruby上で上記の拡張クラスが読み込まれます。

mod_mrubyの初期化フェイズはap_mrb_init.cの中で行われており、拡張クラス用の初期化関数はap_mruby_ext_calss_init()でされています。

[program lang=’c’ escaped=’true’]

// add extended class init functions like ap_mruby_redis_init() in lib/redis/redis.c
void ap_mruby_redis_init(mrb_state *mrb, struct RClass *class_core);
void ap_mruby_hello_init(mrb_state *mrb, struct RClass *class_core);

// init phase for extended calss like lib/redis/redis.c
void ap_mruby_ext_calss_init(mrb_state *mrb, struct RClass *class_core)
{
#ifdef ENABLE_REDIS
    ap_mruby_redis_init(mrb, class_core); DONE;
#endif
#ifdef ENABLE_HELLO
    ap_mruby_hello_init(mrb, class_core); DONE;
#endif
}

[/program]

上記のように、ap_mruby_hello_init関数のプロトタイプ宣言と、ap_mruby_ext_class_init内で拡張クラスの初期化関数を実行します。マクロでENABLE_***を用意していますが、これは以下のようにmod_mruby.hに記述する事で、いらない昨日はDISABLEしてバイナリを軽くできるようにしています。これは、後々configureで–disable-hoge-calss とかできるようにしようと思っています。

configureで対応しました:2012/12/08追記

以下のように、configure.acに追記する事で、–enable-calssで拡張ライブラリのENABLEの切り替えをできるようにしています。基本はDISABLEです。

[program lang=’bash’ escaped=’true’]

AC_ARG_ENABLE([redis],
    [AS_HELP_STRING([--enable-redis],
        [redis feature (default is no)])],
    [],
    [enable_redis=no]
)

AS_IF([test "x$enable_redis" != xno],
    [AC_DEFINE([ENABLE_REDIS], [1], [redis support])]
)

[/program]

再度、config.h.inとconfigureを作り直します。

[program lang=’bash’ escaped=’true’]

autoconf
autoheader

[/program]

例えば、helloクラスとredisクラスをenableにする場合は、以下のようにconfigureを実行します。

[program lang=’bash’ escaped=’true’]

./configure --enable-hello --enable-redis

[/program]

実際に動かしてみる

コンパイルの仕方は変わりません。以下のようにコンパイルします。

[program lang=’bash’ escaped=’true’]

./configure --enable-hello

make

sudo make install

sudo make restart

[/program]

Apacheのconfには以下のように書いておきましょう。これも拡張ライブラリに関わらずいつも通りです。

[program lang=’apache’ escaped=’true’]

LoadModule mruby_module modules/mod_mruby.so
AddHandler mruby-script .rb

[/program]

先ほど作成したhelloクラスで以下のようなコードを書きます。

[program lang=’ruby’ escaped=’true’]

h = Apache::Hello.new

Apache.rputs(h.hello("matsumoto"))

[/program]

そして、クライアントから http://example.com/hello.rb にアクセスして以下のように文字列が出力されたら成功です!

[program lang=’text’ escaped=’true’]

hello matsumoto-san

[/program]

さいごに

どうでしょうか。かなり簡単にmrubyの拡張クラスを実装できる事がわかりました。

mrubyで何しようかなぁ、と悩んでいる人は一度mod_mrubyの拡張ライブラリを作ってみて、Apacheで色々動かして遊んでみてはどうでしょうか。やっぱり動くと楽しい物ですね。

もうすこし進んだ実装をしたい場合等は、lib/redis/redis.c あたりを参考にすると良いと思います。

現在は、例えば以下のような拡張ライブラリがあると面白いなぁと思っています。基本はDisableにしておくものが多くなると思っていますが。

  • sqlite3の拡張クラス
  • memcachedの拡張クラス
  • OSリソース値取得の拡張クラス
  • jsonの拡張クラス
  • msgpackの拡張クラス
  • WebSocketの拡張クラス
  • Zabbix APIの拡張クラス
  • IRCの拡張クラス
  • twitterの拡張クラス

ではでは、面白い拡張ライブラリのpull-requestをお待ちしております!