#316 Private Pub
- Download:
- source codeProject Files in Zip (80.9 KB)
- mp4Full Size H.264 Video (15.2 MB)
- m4vSmaller H.264 Video (8.28 MB)
- webmFull Size VP8 Video (10 MB)
- ogvFull Size Theora Video (17.4 MB)
エピソード260ではFayeを使ってユーザのブラウザをリアルタイムに更新する方法を紹介しました。Fayeは優れているものの、動作させるまでに少し手間がかかります。完全にセキュアな状態にしたい場合は特にそうです。これを解決するためにRyan Batesが作成したのが、Fayeの上で動作するPrivate Pubというgemです。彼が目指したのは、Railsアプリケーションでリアルタイムイベントをさらに簡単に配信/購読できるようにすることです。
今回扱うアプリケーションはシンプルなチャット用アプリケーションです。ページの下の方にあるテキストフィールドにメッセージを入力して「Send」をクリックすると、ブラウザはメッセージを保存するためにAJAXリクエストを送信し、ブラウザを更新してメインのチャットウィンドウにメッセージを追加します。
しかしこのアプリケーションには問題があります。複数のチャットクライアントが開いている場合、メッセージが送信されたときにすべてがリアルタイムには更新されません。メッセージを送信したクライアントだけに新しいメッセージが表示されます。他のすべてのクライアントは、メッセージを表示するためにページをリロードする必要があります。この問題を解決する方法はいくつかあります。一つは各クライアントが数秒ごとに新規メッセージを確認するためにポーリングをおこなう方法ですが、ベストなアイデアとはありません。もう一つの方法は、各クライアントでサーバへのソケットコネクションを開いたままにして、新規メッセージが来たらサーバがそれをプッシュして、クライアントにリアルタイムに表示されるようにします。この方法の問題は、Railsのアーキテクチャが長いリクエストを扱うように設定されていないという点です。
FayeとPrivate Pubがこの問題を解決してくれます。その詳細を見る前に、我々のチャットアプリケーションが動作する様子を見てみましょう。まずチャットページのビューテンプレートから始めます。
<h1>Chat</h1> <ul id="chat"> <%= render @messages %> </ul> <%= form_for Message.new, remote: true do |f| %> <%= f.text_field :content %> <%= f.submit "Send" %> <% end %>
このテンプレートはmessages
と呼ばれる部分テンプレート(partial)をrenderしてメッセージのリストを表示し、新規メッセージを作成するためのフォームが含まれています。フォームはremote: true
オプションを使用してAJAXリクエストを介して送信されます。フォームが送信されるとMessagesController
のcreate
アクションに送られます。次にそれを見てみましょう。
class MessagesController < ApplicationController def index @messages = Message.all end def create @message = Message.create!(params[:message]) end end
create
アクションはとてもシンプルです。新規のMessage
レコードを作成しますが、このアクションがAJAXを介して呼び出されてJavaScriptテンプレートを表示するときに、何もレスポンスがないことに注意してください。そのJavaScriptテンプレートは以下のとおりです。
$("#chat").append("<%= j render(@message) %>");
$("#new_message")[0].reset();
このJavaScriptは、新規に作成されたメッセージをチャットリストの末尾に追加し、新規メッセージを入力するテキストフィールドの中身をクリアします。やりたいことは、このメッセージをすべてのチャットクライアントにbroadcastして、送信したクライアントでだけでなく、すべてで新規メッセージが表示されるようにすることです。Private Pubを利用すれば、これを実現することができます。
Private Pubのインストール
Private Pubは通常の方法でインストールします。アプリケーションのgemfileにgemを追加してbundle
コマンドを実行します。インストールをおこなうと、同時にFayeとその依存関係もインストールされます。
source 'http://rubygems.org' gem 'rails', '3.1.3' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.5' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'private_pub'
次に以下のジェネレータを実行して、Fayeサーバを起動するのに必要な設定ファイルとRackupファイルを生成します。
$ rails g private_pub:install create config/private_pub.yml create private_pub.ru
以下のコマンドを実行してそのRackサーバを起動します。
$ rackup private_pub.ru -s thin -E production >> Thin web server (v1.3.1 codename Triple Espresso) >> Maximum connections set to 1024 >> Listening on 0.0.0.0:9292, CTRL+C to stop
このコマンドは、本番稼働環境でFayeを動作させるのに必要なThinサーバを使ってFayeを起動します。最後にアプリケーションのJavaScript manifestファイルにprivate_pub
を追加します。
// This is a manifest file that'll be compiled into including all the files listed below. // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. // //= require jquery //= require jquery_ujs //= require private_pub //= require_tree .
もし対象のアプリケーションがRails 3.1よりも前のバーションを使用している場合、このJavaScriptファイルをlayoutファイルにインクルードします。
アプリケーションでPrivate Pubを使用する
Private Pubが設定できたら、簡単にチャンネルを購読してそのチャンネルに更新通知を発行することができます。必要な作業は、ビューテンプレート(例えば今回作成したメッセージを表示するページ)でチャンネルを購読するために、subscribe_to
を呼び出してチャンネル名を渡すだけです。Fayeのチャンネルの形式はパスの表記に似ていて、ここでは/messages/new
を使用します。
<h1>Chat</h1> <ul id="chat"> <%= render @messages %> </ul> <%= form_for Message.new, remote: true do |f| %> <%= f.text_field :content %> <%= f.submit "Send" %> <% end %> <%= subscribe_to "/messages/new" %>
次にAJAXリクエストの結果として表示されるJavaScriptテンプレート(今回の例では新規のチャットメッセージを表示する部分)で、コードをpublish_to
ブロックでラップします。
<% publish_to "/messages/new" do %> $("#chat").append("<%= j render(@message) %>"); $("#new_message")[0].reset(); <% end %>
これをおこなうことはつまり、このJavaScriptが元のリクエストをおこなったクライアントだけに返されるのではなく、購読しているすべてのクライアントの/messages/new
チャンネルに配信されることを意味します。ではこれを試してみましょう。アプリケーションを2つの別々のブラウザで開き、片方でメッセージを入力すると、すぐにもう一方に表示されます。
つねにAJAXレスポンスから直接チャンネルに発行したいとは限りません。そうではなく例えばコントローラアクションから発行したい場合もあるでしょう。これをRubyコードのどこからでもおこなうことができます。PrivatePub.publish_to
を使って、希望する発行先のチャンネル名とすべての購読しているクライアントで実行したいJavaScriptコードを渡します。これをアラートを返す方法で実際にやってみます。
def create @message = Message.create!(params[:message]) PrivatePub.publish_to("/messages/new", "alert('#{@message.content}');") end
一つのブラウザでメッセージを入力すると、両方のブラウザでアラートが表示されます。
JSONデータを扱う
JavaScriptコードではなくJSONデータを使った処理を好むならそれも可能です。その場合は文字列以外のオブジェクト(例えばハッシュ)をpublish_to
に渡すとJSONに変換されます。
def create @message = Message.create!(params[:message]) PrivatePub.publish_to("/messages/new", message: @message) end
この方法をとる場合、返されるJSONを処理するために少しJavaScriptを書きます。これをmessages.js.coffee
に記述します。
PrivatePub.subscribe "/messages/new", (data, channel) -> alert data.message.content
このファイルでPrivatePub.subscribe
を呼び出します。これは引数にチャンネル名とコールバック関数(data
とchannel
を引数にとる)をとります。このコールバックはチャンネルがJSONを受け取るたびに起動されます。我々のコードでは、返されたデータのmessage.content
をただアラートで表示するだけです。ページをリロードして再度メッセージを入力すると、サーバから返されたJSONデータに基づくアラートが表示されます。
Private Pubを利用する一つの利点は、デフォルトでチャンネルがプライベートになることです。つまりユーザが明示的に購読していないチャンネルを受信してしますことを心配する必要がありません。これによって例えば、チャンネルを購読しているユーザだけがメッセージを読むことができる、プライベートなチャットルームを簡単に作成することができます。また一定期間後に購読を期限切れにすることで、ログアウト後にユーザから見えないようにできます。Private Pubを利用するRailsアプリケーションにはprivate_pub.yml
ファイルがあり、そこで設定をおこないます。
development: server: "http://localhost:9292/faye" secret_token: "secret" test: server: "http://localhost:9292/faye" secret_token: "secret" production: server: "http://example.com/faye" secret_token: "210eb617b6ce1c351d986a3185d34025cf42e5091a37502f18f595f7e8773853" signature_expiration: 3600 # one hour