成果物
デモ(Heroku) http://fluxchat.takeyu-web.com/
ソースコード https://github.com/takeyuweb/rails-fluxchat-example
キーワード
- Rails 4.2
- ActionCable
- ES6
- React
- Flux (Alt)
Action Cable?
まだコード全部は読んでないのですが、WebSocketを使ったリアルタイム通信を手軽に実装するためのサーバ・クライアントフレームワーク実装だと感じました。(違ってたらご指摘下さい!)
Railsらしく規約に従ってサーバ・クライアント側で所定のクラスを作っておけば、実際の接続処理や接続の監視・再接続、受け取ったデータのハンドリングなど細かいことはActionCableが良い感じにしてくれるっぽい。
なおAction CableのサーバはこれまでのWebサーバ(rails s
なやつとか)とは別に立ち上げる必要があります。(もちろんポートも空けます)
# cable/config.ru
require ::File.expand_path('../../config/environment', __FILE__)
Rails.application.eager_load!
require 'action_cable/process/logging'
run ActionCable.server
で28080番ポートでWebSocketサーバを起動(※ポートは決まってないのでなんでもいいです。ここでは公式のサンプルに合わせました)
$ bundle exec puma -p 28080 cable/config.ru
HTTPサーバはもちろん別
$ bundle exec rails s
なおRails5の新機能ではありますがRails4でも使えます。
gem 'rails', '4.2.4'
gem 'actioncable', github: 'rails/actioncable'
gem 'puma'
ポイント
FluxとWebScoket(ActionCable)
Fluxについてまだ勉強不足なので理解違いがあるかもしれませんが、図の「Web API Utils」にあてはめて実装してみました。
送信
View (React Component) -> Action Creator -> Channel (ActionCable Subscription)
Viewで発生したイベントからAction Creatorを経由してActionCableのChannelへメッセージを投げます。
受信
Channel (ActionCable Subscription) -> Action Creator -> Dispatcher -> Store -> View (React Component)
Channelへ入ってきたデータをAction Creatorに渡し、後のことは任せます。
Action Cable
認証
WebSocketではCookieが使えるので、署名済みCookieを使うのが楽です。
Action Cableで送信するデータ
公式のサンプルアプリではRails5の新しいRendererでアクション外で部分テンプレートを処理しHTMLを送りつける感じですが、Action Cable自体はなんでも送れるので、クライアント側でそれにあわせた処理をすれば良いです。
今回のサンプルでは単に送り側(Ruby/Rails)でモデルインスタンスを.to_json
して、受け取り側(JavaScript/ActionCable.Subscription)でJSON.parse
してみました。
コントローラやバックグラウンド処理からクライアントに通知を送る
ActionCable.server.broadcast
であとはよしなに。時間がかかることもあるので、Active Jobと組み合わせて非同期で実行するのがベター。
class MessageRelayJob < ApplicationJob
def perform(message)
if message.to.present?
# 宛先が指定されている場合はそこと自分に送る
ActionCable.server.broadcast "chat_activity:#{message.uuid}", message.to_json
ActionCable.server.broadcast "chat_activity:#{message.to}", message.to_json
else
# そうでなければ全体へ
ActionCable.server.broadcast 'chat_activity', message.to_json
end
end
end
実際には全体へのブロードキャストは宛先が死ぬほど増えそうなので、チャットであればチャットルームIDなどで絞り込む必要がありそう。
require('react')みたいなことがしたい、あとES6で書きたい
俺の最近のRailsのJS開発環境を教えてやる
http://qiita.com/kozo002/items/a276569e85395fce801e
こちらの記事が参考になりました。
楽しい
そんな感じでとりあえずやってみたAction CableとFluxですがなかなか楽しいです。
実運用ではコネクション張りっぱなしによるリソースの消費がどんな具合かとか、負荷テストはどうやるの?とか、サーバは何使う?とか、経路上のたとえばELBがどうなっているかとか、Android標準ブラウザの対応が4.4以降で割と非対応端末多そうな問題とかいろいろ考えることはありますが、ぜひ使っていきたいと思っています。
気になるセキュリティ
未調査。
セキュリティ回りで参考になりそうな記事メモ。
IPA セキュア・プログラミング講座:Webアプリケーション編
近年のJavaScriptプログラミングでは処理全体をdocument#readyのクロージャで括るのが一般的ですが、その場合仮にXSSの脆弱性があったとしても外部から接続済みのWebSocketインスタンスにアクセスすることは不可能です。
DHHのサンプルや今回のデモチャットではwindow.App.cable.connection.webSocket
でWebSocketインスタンスがとれてしまうので、そのあたり取り回しを考えた方がよいかも。