EventMachineで簡単インターフェース
初めてmorizyun氏のこの記事を読んだときはEventMachineとかforemanとかって何をするものか知らなかったのですが、EventMachineというのは「[Ruby] Reactorパターンについて」にも書いてある通り、ソケットの受信待機をして貰って簡単にReactorパターンを実現することが出来るツールってことのようです。だからわざわざ自分でプロセス間通信の手続きを書く必要がなくアプリケーションレベルのコマンドを用意してあげるだけで済むのではないか?と思いながら作ったら思惑通りにうまく動いたって感じです。実際に組み込む前には、以下のような単純なサンプルで確認しました。
#server側サンプル
require 'eventmachine'
class Serv < EM::Connection
attr_accessor :track
def post_init
puts "myserv: init"
end
def receive_data(data)
puts data
send_data @track
EM.stop if data =~ /stop/i
end
def connection_completed
puts "myserv: completed"
end
def unbind
puts "myserv: unbind"
end
end
EM.run do
EM.start_server("127.0.0.1", 10000, Serv) do |conn|
conn.track = ARGV[0]
end
end
以下のようにコマンドラインパラメータを付加して起動し、
ruby myserv.rb O.K.
クライアント接続時にServクラスのプロパティに保持されていることを確認する。
#client側サンプル
require "net/telnet"
begin
localhost = Net::Telnet::new("Host" => "localhost",
"Port" => 10000,
"Timeout" => 1,
"Telnetmode" => false,
"Output_log" => "./temp0.log",
"Prompt" => "O.K.")
localhost.cmd("search word") { |c| print c }
localhost.close
localhost = nil
p "end"
rescue Net::ReadTimeout
p "readtimeout"
p $!.to_s
rescue
p $!.to_s
end
このserver側の処理を、twitterAPIを使ってDBに検索結果を保存するスクリプトに組み込んで、client側の処理をRails製のサイトに組み込めばいいわけです。
あと、server側のスクリプトは、ユーザ毎に起動してユーザがWebサイトから制御しなければならないので、foremanは必要無さそうです。
サーバースクリプトへのパラメータの受け渡し
EventMachine::Connectionを継承したクラスServにユーザ情報を保存するメンバ変数(プロパティ)を用意して、Webサイト(client側)からサーバースクリプトを起動する時にコマンドライン引数でそれらの値を渡してあげます。起動の際に「10000+ユーザID」をポート番号に指定することでWebサイト利用者とサーバースクリプト利用者を一致させています。EventMachineを使ったプログラム同士が変数を共有する仕組みのようなものがあるかもしれませんが、ステートレスなHTTPで動いているWebサイトから起動するのだから、コマンドラインで渡すのがちょうど良さそうです。
twitterのStreamingAPIを使用する部分は以前書いた記事で紹介したmorizyun氏のコードをServ.trackingメソッドに閉じ込めてほぼそのまま動かしています。twitter側でエラーが発生した場合このままでいいのかよく分かりませんが、エラーになればWebサイト側からサーバースクリプトを再起動してもらえばいいという考え方で作ってます。
client側からserver側スクリプトに受け渡すパラメータはTwitterAPIを利用するために必要な認証key文字列(4種類)とユーザIDとTwitterのfilterAPIに渡す検索文字列(検索タグ)です。
#実際のソースの一部
require 'eventmachine'
require 'optparse'
class Serv < EM::Connection
attr_accessor :uid
attr_accessor :track
attr_accessor :c_key
attr_accessor :c_secret
attr_accessor :a_key
attr_accessor :a_secret
def receive_data(data)
case data
when /stop/i
send_data "O.K."
EM.stop
when /check/i
send_data @track
省略
:
def tracking(track, c_key, c_secret, a_key, a_secret, uid)
stream = Twitter::JSONStream.connect(
省略
:
EM.run do
EM.start_server("127.0.0.1", 10000 + params["p"].to_i, Serv) do |conn|
conn.track = params["t"]
conn.uid = params["p"].to_i
conn.c_key = params["c"]
conn.c_secret = params["k"]
conn.a_key = params["s"]
conn.a_secret = params["a"]
end
end