サーバー側で発生する何らかの情報を、クライアント画面(ウェブブラウザ)に表示するためには、従来ポーリングしか方法がありませんでした。しかし、ポーリングにはいくつかの課題が残ります。
Aloneでは、これに対し2種類の方法を提供します。
COMETとは、ポーリングベースですが、サーバー側の応答をすぐに返さず保留しておき、何らかのイベントが発生した時に応答することを繰り返すことで、擬似的にサーバープッシュを実現する技術です。
サーバーで発生したイベントを、もれなくクライアントへ伝えるために、番号付きメッセージキューを使います。
サーバーでランダム時間で発生するイベントを、画面上にリアルタイムで表示する。
require "al_worker_ipc" require "al_worker_message" class CometServer < AlWorker def initialize2() @ipc = Ipc.new() @ipc.chmod = 0666 @ipc.run( self ) @msg = NumberedMessage.new() end ## # アイドルタスクで、ランダム時間でメッセージ(イベント)を発生させる # def idle_task() loop do sleep rand(10) @msg.send( {"time"=>Time.now.to_s} ) end end ## # クライアント(ブラウザ, JavaScript)からのリクエストを受け、 # メッセージ(イベント)の配列を返す。 # listen {"TID":n} # def ipc_a_listen( sock, param ) tid = param["TID"].to_i if tid > 0 # TIDが有効なら、キュー内TID以降のメッセージを返す。 # もしまだTID番が発生していなければ、ここでウェイトする。 ret = @msg.receive( tid ) else # TIDが無効なら、初期アクセスとみなして全メッセージを返す。 ret = @msg.queue.dup() end reply( sock, 200, "OK", ret ) end end server = CometServer.new( "comet_server" ) server.parse_option() server.daemon()
require "al_template" require "al_form" require "al_worker_ipc" class AlController include AlWorker::IpcAction def initialize() @ipc = AlWorker::Ipc.open( "/tmp/comet_server" ) end def action_index() AlTemplate.run( 'index.rhtml' ) end end
<%= header_section %> <script type="text/javascript" src="/js/alone.js"></script> <%= body_section %> サーバー発生イベント <div id="event_display" style="border: 1px solid black"> </div> <script type="text/javascript"> function listen( tid ) { var e = document.getElementById( "event_display" ); var ipc = new Alone.Ipc(); // コールバック // (ここで接続は一旦切れている) ipc.success = function( data, status ) { var tid = 0; // 前回のTIDから現在までに発生した全てのイベントが // 配列として data に渡されるので、繰り返して表示する。 for( var i = 0; i < data.length; i++ ) { var html = Alone.escape_html( data[i].time ) + '<br>'; e.innerHTML = e.innerHTML + html; tid = data[i].TID; } // データの最終TID+1を渡して、次回そこからのイベント送信を // 依頼することで、取りこぼしがなくなる。 listen( tid + 1 ); }; // IPC開始 ipc.call( "listen", {TID: tid} ); } // サーバへ問い合わせ開始 // 初回は無効なTIDを指定して、キューの全データを取得する。 listen( 0 ); </script> <%= footer_section %>
リクエストURL例
http://*/cgi-bin/index.rb?ctrl=comet&action=ipc&ipc=listen&arg={"TID":5}
action=ipc は固定値です。ipc= は、サーバー側受付IPC名を指定します。
JavaScript中では、以下の通りライブラリを使用してIPC callします。
ipc.call( "listen", {TID: tid} );
レスポンスは、要素2の配列をJSONエンコードしたものが返ります。
例
["200. OK", [{"time":"2013-06-04 12:13:20 +0900","TID":5},{"time":"2013-06-04 12:13:26 +0900","TID":6}]]
「JavaScriptからIPCをコールする」のプロトコル詳細も併せて参照してください。
ブラウザがサポートしていれば、Server Sent Events ( w3.org ) を使う方法が以下の点で優れています。
サーバーでランダム時間で発生するイベントを、画面上にリアルタイムで表示する。
require "al_worker_ipc" require "al_worker_message" class SsevServer < AlWorker def initialize2() @ipc = Ipc.new() @ipc.chmod = 0666 @ipc.run( self ) @msg = NumberedMessage.new() end ## # アイドルタスクで、ランダム時間でメッセージ(イベント)を発生させる # def idle_task() loop do sleep rand(10) @msg.send( {"time"=>Time.now.to_s} ) end end ## # クライアント(ブラウザ, JavaScript)からのリクエストを受け、 # メッセージ(イベント)をServerSentEventsプロトコルで返す。 # def ipc_a_listen_ssev( sock, param ) tid = param["LAST_EVENT_ID"] == 0 ? @msg.tid : param["LAST_EVENT_ID"] @msg.cycle( tid + 1 ) { |m| sock.puts "id: #{m[:TID].to_i}" sock.puts "data: #{m['time']}" sock.puts "" } end end server = SsevServer.new( "ssev_server" ) server.parse_option() server.daemon()
require "al_template" require "al_form" require "al_worker_ipc" class AlController include AlWorker::IpcAction def initialize() @ipc = AlWorker::Ipc.open( "/tmp/ssev_server" ) end def action_index() AlTemplate.run( 'index.rhtml' ) end end
<%= header_section %> <script type="text/javascript" src="/js/alone.js"></script> <%= body_section %> サーバー発生イベント <div id="event_display" style="border: 1px solid black"> </div> <script type="text/javascript"> function listen_ssev() { var e = document.getElementById( "event_display" ); // action:"ssev"は固定値。 // ipc:は、サーバー側受付IPC名を指定する。 var uri = Alone.make_uri({ action:"ssev", ipc:"listen_ssev" }); var evs = new EventSource( uri ); evs.onmessage = function( ev ) { var html = Alone.escape_html( ev.data ) + '<br>'; e.innerHTML = e.innerHTML + html; } } // サーバへ問い合わせ開始 listen_ssev(); </script> <%= footer_section %>
リクエストURL例
GET http://*/cgi-bin/index.rb?ctrl=ssev&action=ssev&ipc=listen_ssev
action=ssev は固定値です。ipc= は、サーバー側受付IPC名を指定します。
JavaScript中では、以下の通り、ライブラリを使用してURLを生成します。
var uri = Alone.make_uri({ action:"ssev", ipc:"listen_ssev" });
ブラウザによる再接続時のリクエストには、ブラウザによって自動的に LAST_EVENT_ID パラメータがリクエストヘッダに付与されます。
ServerSentEventsプロトコルでデータを返します。
例
id: 1 data: xxxxxx (改行) id: 2 data: yyyyyy
LAST_EVENT_IDは、Aloneフレームワークによってパラメータから取得できるように構成されますので、param["LAST_EVENT_ID"] を参照して送信済み/未送信イベントを識別します。