uninitialized constant Rack::WebSocket::Handler::Thin::WebSocketError
xaviershay opened this issue · 5 comments
Hello,
I'm trying to drive websocket push with a sinatra app and getting an unintialized constant error. The following script can replicate the problem.
# config.ru
require 'rack/websocket'
require 'sinatra/base'
class Pusher < Rack::WebSocket::Application
def on_open(env)
send_data "OPEN"
end
def push
send_data "hello"
end
def push2
EM.next_tick {
send_data "hello"
}
end
end
class WebApp < Sinatra::Base
def initialize(pusher)
super()
@pusher = pusher
end
get '/' do
<<-HTML
<html>
<head>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js'></script>
<script>
$(function() {
var socket = new WebSocket('ws://localhost:3000/ws');
socket.onmessage = function(msg) {
alert(msg.data);
}
});
</script>
</head>
<body>
</body>
</html>
HTML
end
get '/push' do
@pusher.push
end
get '/push2' do
@pusher.push
end
end
pusher = Pusher.new
map '/ws' do
run pusher
end
map '/' do
run WebApp.new(pusher)
end
thin -R config.ru start &
curl http://localhost:3000/push # Error
curl http://localhost:3000/push2 # Error
The exception raised in both cases is:
NameError - uninitialized constant Rack::WebSocket::Handler::Thin::WebSocketError:
websocket-rack-0.3.0/lib/rack/websocket/handler/thin.rb:22:in `send_data'
websocket-rack-0.3.0/lib/rack/websocket/application.rb:36:in `method_missing'
config.ru:11:in `push'
Is this the correct way to be architecting things? (Regardless, at the very least a better error should be thrown)
I will fix it later today(it should throw Rack::WebSocket::WebSocketError - will do in 0.3.1 ;)
About implementation - it should be implemented a little different:
# config.ru
require 'rack/websocket'
require 'sinatra/base'
class Pusher < Rack::WebSocket::Application
def self.connections
@connection ||= []
end
def on_open(env)
self.class.connections << self
send_data "OPEN"
end
def on_close(env)
self.class.connections.delete(self)
end
def push
send_data "hello"
end
def push2
EM.next_tick {
send_data "hello"
}
end
end
class WebApp < Sinatra::Base
def initialize
super()
end
get '/' do
<<-HTML
<html>
<head>
<script src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js'></script>
<script>
$(function() {
var socket = new WebSocket('ws://localhost:3000/ws');
socket.onmessage = function(msg) {
alert(msg.data);
}
});
</script>
</head>
<body>
</body>
</html>
HTML
end
get '/push' do
Pusher.connections.each { |connection| connection.push }
end
get '/push2' do
Pusher.connections.each { |connection| connection.push2 }
end
end
map '/ws' do
run Pusher.new # This should be initialized everytime request is called
end
map '/' do
run WebApp.new
end
First of all - there is no direct connection between HTTP connection and WebSocket connection. You can identify them by providing some data(i.e. connecting to 'ws://localhost:3000/ws?uid=abc') and then sending message only to connection with specified uid. So you can't pass pusher instance to HTTP request.
Another one is that for each WS connection you should create new instance of Pusher - each of them will support only one request.
ok that makes sense and I have it working now. Thanks!
I would love to see an implementation on this lib that looks something like:
class Pusher < Rack::WebSocket::Application
def on_open(env, socket)
subscription = Subscription.new(:channel => env['PATH_INFO'])
subscription.on_message { |m| socket.send_data m }
socket.send_data 'Subscribing'
subscription.subscribe
socket.on_close {
socket.send 'Unsubscribing and closing connection'
subscription.delete
}
end
end
This way an array or hash of connections doesn't need to be maintained.
the correct one should look like that:
class Pusher < Rack::WebSocket::Application
def on_open(env, socket)
@subscription = Subscription.new(:channel => env['PATH_INFO'])
@subscription.on_message { |m| socket.send_data m }
socket.send_data 'Subscribing'
@subscription.subscribe
end
def on_close(env, socket)
socket.send 'Unsubscribing and closing connection'
@subscription.delete
end
end
The only thing that will remain for you is to script Subscription model :) It could be good idea to script also Pusher#on_message to pass it to Subscription instance.
We ended up with something similar to that at https://github.com/polleverywhere/push/blob/backends/lib/push/transport/web_socket.rb, thanks for validating that approach! Also, thanks for integrating em-websockets into Rack.