ユーザ用ツール

サイト用ツール


alworker:サーバーからの情報を画面に表示する

差分

この文書の現在のバージョンと選択したバージョンの差分を表示します。

この比較画面にリンクする

alworker:サーバーからの情報を画面に表示する [2014/11/12 19:26] (現在)
ライン 1: ライン 1:
 +====== AlWorker サーバーからの情報を画面に表示する ======
 +
 +サーバー側で発生する何らかの情報を、クライアント画面(ウェブブラウザ)に表示するためには、従来ポーリングしか方法がありませんでした。しかし、ポーリングにはいくつかの課題が残ります。
 +  * リアルタイム性が、ポーリングサイクルに制限される。
 +  * イベントが発生していない時もポーリングのための通信が発生するため、サーバー及びネットワークへの負荷が大きい。
 +  * 前回のポーリングから現在までに発生した全てのイベントを、もれなくクライアントに伝えるには、相応の工夫が必要になる。
 +
 +Aloneでは、これに対し2種類の方法を提供します。
 +  * Long poll (COMET)による方法
 +  * ServerSentEventsによる方法
 +
 +====== Long poll (COMET)による方法 ======
 +
 +COMETとは、ポーリングベースですが、サーバー側の応答をすぐに返さず保留しておき、何らかのイベントが発生した時に応答することを繰り返すことで、擬似的にサーバープッシュを実現する技術です。\\
 +サーバーで発生したイベントを、もれなくクライアントへ伝えるために、番号付きメッセージキューを使います。\\
 +
 +===== サンプル =====
 +
 +サーバーでランダム時間で発生するイベントを、画面上にリアルタイムで表示する。
 +
 +==== サーバー ====
 +<file ruby comet_server.rb>​
 +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()
 +</​file>​
 +
 +==== Alone cgiコントローラ ====
 +<file ruby main.rb>
 +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
 +</​file>​
 +
 +==== 画面テンプレートとJavaScript ====
 +<file html index.rhtml>​
 +<%= 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 %>
 +</​file>​
 +
 +===== 注意点 =====
 +  * イベントの間隔が長く開く場合(例えば3分間)、ブラウザやウェブサーバー、プロキシーサーバー、NATBOXなどが接続を強制切断する場合があります。
 +  * そのため、実用的には定期的にアイドルイベントを流すなどして、自主的に接続を保つ必要があります。
 +
 +===== プロトコル詳細 =====
 +
 +==== cgiリクエスト ====
 +
 +リクエスト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}]]
 +
 +[[alworker:​javascriptからIPCをコールする#​プロトコル詳細|「JavaScriptからIPCをコールする」のプロトコル詳細]]も併せて参照してください。
 +
 +====== ServerSentEventsによる方法 ======
 +
 +ブラウザがサポートしていれば、Server Sent Events ( [[http://​dev.w3.org/​html5/​eventsource/​|w3.org]] ) を使う方法が以下の点で優れています。
 +  * 接続が連続する。(COMETはイベントのたびに切断/接続を繰り返す)
 +  * そのため、負荷が少なくリアルタイム性に優れている。
 +  * 切断が切れた場合、ブラウザが自動的に再接続してくれる。
 +
 +
 +
 +===== サンプル =====
 +
 +サーバーでランダム時間で発生するイベントを、画面上にリアルタイムで表示する。
 +
 +==== サーバー ====
 +<file ruby ssev_server.rb>​
 +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()
 +
 +</​file>​
 +
 +==== Alone cgiコントローラ ====
 +<file ruby main.rb>
 +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
 +
 +</​file>​
 +
 +==== 画面テンプレートとJavaScript ====
 +<file html index.rhtml>​
 +<%= 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 %>
 +
 +</​file>​
 +
 +
 +===== 注意点 =====
 +  * COMET同様、他要因によって接続が切断される場合がありますが、ほとんどの場合はブラウザが自動的に再接続を行います。
 +  * ただし、接続が切れてから実際にブラウザが再接続するまでタイムラグがある場合がありますので、定期的なアイドルイベントを併用するのは良いアイデアです。
 +
 +===== ServerSentEvents 詳細 =====
 +
 +==== cgiリクエスト ====
 +
 +リクエスト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"​] を参照して送信済み/未送信イベントを識別します。
  
alworker/サーバーからの情報を画面に表示する.txt · 最終更新: 2014/11/12 19:26 by hirohito