サンプルチャット
試しにということで最低限の骨組みだけのものを書いてみました。チャット初めて書きました。Thread::Queue
も初めて使いました。
sonota88/sinatra-webrick-samplechat
https://github.com/sonota88/sinatra-webrick-samplechat
現時点( タグ=20190327 )では サーバ側が 38 行、 JavaScript が 109 行。
短いのでサーバ側だけ貼っておきます。
require 'sinatra' require 'sinatra/reloader' class ConnectionManager def initialize @map = {} # session id => queue end def broadcast(msg) @map.each_value { |queue| queue.enq(msg) } end def deq(session_id) unless @map.key?(session_id) @map[session_id] = Thread::Queue.new end @map[session_id].deq end end $conn_manager = ConnectionManager.new get "/" do send_file "index.html" end post "/comet/open" do $conn_manager.deq(params[:sessionid]) end post "/messages" do $conn_manager.broadcast( params[:sessionid] + ": " + params[:body] ) "ok" end
見ての通りですが、サンプルなので
- エラーハンドリングは適当
- タイムアウト、再接続などのハンドリングなし
- 離脱・リロードで発生する幽霊接続の後始末なし
- 永続化なし
経緯
- プロトタイプ、自分だけ or チーム内の数人でしか使わないちょっとしたツールをシュッと作りたいという場合に、いつも Sinatra を使っている
- べんり
- その延長でサーバからの push が必要なものが作りたくなって WebSocket を使おうとした
- WEBrick だとできないらしい
- Thin とかを使うらしい
- でもほんとにちょっとしたやつなんだけどな……
- Sinatra(+WEBrick)だけでなんとかできない?
- そういえば Comet(ロングポーリング)というのがありましたね
要件
上に書いたことと被りますが。 ここらへんが合わない場合はおとなしく WebSocket とかを使った方がよいはず。
- 接続数
- 多くないというか少ない。自分だけ or 数人程度
- なので、富豪的でOK
- 多くないというか少ない。自分だけ or 数人程度
- レイテンシも別に…
- メンテナンス性が良いというか、メンテナンスフリーなのが望ましい
- こういうごくごく小規模はツールは環境が変化して動かなくなることがツール自体を使わなくなる契機になりがち(けっこうばかにできない)なので、枯れてないものになるべく依存したくない……
実用するには
実用しようとするといろいろやらないといけないっぽくて、以下、ちょっと試してみたレベルで得た知見+適当な思いつきのメモ。ここらへんが膨らんできたらライブラリとかありものの利用を検討する。
@map
への追加だけやっていて削除がないので保持する接続(@map
の要素)が増え続ける- ブラウザのタブ開く・閉じる or リロード
- 不要になったものは消さないといけない
Queue#num_waiting == 0
のもの- だけではダメかも。接続ごとに最終接続日時を持っておいてそれと合わせて判断?
- キューに溜まり続けるパターンもありそう
- これも適当な閾値と最終接続日時を合わせて判断?
- タイムアウト・再接続
- enqueue が高頻度な場合
Thread::SizedQueue
も検討(enqueue をブロックする)- 上記のサンプルだとキューにたくさん溜まっていても
deq
で 1個だけ取ってクライアント側にすぐ戻していて、接続がもったいない- 複数個 dequeue してまとめて返す
- とはいえチャット程度なら心配する必要なさそう