- チャット機能を実装する場合、WebSocketを使う、Server Sent Event(SSE)を使う、Pusherなどのサービスを活用するなど、いくつかの方法がありますが、私のケースでは、WebSocketを使うのが最もマッチしていたので、WebSocketを利用することを考えます。この辺の選定についてはまた別途まとめたいと思います
- RailsでWebSocketを使う場合、
websocket-rails
を使うのが、手っ取り早くて良いのではないかと思います - そこで、
websocket-rails
での、チャネル、プライベートチャネル、セキュリティ、認証あたりの関係性が分かりにくかったので、自分なりに整理してみました
websocket-rails
の基本的な使い方
Event Routerへのイベントの追加
- JSクライアントからのリクエストをRailsのコントローラーとマッピングしている
WebsocketRails::EventMap.describe do
namespace :tasks do
subscribe :create, :to => TaskController, :with_method => :create
end
end
JSクライアントからイベントをトリガー
var dispatcher = new WebSocketRails('localhost:3000/websocket');
dispatcher.trigger('tasks.create', {name: "hogehoge"});
イベントのハンドリング
-
send_message
で、クライアント側にプッシュできる-
send_message
は、このイベントを実行したクライアントにしか送信しない- クライアントが接続した際に"接続完了しました"のようなメッセージを表示する場合に使える
-
- 一方、
broadcast_message
は、接続している全てのクライアントにプッシュできる
class TaskController < WebsocketRails::BaseController
def create
task = Task.new message
if task.save
send_message :create_success, task, :namespace => :tasks
else
send_message :create_fail, task, :namespace => :tasks
end
end
end
JSクライアントでレスポンスを受け取る
-
dispatcher.bind
で、コールバックを登録しておく
dispatcher.bind('tasks.create_success', function(task) {
console.log('successfully created ' + task.name);
});
チャネルを使用する場合
- チャネルは、WebSocket接続したクライアントに対して任意のタイミングでメッセージを送ることができる
- チャネルは、複数のチャットルームを管理して、特定のクライアントにブロードキャストする必要がある場合に使える
- チャネルがない場合には、イベントを指定して、そのイベントをトリガーしたクライアントにプッシュする(send_message)か、WebScoketに接続している全クライアントにブロードキャストする(broadcast_message)するかしかない
Event Routerへのイベントの追加
WebsocketRails::EventMap.describe do
subscribe :new_post, to: ChatController, with_method: :create_post
end
JSクライアントでチャネルをサブスクライブする
-
dispatcher.subscribe
でチャネルをサブスクライブすることができる- チャネル名は、乱数などで動的に指定することで、そのチャネル名を知る特定のクライアントだけがアクセスできるチャネルをつくることができる
var dispatcher = new WebSocketRails('localhost:3000/websocket');
postsChannel = dispatcher.subscribe('posts');
JSクライアントからイベントをトリガー
-
dispatcher.trigger
でイベントをトリガーできる(ここは、チャネル名ではない点に注意)
dispatcher.trigger('new_post', post);
イベントのハンドリング
-
WebsocketRails[:posts].trigger
とチャネル名を指定して、triggerをすることで、このチャネルにメッセージをブロードキャストできる
class ChatController < WebsocketRails::BaseController
def create_post
post = Post.new(message)
post.cook
if post.save
WebsocketRails[:posts].trigger 'new_post', post
else
puts "Failed to saved post"
end
end
JSクライアントでレスポンスを受け取る
- dispatcherではなく、
チャネル.bind
でコールバックを登録しておく
postsChannel.bind('new_post', function(post) {
/* イベント受け取り後の処理 */
});
Working with Channels · websocket-rails/websocket-rails Wiki · GitHub
プライベートチャネル
- プライベートチャネルは、1クライアントからの接続を保証するもの
- 接続した際に、他のクライアントが接続していれば、全部追い出して、そのクライアントだけを接続するようにする
- チャットのような2人のクライアントが接続するようなアプリケーションには向いていない
- このようなアプリケーションでプライベートチャネルを使いたい場合は、2つのプライベートチャネルを作って、それぞれにメッセージを送り合うようなことをする必要がある
- もしくは、チャネル名をユニークにすれば、そのユニークなチャネルにアクセスした限定されたクライアントだけがアクセスできる状況を作ることができる
- セキュアとは異なる概念
- 当たり前だけどプライベートとは、他の誰かが接続していないというだけで、その接続がセキュアというわけではない
- また、チャネル名をユニークにしても、そのチャネル名がわかれば接続できてしまう
- プライベートチャネルはパブリックから、プライベートに変更することができる
- デフォルトでは、パブリックチャネルに接続していたクライアントは、そのチャネルから追い出されてしまうが、以下の設定をすることでプライベートチャネルでも複数クライアントからの接続を維持することができる
config.keep_subscribers_when_private = true
Using Private Channels · websocket-rails/websocket-rails Wiki
How to broadcast data to specific subcsriber? · Issue #104 · websocket-rails/websocket-rails
How to prevent spam when using websocket-rails gem? - Stack Overflow
Security
-
window.location.protocol
がhttps
であれば、セキュアなプロトコルwss
で通信してくれる
WebSocket Security | Heroku Dev Center
websocket-rails: standaloneモードのサーバでSSL通信 - Qiita
UserManager
- UserMangerを使えば、特定のユーザーにメッセージを送ることができる
subscribe :client_connected to: WebsocketController, with_method: :client_connected
-
wss://localhost:3000/websocket?client_id=some_unique_id
でリクエストすれば、client_connectedがコールされて、client_idをもつuserが登録される
class WebsocketController < WebsocketRails::BaseController
def client_connected
WebsocketRails.users[params[:client_id]] = connection
end
end
Websocket Railsを使って特定のユーザーにメッセージを送る - 亀の速度で走る
current_user
-
ApplicationController
で、current_user
というメソッドが定義されていて、ユーザーがサインインし、かつWebSocketコネクションが確立されている場合、そのコネクションは、UserManagerに格納される- 逆に、current_userが定義されていない、もしくはサインインしていない場合は、UserManagerにコネクションは格納されない
- つまり、Deviseを使用していれば、自動的にUserManager.usersが手に入る
current_user以外
- ただし、DeviseでUser以外のモデルを使用している場合(例えばAdmin)、current_userではなく、current_adminとなる
- current_adminを使用したい場合は、user_classを変更することで対応できるとあるが、自分の場合は、
User
以外うまく動かなかった
- current_adminを使用したい場合は、user_classを変更することで対応できるとあるが、自分の場合は、
config.user_identifier = :id
config.user_class = Admin
- このプルリクにあるように、Standaloneモードで
NameError: uninitialized constant
が表示される場合は、procを使うと良いとある- でも、これをやってもUserManager.usersは空のまま。。。
Recommend overriding user_class with a proc · alexdunae/websocket-rails@3612137
認証/認可
- WebSocketのプロトコル自体は、認証や認可の仕組みは提供していないので、アプリケーションで実装する必要がある
-
WebsocketRails::BaseController
を継承したコントローラー内で、ApplicationControllerのメソッドは普通に使えるので、Deviseを使っている場合は、current_user
が使える - プライベートチャネルと組み合わせて、サーバー側で認証する方法が紹介されている
Using Private Channels · websocket-rails/websocket-rails Wiki
current_user is not compatible with Warden (Devise) · Issue #216 · websocket-rails/websocket-rails
Can't Access Helpers in Websocket Controller · Issue #3 · websocket-rails/websocket-rails
Register and Authenticate User · Issue #184 · websocket-rails/websocket-rails
websocket-rails gem and authentication - Stack Overflow
Filtering Channel Events · websocket-rails/websocket-rails Wiki
Standalone Mode
- スタンドアロンモードでは、Redisを起動して、イベント処理を任せることができる
WebsocketRails.setup do |config|
config.standalone = true
end
- 以下のコマンドで、スタンドアロンサーバーを起動、停止できる
- Redisを起動した上で、実行する
$rake websocket_rails:start_server
$rake websocket_rails:stop_server
Standalone Server Mode · websocket-rails/websocket-rails Wiki · GitHub
Onlineの検出
How do I tell if a user is online? | Ryan Epp
ActionCable Devise Authentication
Synchronization
Multiple Servers and Background Jobs · websocket-rails/websocket-rails Wiki · GitHub