Action Cable + Flux + React でリアルタイムチャットデモを作ってみた


成果物

デモ(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サーバを起動(※ポートは決まってないのでなんでもいいです。ここでは公式のサンプルに合わせました)
bash
$ bundle exec puma -p 28080 cable/config.ru

HTTPサーバはもちろん別
bash
$ 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アプリケーション編

Webアプリ開発者のためのHTML5セキュリティ入門

Herokuで作るWebSocketアプリケーション

WebSocket通信のメリットを考える

近年のJavaScriptプログラミングでは処理全体をdocument#readyのクロージャで括るのが一般的ですが、その場合仮にXSSの脆弱性があったとしても外部から接続済みのWebSocketインスタンスにアクセスすることは不可能です。

DHHのサンプルや今回のデモチャットではwindow.App.cable.connection.webSocketでWebSocketインスタンスがとれてしまうので、そのあたり取り回しを考えた方がよいかも。