-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Example on how to use the WebSockets promise? #4
Comments
NOTE:: updated with the latest websocket update Hi @dariocravero I will be open sourcing the application we built spider gazelle for soon. However here is the websocket management code: # Require the websocket abstraction
require 'spider-gazelle/upgrades/websocket'
module Orchestrator
class PersistenceController < ActionController::Metal
include ActionController::Rendering
# the promise resolution means this method will be running on the sockets event loop
# We have a custom class for managing the websocket requests
# The start method could have easily been called from within the websocket manager class too
def self.start(hijacked)
ws = ::SpiderGazelle::Websocket.new(hijacked.socket, hijacked.env)
WebsocketManager.new(ws)
ws.start
end
# Cache the callback method for the promise (avoids object creation each time)
START_WS = self.method(:start)
def websocket
hijack = request.env['rack.hijack']
if hijack
promise = hijack.call
promise.then START_WS
throw :async # to prevent rails from complaining
else
render nothing: true, status: :method_not_allowed
end
end
end
end in routes we have: get 'websocket', to: 'persistence#websocket', via: :all then the websocket manager: def initialize(ws, user = OpenStruct.new({id: 'anonymous'}))
@ws = ws
@user = user
@loop = @ws.loop
@bindings = ::ThreadSafe::Cache.new
@stattrak = @loop.observer
@notify_update = method(:notify_update)
@logger = ::Orchestrator::Logger.new(@loop, user)
@ws.progress method(:on_message)
@ws.finally method(:on_shutdown)
@ws.on_open method(:on_open)
end
def on_message(data, ws)
begin
raw_parameters = ::JSON.parse(data, DECODE_OPTIONS)
parameters = ::ActionController::Parameters.new(raw_parameters)
params = parameters.permit(PARAMS)
rescue => e
@logger.print_error(e, 'error parsing websocket request')
error_response(nil, ERRORS[:parse_error], e.message)
return
end
if check_requirements(params)
if security_check(params)
begin
cmd = params[:cmd].to_sym
if COMMANDS.include?(cmd)
self.__send__(cmd, params)
else
@logger.warn("websocket requested unknown command '#{params[:cmd]}'")
error_response(params[:id], ERRORS[:unknown_command], "unknown command: #{params[:cmd]}")
end
rescue => e
@logger.print_error(e, "websocket request failed: #{data}")
error_response(params[:id], ERRORS[:request_failed], e.message)
end
else
# log access attempt here (possible hacking attempt)
@logger.warn('security check failed for websocket request')
error_response(params[:id], ERRORS[:access_denied], 'the access attempt has been recorded')
end
else
# log user information here (possible probing attempt)
reason = 'required parameters were missing from the request'
@logger.warn(reason)
error_response(params[:id], ERRORS[:bad_request], reason)
end
end
def on_shutdown
@pingger.cancel if @pingger
@bindings.each_value &method(:do_unbind)
@bindings = nil
@debug.resolve(true) if @debug # detach debug listeners
end
protected
# Maintain the connection if ping frames are supported
def on_open(evt)
if @ws.ping('pong')
variation = 1 + rand(20000)
@pingger = @loop.scheduler.every(40000 + variation, method(:do_ping))
end
end
def do_ping(time1, time2)
@ws.ping('pong')
end
def error_response(id, code, message)
@ws.text(::JSON.generate({
id: id,
type: :error,
code: code,
msg: message
}))
end |
Pretty much the In the |
Also, it is probably worth noting, I've wrapped the faye websocket driver into a promise for spider-gazelle use. So non-promise methods such as Although I guess we could use something like ruby Forwardable to proxy commonly used methods. NOTE:: This has been implemented |
That's amazing @stakach! I took the gist of it and created a gem that abstracts this implementation for @padrino. Check it out here, I'll be releasing it soon but I'd like to know what you think about it. It includes the latest changes you did on hiding the internals and using Ruby's Forwardable instead. Padrino was lacking an easy and consistent WebSockets base and I guess this is a good step towards allowing different WS backends to be plugged in while using a common interface. It lets you do something like this:
It still has a few quirks that need to be fixed and the tests to be added. I first wanted to be sure that it was going down a good path. There's a simple Padrino's example app here. /cc @nesquena @DAddYE @skade @namusyaka @Ortuna @ujifgc Thoughts? |
Very nice abstraction! |
Yeah, otherwise it might clash with an HTTP route that you may have for another application... |
Hi @stakach,
I hope you're doing well and that the demo went out just fine :).
I'd like to ask you, would you have an example on how to use the WebSockets promise with a Rack application?
Thanks,
Darío
The text was updated successfully, but these errors were encountered: