Realtime.coのRESTful APIをRailsで使ってみました。
現在開発を応援しているサービスでユーザが自分のサイトに対してセキュリティスキャンを実行できます。セキュリティスキャンの種類がsslyzeからskipfishまで色々提供されていますのでスキャンの種類によって数秒で終わるものも何時間もかかるスキャンがありますのでスキャンが当然にバックグラウンドジョブとして非同期的実行されます。そこでスキャンが終えた後にブラウザでリアルタイム的な感じで通知を出してみたかったです。
サーバー側からクライアントに通知を送る方法をググった所でいくつかのサービスをすぐ発見しましたが条件がいくつか僕の中でありました:
- 初期費用がない
- しばらく使ってもほぼ無料
- Flashで作られていない(iOSなどで動いていないため)
- 出版-購読型モデル(PubSub pattern)
価格は主に同時接続数と月間イベント数で決まりますが、だいたいのサービスの無料版同は時接続数が非常に限られています。
そこで、同時接続数も月間無料イベント数も非常に優しいそうなRealtime.coを選びました。
| プラン | 同時接続数 | 同時セッション数 | 月間メッセージ数 | 価格 |
|---|---|---|---|---|
| Free | 1,000 | 30,000 | 5,000,000 | FREE |
| Ignite | 3,000 | - | 31,000,000 | $30 (+$1/mi.msg) |
| Ignite | 10,000 | - | 101,000,000 | $100 (+$1/mi.msg) |
| Ignite | 25,000 | - | 251,000,000 | $250 (+$1/mi.msg) |
| Corporate | - | - | - | $??? (+$1/mi.msg) |
サインアップ後にWeb consoleとちょっと遊んでみたら、最初の印象は確かにシンプルで個人的に好きと思いました。
Subscriptions画面で適当な名前をつかってサブスクリプションを追加したら、Application KeyとPrivate Keyを取得できます。
キー情報の下でデバグ用のコンソールやドキュメンテーションや現在使用状況や詳細設定を開くことができます。
ここは注意しないといけないのは、設定メニューでのauthenticationオプションをONにしないと、Application Keyさえあればどのチャネルのどの情報でもみれますので、ONにしたほうがよさそうな場合が多いでしょう。ちなみにですが、このオプションのデフォルトがOFFです。OFF状態で権限APIがトークンのチェックせずにエラーメッセージ吐かずに正常に処理が終わりますので、ちょっと気をつけないといけません。
APIは多数提供されているので安心できると思いますが、Rubyの場合には非同期的なORTCライブラリしか提供されていません。
半年位そのORTCライブラリをRailsのスレッド上やSidekiq上で使ってみましたが相性が悪くて、安定に動作させられませんでした。
その時に非常に簡単そうなREST APIを発見しました。通知を送るために3個のAPIを叩く必要性があります:
1) GET server/version
APIにアクセスできるためのエンドポイントを取得するAPIです。
正常に動く時にこの感じで動きます。
Request: https://ortc-developers.realtime.co/server/2.1?appkey=xxxxxx
Response: var SOCKET_SERVER = "http://ortc-developers2-euwest1-s0001.realtime.co";
def app_key; ENV['REALTIME_APP_KEY']; end
def private_key; ENV['REALTIME_PRIVATE_KEY']; end
require 'faraday/conductivity'
require 'faraday/adapter/net_http'
def connection access_point
Faraday.new access_point do |faraday|
faraday.request :url_encoded
faraday.adapter :net_http_persistent
end.tap{|c|c.headers[:accept_charset] = 'UTF-8'}
end
response = connection("https://ortc-developers.realtime.co").get "/server/2.1?appkey=#{app_key}"
new_access_point = response.body.to_s.scan(/var SOCKET_SERVER = \"(.*)\";/).flatten.first
2) POST authenticate
通知を送るにはトークンに対してチャネルを指定しないといけないです。
引数は
-
AT (Authentication Token)
有効な値: 許可されている文字が指定されていないですが、 [a-zA-Z0-9]* は大丈夫そうです。
適当な文字列。 -
PVT (Is Private)
有効な値: [01]
全然説明されていないオプションですが、確認している限りにPVTが1の場合に最初使うIPがトークンと
アタッチされます。0の場合にはトークンを持っていれば、どのIPでもsubscribeできます。 -
AK (Application Key)
管理画面で確認できるApplication Keyです -
PK (Private Key)
管理画面で確認できるPrivate Keyです -
TTL
TTL (Time to Live)
有効な値:[0-9]*例:1800
トークンのTTLです。単位は秒だと思います。 -
TP
TP (Total Permissions)
有効な値:[0-9]*
この値も説明されていないですが、このトークンで使いたいチャネルの数です。 -
チャネル (複数可能)
チャネル名
有効な値:[rw]{1}
使えるチャネルを指定できます。rは読み込みのみです。wは(現在の仕様だと)読み込みと書き込みの権限です。
def authenticate! access_point, channel, token
path = '/authenticate'
params = {
'AT' => token, # authentication token
'PVT' => '0', # is private
'AK' => app_key, # application key
'TTL' => '86400', # time to live
'PK' => private_key, # private key
'TP' => '1', # total permissions
channel => 'w',
}
response = connection(access_point).post path, params
raise 'failed to set permissions' unless response.status == 201 && response.body.to_s.include?('Created')
response
end
authenticate!(new_access_point, 'test_channel', 'test_token')
3) POST send
メッセージを送信するAPIです。
引数は
-
AT (Authentication Token)
有効な値: 許可されている文字が指定されていないですが、 [a-zA-Z0-9]* は大丈夫そうです。
適当な文字列。 -
AK (Application Key)
管理画面で確認できるApplication Keyです -
PK (Private Key)
管理画面で確認できるPrivate Keyです -
C (Channel)
チャネル名 -
M (Message)
メッセージです。realtime.coのメッセージの長さの制限がないですが、チャンクの最大サイズは800バイトです。
メッセージのヘッダーもあります。ヘッダーのformatは
[0-9a-z]{8}_[0-9]*-[0-9]*.{1,800}
です。
最初の8文字はメッセージのIDです。
その次は_とメッセージチャンクの番号です。オフセットは1です。
その次は-とチャンクの数です。
その後はチャンクがきます。
require 'securerandom'
def send_message! access_point, channel, token, message
path = '/send'
params = {
'AT' => token, # authentication token
'AK' => app_key, # application key
'PK' => private_key, # private key
'C' => channel, # channel
'M' => nil, # message
}
message_id = SecureRandom.hex(4)
chunks = message.scan(/.{1,400}/).collect
chunks.each_with_index do |chunk, index|
chunk = "#{message_id}_#{index + 1}-#{chunks.size}_#{chunk}"
params['M'] = chunk
connection(access_point).post path, params
end
end
send_message! new_access_point, 'test_channel', 'test_token', 'Test message'
Realtimeの全体印象はサービスは良くて、ドキュメンテーションもそこそこありますが、
オプションの説明やエラーハンドリングについてもうちょっと期待できると思います。
gemをリリーズしたいと思います。その後またアップデートを書きたいと思います。