non vorrei lavorare

ブログ名の通りです。javascript three.js mruby rust OCaml golang julialang blender

io.jsでmruby実装のhttp2サーバーのtrusterdを動かした

こんばんは、今朝は次男が「いってらっしゃい」と「バイバイ」ではなく、送り出してくれました。kjunichiです。

背景

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