これは何か?
faye-websocket-rubyを使ってHello Worldします。
READMEと同レベルの情報しか書きません。
faye-websocket-rubyとは?
FayeというPubSubライブラリのRuby実装です。実質的にはRubyでWebSocketを扱う際に使われるライブラリです。Rails5のActionCableにも使われているとか、使われていないとか。Fayeの発音はフェイのようです。
Server-sent eventsにも対応しており、サーバからブラウザへの一方向のpush通知にも使えます。今回は扱いません。
準備
前提条件として、Ruby 3.3.4 がインストールされているものとします。
Gemfileを作る
bundle init
Gemfileというファイルができます。
faye-websocket
をインストールします。
bundle add faye-websocket
を実行すると、指定したライブラリと、そのライブラリが使うライブラリをまとめてインストールします。
使い方
app.rb の作成
READMEに記載のapp.rb
を使います。
require 'faye/websocket'
App = lambda do |env|
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env)
ws.on :message do |event|
ws.send(event.data)
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
# Return async Rack response
ws.rack_response
else
# Normal HTTP request
[200, {'Content-Type' => 'text/plain'}, ['Hello']]
end
end
app.rbの説明
Rackへの対応
faye-websocket-rubyは、Rack仕様に対応しています。
Rackのenv
変数を使って、WebSocketコネクションを作ることができます。
Faye::WebSocket.websocket?(env)
でWebSocketのリクエストであるか判定できます。
Faye::WebSocket.new(env)
でWebSocketのコネクションインスタンスを取得できます。
WebSocketのイベントを扱う
WebSocketのイベントハンドラーは
ws.on :message do |event|
ws.send(event.data)
end
こんな素直なスタイルで書けます。
スレッドベースのWebアプリケーションサーバを使う
faye-websocket-rubyは、非同期に結果を返すために内部でEventMachineを使っています。EventMachineは、IO以外の非同期な処理を非同期に実行するためにスレッドを使います。
Rack仕様に対応した、スレッドベースのWebアプリケーションサーバが使えます。
代表的なものに
があります。WEBrickでは動きません。
Thinでfaye-websocketを動かす
Gem のインストール
bundle add thin
config.ru の作成
require 'thin'
require_relative './app'
Faye::WebSocket.load_adapter('thin')
run App
注意点
config.ru
にFaye::WebSocket.load_adapter('thin')
を書かないと、ブラウザからWebSocketで接続する際に、次のエラーが出ます。
VM293:1 WebSocket connection to 'ws://localhost:9292/' failed: Error during WebSocket handshake: 'Upgrade' header is missing
なぜ、必要なのかは知りません。
起動
thin start -R config.ru -p 9292
動作確認(ブラウザの開発コンソールから)
ブラウザで http://localhost:9292 を開くとHello
が表示されます。
開発コンソールを開いて
const ws = new WebSocket('ws://localhost:9292')
ws.onmessage = console.log
ws.send('hello')
を実行すると、コンソールにMessageEvent
が表示されます。
Pumaでfaye-websocketを動かす
Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
gem "rack"
gem "puma"
gem "faye-websocket"
config.ru
require 'puma'
require './app'
run App
起動
puma config.ru -p 9292
rackupコマンドを使う場合
rackup -s puma -E deployment
-E deployment
が重要です。
これをつけずにrackupコマンドを実行すると、Rack::Lintモジュールがマウントされます。1
ws.rack_response
は固定で、ダミーのトリプル[-1, {}, []]
を返します。2
これがRack::Lintに引っかかり、次のエラーが出ます。
#<Thread:0x00007fa46801ad10@/usr/local/lib/ruby/gems/2.5.0/gems/faye-websocket-0.10.7/lib/faye/websocket.rb:40 run> terminated with exception (report_on_exception is true):
Traceback (most recent call last):
10: from /usr/local/lib/ruby/gems/2.5.0/gems/faye-websocket-0.10.7/lib/faye/websocket.rb:40:in `block in ensure_reactor_running'
9: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:194:in `run'
8: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:194:in `run_machine'
7: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:974:in `run_deferred_callbacks'
6: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:974:in `times'
5: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:977:in `block in run_deferred_callbacks'
4: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:237:in `block in schedule'
3: from /usr/local/lib/ruby/gems/2.5.0/gems/faye-websocket-0.10.7/lib/faye/rack_stream.rb:39:in `block in hijack_rack_socket'
2: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:741:in `attach'
1: from /usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:763:in `attach_io'
/usr/local/lib/ruby/gems/2.5.0/gems/eventmachine-1.2.5/lib/eventmachine.rb:763:in `attach_fd': no implicit conversion of Rack::Lint::HijackWrapper into Integer (TypeError)
Rack::Lint::LintError: Status must be >=100 seen as integer
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/lint.rb:20:in `assert'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/lint.rb:620:in `check_status'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/lint.rb:51:in `_call'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/lint.rb:37:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/show_exceptions.rb:23:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/common_logger.rb:33:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/chunked.rb:54:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/rack-2.0.4/lib/rack/content_length.rb:15:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/puma-3.11.0/lib/puma/configuration.rb:225:in `call'
/usr/local/lib/ruby/gems/2.5.0/gems/puma-3.11.0/lib/puma/server.rb:624:in `handle_request'
/usr/local/lib/ruby/gems/2.5.0/gems/puma-3.11.0/lib/puma/server.rb:438:in `process_client'
/usr/local/lib/ruby/gems/2.5.0/gems/puma-3.11.0/lib/puma/server.rb:302:in `block in run'
/usr/local/lib/ruby/gems/2.5.0/gems/puma-3.11.0/lib/puma/thread_pool.rb:120:in `block in spawn_thread'
::1 - - [13/Feb/2018:18:08:58 +0900] "GET / HTTP/1.1" 500 1195 0.0109
動作確認
Thinと一緒です。