0

Sinatraのroutesを直接操作する

get ‘/’とかpost ‘/message’ とかDSLで登録したものをroutesという。

Sinatra::Base#routesにHashがあって、これを直接のぞける。
logoutとかloginとか、すでにrouteとして登録されている名前でユーザ名を作れないようにするとかに使えると思う。



リクエストメソッドをkeyとしたhashの子要素に配列の配列があって、1つ目の要素がパスにマッチするかどうか判定する正規表現。

{"GET"=>[[/^\/cometio\/cometio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5cab8d78@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab6d98@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/websocketio\/websocketio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5a1d8598@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]], "HEAD"=>[[/^\/cometio\/cometio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5cab7608@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab6500@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>], [/^\/websocketio\/websocketio(?:\.|%2E)js$/, [], [], #<Proc:0x007fbd5a227a80@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]], "POST"=>[[/^\/cometio\/io$/, [], [], #<Proc:0x007fbd5cab5d80@/Users/sho/.rvm/gems/ruby-1.9.2-p318/gems/sinatra-1.3.6/lib/sinatra/base.rb:1293>]]}



たとえば /cometio/cometio.js を削除するなら

Sinatra::Application.routes["GET"].delete_if{|route|
"/cometio/cometio.js" =~ route[0]
}

0

Bundlerとgem.dependencyの順序

bundler使う時、依存関係の根っこのgemほど下に書くべきらしい。

sinatra-rocketio -> (sinatra-cometio/sinatra-websocketio) -> sinatra-contrib -> sinatra -> rack
みたいな依存の階層関係があるgemから、複数のgemをGemfile/.gemspecに書いてしまうと、親のgemの方がメジャーバージョンアップした時に依存解決できなくなる事がある。


sinatra-rocketioのリポジトリで

bundle install
するとこういうエラーがでてた(もう直った)
Fetching gem metadata from https://rubygems.org/........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Bundler could not find compatible versions for gem "sinatra":
In Gemfile:
sinatra-rocketio (>= 0) ruby depends on
sinatra (~> 1.3.0) ruby

sinatra-rocketio (>= 0) ruby depends on
sinatra (1.4.1)
何を言っているのかわからない・・


これがsinatra-rocketio.gemspec
gem.add_dependency "rack", ">= 1.5.0"
gem.add_dependency "sinatra", ">= 1.3.6"
gem.add_dependency "eventmachine", ">= 1.0.0"
gem.add_dependency "event_emitter", ">= 0.2.3"
gem.add_dependency "sinatra-contrib", ">= 1.3.2"
gem.add_dependency "sinatra-cometio", ">= 0.3.7"
gem.add_dependency "sinatra-websocketio", ">= 0.1.5"

これはsintra1.4がリリースされたが、sinatra-contribのgemspecがsinatra1.3系統の最新版を使うようになっているため。

上の行から解釈していくっぽいので、sinatra-contribをsinatraより上に書けばsinatra1.3.6が使われる。


というわけでこういう感じで書けばいい
gem.add_dependency "sinatra-cometio", ">= 0.3.8"
gem.add_dependency "sinatra-websocketio", ">= 0.1.6"
gem.add_dependency "sinatra"
gem.add_dependency "eventmachine", ">= 1.0.0"
gem.add_dependency "event_emitter", ">= 0.2.3"

これのおかげで、bundlerでインストールするとずっと0.0.3がインストールされちゃってたけど今の最新は0.0.8です

0

RocketIOの全イベント取得方法

RocketIOではevent_emitterという(node.jsのを参考にして作った)イベント管理機構を使っている(Ruby用JavaScript用


event_emitterの全てのイベントを取得する手段がある


Ruby

user.on :* do |event_name, args|
puts event_name # 呼び出されたイベント名
p args
end


JavaScript
user.on('*', function(event_name, data){
console.log(event_name + ' was called');
console.log(data);
});


RocketIOでも、これを使っているので
Sinatra::RocketIO.on :* do |event_name, args|
puts "#{event_name} - #{args.inspect}"
end
とするとサーバー側で起こったイベント全部取れるし

var io = new RocketIO().connect();
io.on("*", function(event_name, args){
console.log(event_name);
console.log(args);
});
これでクライアント側のイベント全部取れる。


関係ないクライアントにデータ送信してるかも、とか、余計なイベント送ってるかも等の調査に便利。

0

Sinatra RocketIOというプラグイン作った、これでWebSocketとCometが使える

nodeのSocket.ioっぽい物のRuby版を作った。

https://github.com/shokai/sinatra-rocketio


依存

EventMachineが有効なWebサーバー(thinとか)と、jQueryが必要。
Rubyは1.8.7〜2.0.0まで動く。


Sinatraで使う


インストール
gem install sinatra-rocketio


ブラウザとか回線に応じてWebSocketとCometの使える方が自動的に選ばれる。
切断されても定期的に再接続を試みるようになってる。


sinatraに読みこむだけで使える。sinatraのプロセス内にwebsocketサーバーも組み込まれる。
require 'sinatra'
require 'sinatra/rocketio'

modular styleではclass内でregisterすればok
class MyApp < Sinatra::Base
register Sinatra::RocketIO
## 略
end


サーバーからブラウザにpushするの簡単にできる


Sinatra側
io = Sinatra::RocketIO

io.push :temperature, 35 # 全てのクライアントに送信
io.push :light, {:value => 150}, {:to => session_id} # 特定のクライアントへ
pushはsinatraのgetとかpostの中に書いてもいい。どこに書いてもpushできる。


クライアント側。JSライブラリを読み込む。
<script src="<%= rocketio_js %>"></script>
hamlの場合はこう
%script{:src => rocketio_js}

そしてJavaScriptに受信イベント書く
var io = new RocketIO().connect(); // WebSocketとCometの適当な方が使われる
io.on("temperature", function(value){
console.log("温度 : " + value);
}); // => "温度 : 35"
io.on("light", function(data){
console.log("明るさ : " + data.value);
}); // => "明るさ : 150"

通信というより、クライアント側でイベント登録しておいてサーバーから呼び出せる感じ。


クライアントからサーバーへ送信


JS側
io.on("connect", function(){
io.push("chat", {name: "shokai", message: "hello"});
});

Ruby側
io.on :chat do |data, client|
puts "#{data['name']} : #{data['message']} <#{client.session}> type:#{client.type}"
io.push :chat, data # 全クライアントに返送
end
逆も同じ要領でできる。”chat”というイベントを登録しておいて受信してみた。
チャットなので全クライアントに返送もする。

サーバーは、クライアントのセッションIDとtype(websocketかcometか)も取れる。


通信方法のまとめ


サーバー側
Sinatra::RocketIO.on “イベント名” でイベント登録して受信。
Sinatra::RocketIO.push “イベント名”, “データ” で送信。

クライアント側
var io = new RocketIO().connect(); で得たio(RocketIOのインスタンス)を使う。
io.on(“イベント名”, コールバック関数) でイベント登録して受信。
io.push(“イベント名”, データ) でサーバーに送信。


だいたいこんな感じで使える。

試しにチャットを作ってみた


http://rocketio-chat.herokuapp.com/
herokuなのでwebsocketが使えなくて全員cometで接続される。

http://dev.shokai.org:4000/
さくらVPSサーバーなのでwebsocket&comet両方とも使える。


ソースコードはここ https://github.com/shokai/rocketio-chat-sample
サーバー側Ruby 25行、クライアント側JS 39行でチャット作れてた。


構成

Comet部分はSinatra CometIOという独立したSinatraプラグインで実装してある。これを一番最初に作った。
WebSocket部分もSinatra::WebSocketIOという、Cometと同じAPIで使えるやつで実装した。
RocketIOはこれらをまとめた。


イベント登録して発火する部分は、event_emitter.jsevent_emitter.rbというJSとRubyそれぞれで使えるイベント管理ライブラリを作った。


性能

2年前に買ったMacMini上のVMWareで動かしてるUbuntuで
WebSocketは1万5千クライアント、Cometは500クライアントぐらい同時接続して普通に動いた。
WebSocketは1万クライアント接続して、そのうち1つがサーバーにpush、サーバーが全クライアントに返送し終わるまで平均0.7秒ぐらい。
CPUが良ければもっといけると思う。

同時接続数を1万以上やるにはOSのファイルディスクリプタの設定とか必要。
https://github.com/shokai/sinatra-websocketio/wiki/C10K

0

Ubuntu/Debianでarduino_firmata動くようになった

これ → 64bit Ubuntu+Arduino UNOでarduino_firmataが動かないらしい

なおった。Raspberry Piでも安定して動くようになった。

serialport gemの read_nonblockとwrite_nonblockを使っていると、DebianやUbuntuで動かなくなっていたのでデフォルトでnonblock使わないようにした。


今まで通りノンブロッキングIOで使いたい人は接続時に

arduino = ArduinoFirmata.connect "/dev/tty.usb-device-name", :nonblock_io => true
で接続すればいい

Arduino Firmata on Ruby