11
8

faye-websocket-rubyをWebSocketサーバーとして使う

Last updated at Posted at 2017-12-28

これは何か?

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.ruFaye::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と一緒です。

  1. https://github.com/rack/rack/blob/5c36acac4d298dcf04c58a10014c849a478da53f/lib/rack/server.rb#L242]

  2. https://github.com/faye/faye-websocket-ruby/blob/bbef55a2cd7fd52d145cdc73c0d162573b2a83a8/lib/faye/websocket.rb#L90

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8