マルチプラットフォームでmrubyを使ってHTTP通信する方法

mrubyも少しずつ知られてきていて、WindowsやLinux、MacOSX等マルチプラットフォームで色々遊んでいる人が多いことでしょう。そうなってくると、しばらく弄ってみた後はやっぱりマルチプラットフォームで同じようにHTTPで通信してみたいと思いませんか?

例えば、

  • mrubyでHTTPのGETとかPOSTとかしてみたり
  • mrubyを組み込んだデバイスからTwitterに呟いてみたり
  • 各種デバイスからZabbixをつついてみたり
  • エアコンを監視してるRaspberry PiからGrowthForecastにデータを送ってグラフ化してみたり
  • 組み込みデバイスや低レイヤーなソフトウェアからfluentdにデータを送って解析してみたり

ということで、今回はそういうことをするためにはmruby側でどういう準備をして、どう実装すればいいかの導入部分を説明したいと思います。

HTTP関連のmrbgemsを導入

mrubyは基本的に全ての機能をスタティックにリンクするので、まずは以下のようなHTTPで色々遊ぶためのmrbgemsをリンクする設定をbuild_config.rbに書きましょう。

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

MRuby::Build.new do ¦conf¦

  toolchain :gcc
  conf.gembox 'default'

  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
  conf.gem :git => 'https://github.com/iij/mruby-digest.git'
  conf.gem :git => 'https://github.com/mattn/mruby-json.git'
  conf.gem :git => 'https://github.com/mattn/mruby-uv.git'
  conf.gem :git => 'https://github.com/mattn/mruby-http.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-simplehttp.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-httprequest.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-oauth.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-sleep.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-zabbix.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-growthforecast.git'
  conf.gem :git => 'https://github.com/y-ken/fluent-logger-mruby'

end

[/program]

続いて、マルチプラットフォームでのTCP通信の核となるlibuvが必要になりますので、これをgithubからダウンロードして最新のものをビルドしておきましょう。最近のgithubのlibuvはconfigureスクリプトも用意されてきているので、./configure make make installで基本的にOKなはずです。

そして、上記のようにlibuvをインストール後、build_config.rbにmrbgemの設定を記述した状態で、rakeあるいはmakeすればビルドが完了するはずです。

HTTP通信を色々してみる

では早速、rakeによって生成されたバイナリbin/mrubyを使って、HTTP通信してみましょう。上記のmrbgemを組み込んだmrubyバイナリやlibmruby.aをリンクさせたバイナリは、以下のようなRubyコードを実行することができます。

例えば、任意のURLに対して、普通にGETしたりPOSTするには以下のように書きましょう。

  • GETする場合

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

h = HttpRequest.new()
p h.get("https://blog.matsumoto-r.jp/")

[/program]

これでHTTPのGETができてしまいます。

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

./bin/mruby get.rb

[/program]

で実行してやると良いでしょう。以下、同様に./bin/mrubyで実行できます。

  • POSTする場合

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

h = HttpRequest.new()
h.post("http://127.0.0.1:5125/http/master/access", {
  :number => 7,
  :color  => "#333399",
  :mode   => "count",
},{
  'User-Agent' => "mruby-growthforecast",
})

[/program]

とかしてやれば、HTTPのPOSTができます。

Twitterに呟いたりTL眺めたりしてみる

次にTwitterに呟いてみたいときは、以下のように書くと良いでしょう。

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

CONSUMER_KEY        = ''
CONSUMER_SECRET     = ''
ACCESS_TOKEN        = ''
ACCESS_TOKEN_SECRET = ''
API_URL             = 'http://api.twitter.com/1.1/statuses/update.json'

