io.jsでmruby実装のhttp2サーバーのtrusterdを動かした
背景
Blenderでmrubyをctypes経由で動かせないか?から始まり、 Blenderでmrubyを動かし、trusterdを動かすところまで出来た。
このctypes、内部的にはlibffiを使っているそうなので、他の言語処理系でも libffiが使えるものなら簡単に出来るはず。
node.jsやio.jsでもこのlibffiを使ったnode-ffiがあり、NodObjCなど使っている自分にはなじみ深いので、 やってみた。
今回はtrusterdをio.jsの現時点で最新のv1.2.0で動かしてみる。
なにはなくともmruby
git clone https://github.com/mruby/mruby.git cd mruby
trusterdを組み込んだmrubyをビルド
trusterdを動かすのに必要なmrbgemsを組み込んだmrubyをビルドする。
build_config.rb を以下の様に編集する。
OSXでHomebrewの場合
brew install libev brew install libxml2 brew install libevent brew install zlib brew install spdylay export ACLOCAL_PATH=/usr/local/Cellar/libxml2/2.9.2/share/aclocal/ export PKG_CONFIG_PATH=/usr/local/Cellar/openssl/1.0.2/lib/pkgconfig:/usr/local/Cellar/zlib/1.2.8/lib/pkgconfig/ brew link openssl --force
作業終了時は
brew unlink openssl
あとは、以下の通り。
rake
ここまでの作業で、trusterdを組み込んだmrubyの実行ファイルがmruby/bin/mrubyに出来ている。 また、以降の作業で必要なlibmruby.a等もmrubyのソースツリーに作成されている。
trusterdの共有ライブラリ化
libffiで呼び出すには共有ライブラリとなっている必要ある。 これはlibffiが内部でdlopenで対象のライブラリを開く為。
共有ライブラリ側から呼び出し元の関数を呼び出す
共有ライブラリ側のmrubyからjsを呼び出すことが出来ると、trusterdでリクエストを受けて、 js側で処理をすることが可能なる。
共有ライブラリ側で、コールバックの為の関数をポインタを受け取れるような関数を用意し、 呼び出し側でコールバックしてもらいたい関数をCの関数ポインタとして共有ライブラリ側に 渡さすことが出来ればこれが実現できる。
幸い、node-ffiではjsの関数を関数ポインタとして、共有ライブラリ側に渡せる仕組みがあり、 これが実現できる。
以下のように、関数ポインタをjsで扱える。
var ffi = require('ffi'); // C言語での見え方: int func(char *p) の関数ポインタ var funcPtr = ffi.Function('int', ['string']);
共有ライブラリの作成
trusterdに関数ポインタ引数として受ける口と受けた関数ポインタを呼び出せる機能を拡張し、 共有ライブラリとして生成する。
コードは以下のようになる。
mruby-configが便利
以上で、共有ライブラリに必要なものは一通りそろった。 これらを元に共有ライブラリを作成する。
mrubyはlibmruby.aなるライブラリが作られるのだが、これは、mrubyのコア部分のみしか 入っていない。今回使用するtrustredはこの他にオブジェクトが必要となる。
mrubyのソースツリーからオブジェクトファイルをちまちま設定するのは大変だなぁと軽く絶望しかけるが、 mrubyのソースツリーのbin/mruby-configなるコマンドが助けてくれる。
pkg-configと同様にライブラリ作成時に必要な各種パスや、リンクすべきオブジェクトを出力してくれる。
--cflags
bin/mruby-config --cflags
Rakefileにしてみた
折角mruby使っているのだからRakefileでやってみた。 ちなみに、Makefileを書く場合は、 [Trusterdの作者のさんの「C言語のアプリにmruby経由でtrusterdのHTTP/2サーバ機能を5分で組み込む方法 - 人間とウェブの未来」の記事中のMakefileが非常に参考になる。
desc 'libtrusterd.dylib' MRUBY_ROOT = "./mruby" LIB_NAME = "libtrusterd" if RUBY_PLATFORM =~ /darwin/i LIB_EXT = "dylib" end if RUBY_PLATFORM =~ /linux/i LIB_EXT = "so" end LIB_FULL_NAME = LIB_NAME + "." + LIB_EXT task :default => "all" task :all do cflags=`#{MRUBY_ROOT}/bin/mruby-config --cflags` cflags.chomp!() ldflags=`#{MRUBY_ROOT}/bin/mruby-config --ldflags` ldflags.chomp!() ldflags_before_libs=`#{MRUBY_ROOT}/bin/mruby-config --ldflags-before-libs` ldflags_before_libs.chomp!() libs=`#{MRUBY_ROOT}/bin/mruby-config --libs` libs.chomp!() p ldflags_before_libs sh "gcc #{cflags} -shared -fPIC trusterdBoot.c #{ldflags} #{ldflags_before_libs} #{libs} -o #{LIB_FULL_NAME}" end
libtrusterd作成
rake
io.js側の準備
node-ffiを入れる
io.jsや最新のnode.js v0.12.0はオリジナルのnode-ffiでは動かない。 この記事書くために動くようにしたnode-ffiをGithubに置いた。
npm install kjunichi/node-ffi npm install ref
共有ライブラリに関数を渡す
関数ポインタの作成
以下の様にff.FunctionにC側から呼び出す際の方を指定して 関数ポインタを取得できる。
var funcPtr = ffi.Function('int', ['string']);
これは、C側から見た際に
int hoge(char *foo);
trusterd.js
JavaScriptのクライアントコード
の関数ポインタになる。
うごかしてHTTP/2を堪能する
node trusterd
Chromeなり、Firefoxで
http://127.0.0.1:8080/test
にアクセスする。
すると、nodeを動かしたターミナルに
#<HTTP2::Server:0x10207b650>
"test"
class:Call, method:py_exec
Result is this is trusterd!
と表示され、trusterdでリクエストを受け付け、node.js側の関数を呼び出した ことが分かる。
終了方法
nodeを起動したターミナルで、 コントロール+Cを押すと、プロンプトに戻る。
まとめ
Githubにこの記事の内容にそったライブラリを上げた。
参考Link
関連記事