tweet       = "happy new year from mruby-oauth"
ex_headers  = {'Content-Type' => 'application/x-www-form-urlencoded'}
twitter     = OAuth.new(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
response    = twitter.post(API_URL, {:status => tweet}, ex_headers)

if response.code.to_i == 200
  puts "tweet success: #{tweet}"
else
  puts "tweet failed: #{tweet}: bellow response"
  p response
end

[/program]

これて、ツイートできるはずです。

単純にストリームAPIを使わずにタイムラインをmrubyで眺める場合など、以下のように書くと良いでしょう。

[program lang=’actionscript3′ escaped=’true’]

CONSUMER_KEY        = ""
CONSUMER_SECRET     = ""
ACCESS_TOKEN        = ""
ACCESS_TOKEN_SECRET = ""
GET_API_URL         = 'http://api.twitter.com/1.1/statuses/home_timeline.json?count='

tweet_num = 10
interval = 60
twitter = OAuth.new(CONSUMER_KEY, CONSUMER_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET)

new_id = 0
old_id = 0
while
  response = twitter.get(GET_API_URL + tweet_num.to_s)
  if response.code.to_i == 200
    data = JSON::parse(response.body)
    if old_id == 0 ¦¦ old_id != data[0]["id"].to_i
      new_id = data[0]["id"].to_i
      data.reverse.each do ¦d¦
        if old_id < d["id"].to_i
          puts "[¥e[33m#{d["created_at"]}¥e[m] ¥e[36m#{d["user"]["screen_name"]}¥e[m: #{d["text"]}"
        end
      end
      old_id = new_id
    end
  else
    p response
    raise "Request failed: " + response.code.to_s
  end
  sleep interval
end

[/program]

これを./bin/mruby timeline.rbとかして実行すると、タイムラインが表示されます。

ZabbixのAPIをつついてみる

ZabbixのAPIのをつつきたい場合は、以下のように書くと良いでしょう。

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

config = {
  :url  => "http://127.0.0.1/zabbix/api_jsonrpc.php", 
  :ua   => "mruby-zabbix",
  :user => "api-admin",
  :pass => "api-admin",
}

z = Zabbix::Client.new(config)

data = {
  :method => "get",
  :object => "host",
  :params => {
               :output => "extend",
               :filter => {
                            :host    =>  "example.com",
                          },
             },
}

puts "request:  #{JSON::stringify(data)}"
puts "response: #{z.post(data)["body"]}"

[/program]

paramsシンボルにAPIの仕様にのっとったJSONをハッシュ形式で与えてやれば、Zabbixにmrubyを組み込んだ各種デバイスから指示が出せます。

GrowthForecastにデータを送ってみる

エアコンを監視していたり、様々なセンサーデータをRaspberry Piで集めていて、C/C++アプリからグラフ化したいデータをGrowthForecastに送りたい場合は、以下のように書くと良いでしょう。

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

g = GrowthForecast::Client.new("127.0.0.1", 5125)

config = {
  :service  => "apache", 
  :section  => "scoreboard",
  :graph    => "worker",
}

data = {
  :number   => 7, 
  :color    => "#333399",
  :mode     => "count",
}

p g.post(config, data)

[/program]

これで、エアコンを監視しているRaspberry Piから、GrowthForecastにデータを送って良い感じに宅内情報をグラフ化する事ができます。

Fluentdにデータを送ってみる

組み込みデバイスにmrubyを組み込んでいたり、低レイヤーなアプリから定期的にログを直接fluentdに投げたい場合は、以下のように書くと良いでしょう。

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

log = Fluent::Logger.new(nil, :host=>'127.0.0.1', :port=>8888)
log.post('myapp.access', {"agent"=>"foo"})

[/program]

これで簡単にmrubyを組み込んだデバイスや低レイヤーなソフトウェアの内部情報をfluentdに投げて解析する事ができます。

C/C++アプリへの組み込み方

基本はここに書かれているような感じです。これを参考にすると良いでしょう。分からない事があったら、Twitterでmrubyというキーワードを入れて質問すると、誰かが反応してくれると思います。

基本は、

  • rakeで生成されたbuild/host/lib/libmruby.aをCアプリにリンクさせる
  • リンクさせる際にはbuild/host/lib/libmruby.flags.makから必要なincludeファイルやライブラリを得る

です。libmruby.flags.makから得たcflagsとldflagsとlibsをgccのオプションに渡して、libmruby.aをリンクさせる、という手順です。

例えばmod_mrubyでは以下のようなMakefileを使って、動的に読み込むようにしています。

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

#   suport mrbgems
MRUBY_MAK_FILE := $(MRUBY_ROOT)/build/host/lib/libmruby.flags.mak
ifeq ($(wildcard $(MRUBY_MAK_FILE)),)
  MRUBY_CFLAGS =
  MRUBY_LDFLAGS =
  MRUBY_LIBS =
else
  include $(MRUBY_MAK_FILE)
endif

APXS_LDFLAGS=-Wl,-lcrypto,-lm, 
CFLAGS=$(INC)
LDFLAGS=$(MRUBY_LDFLAGS)
LIBS=$(LIB) $(MRUBY_LIBS)

[/program]

これによって生成されたC/C++アプリから上記のRubyコードにデータをC側から渡しつつ実行することで、C/C++アプリから簡単にmruby経由でのHTTP通信が可能になります。

最後に

どうでしょう。このように、mrubyをちょっと弄ってみたら、HTTP通信をしたくなるのはエンジニアの性だと思いますので、上記のサンプルを参考に色々とC/C++アプリやデバイス等から簡単にHTTP通信を楽しんでみて下さい。最近はmrubyで色々なデバイスを動かしている事例を見かけますので、そこからHTTP通信でデータを送受信したりすると面白い事が沢山できるかもしれませんね!

何か良い感じのmrbgemができる事を楽しみにしています。