Rails Faye Websocket client just to send one message - ruby-on-rails

I'm trying to broadcast from my controller a message to all registered clients.
The better way I found to do this is to create a Faye client in my controller, send my message, and close it right after message is sent.
#my_controller.rb
EM.run {
ws = Faye::WebSocket::Client.new(Rails.application.config.websocket_url)
ws.send(JSON.dump(this_order.service_json))
EM.stop
}
Although this code partially works, it closes all my browser connections to Faye.
Faye is implemented in a middleware, like this:
class FayeMiddleware
KEEPALIVE_TIME = 30 # in seconds
def initialize(app)
#app = app
#clients = []
end
def call(env)
if Faye::WebSocket.websocket?(env)
# WebSockets logic goes here
ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME })
ws.on :open do |event|
p [:open, ws.object_id]
#clients << ws
end
ws.on :close do |event|
p [:close, ws.object_id, event.code, event.reason]
#clients.delete(ws)
ws = nil
end
ws.on :message do |event|
p [:message, event.data]
puts event.data
#clients.each {|client| client.send(event.data) }
end
# Return async Rack response
ws.rack_response
else
#app.call(env)
end
end
end
I made this code referring to this tutorial.
I don't understand why all my web sockets get closed when I stop my EM. Can anyone help me ?

I found a solution:
EM.run {
ws = Faye::WebSocket::Client.new(Rails.application.config.websocket_url)
ws.on :open do |event|
ws.send(JSON.dump(this_order.service_json))
ws.close
end
}
This waits for the socket to open, then send the message and closes. No need to stop the EventMachine i guess.

Try this may be it will help you ..
How can I push to Faye Server from Rails Controller?

Using the WebSocket client
The client supports both the plain-text ws protocol and the encrypted wss protocol, and has exactly the same interface as a socket you would use in a web browser. On the wire it identifies itself as hybi-13.
require 'faye/websocket'
require 'eventmachine'
EM.run {
ws = Faye::WebSocket::Client.new('ws://www.example.com/')
ws.on :open do |event|
p [:open]
ws.send('Hello, world!')
end
ws.on :message do |event|
p [:message, event.data]
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
}
The WebSocket client also lets you inspect the status and headers of the handshake response via its status and headers methods.
To connect via a proxy, set the proxy option to the HTTP origin of the proxy, including any authorization information and custom headers you require:
ws = Faye::WebSocket::Client.new('ws://www.example.com/', [], {:proxy => {
:origin => 'http://username:password#proxy.example.com',
:headers => {'User-Agent' => 'ruby'}
}
})
for more help use this link

Related

Ruby server side Websocket client

Lately i have been experimenting with ruby and websockets. So i created a new Rails 5 project with ActionCable, all seems to work fine with it.
But also i created a ruby plain script with the Faye's ruby websocket client. Unlike most tutorials on internet i want to try a server side (as a client) script, not a frontend JS script inside an HTML file.
So i tried the basic usage of it and i successfully make the handshake to perform correctly but i can't continue testing because i cant figure where to subscribe after connected to a desired channel exposed in the Rails server.
Here is my ruby script code:
require 'faye/websocket'
require 'eventmachine'
EM.run {
ws = Faye::WebSocket::Client.new('ws://localhost:3001/cable',nil,{
headers: {'Origin' => 'ws://localhost:3001/cable'}
})
ws.on :open do |event|
p [:open]
ws.send({data: 'hello'})
end
ws.on :message do |event|
p [:message, event.data]
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
ws.on :error do |event|
p [:close, event.code, event.reason]
ws = nil
end
ws.send({data: 'yoyoyooy'}) # This gets sent to nowhere..
# I was hoping into subscribing a channel and callbacks for that channel, something like:
# ws.subscribe('my-channel',receive_message_callback,error_callback)
}
On the actioncable side my connection class does trigger the connect method, but i am still unsure how to interact with a channel. Like subscribing from the client so i can start sending and receiving messages.
By reading the readme of websocket action cable client gem i realized that any websocket would do the job, i just needed to send the proper payload in the send method according to what ActionCable needs. In this case:
ws.send {"command":"subscribe","identifier":"{\"channel\":\"#{channel}\",\"some_id\":\"1\"}"}.to_json
Off-topic: I couldn't find this in the rails overview page I just needed to search over the ActionCable Protocol

Websocket Server On Rails - Client Only

I want to listen to a websocket stream using the faye-websocket-ruby gem. But all I can find is standalone example scripts.
How can I integrate the following script in my current Rails application ?
require 'faye/websocket'
require 'eventmachine'
EM.run {
ws = Faye::WebSocket::Client.new('ws://www.example.com/')
ws.on :open do |event|
p [:open]
ws.send('Hello, world!')
end
ws.on :message do |event|
p [:message, event.data]
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
}
Thanks !
EDIT : A basic use case would be to receive data from Binance Websocket Streams and store in a database every trade received.

Class variable in new thread not in scope

I am having an issue with the following and that might be straightforward to someone with more knowledge or experience. Having spent the whole weekend trying to figure it out without success...extra help and insight would be very appreciated, thank you.
I have a class variable, ##clients that keeps track of opened websocket connections. When I access that variable from within the on.message block from the redis_sub.subscribe block (in a new Thread) the array is empty. I made a test class variable ##test that is incremented everytime a new websocket connection happens and log that variable, same, the log shows ##test to be 0, its initial value.
class Shrimp
# Heroku has a 50 seconds idle connection time limit.
KEEPALIVE_TIME = 15 # in seconds
##clients = []
##test = 0
class << self
def load_session(client)
if !client.env["rack.session"].loaded?
client.env["rack.session"][:init] = true
end
end
def get_client(user_id)
# check if user is one of the ws clients
##clients.each do |client|
# lazily load session
load_session(client)
# get user_id from the session
if(client.env["rack.session"]["warden.user.user.key"][0][0] == user_id)
return client
end
end
nil
end
end
def initialize(app)
#app = app
redis_uri = URI.parse(ENV["REDISCLOUD_URL"])
# work on a separte thread not to block current thread
Thread.new do
redis_sub = Redis.new(host: redis_uri.host, port: redis_uri.port, password: redis_uri.password)
redis_sub.subscribe(ENV["REDIS_CHANNEL"]) do |on| # thread blocking operation
p [:redis_suscribe]
p [:test, ##test]
on.message do |channel, msg|
data = JSON.parse(msg)
p [:redis_receive_message, data]
p [:test, ##test]
client = Shrimp.get_client(data["user_id"])
client.send(data["thumbnail_urls"].to_json) if client
end
end
end
end
def call(env)
env['rack.shrimp'] = self
if Faye::WebSocket.websocket?(env)
puts websocket_string
# Send every KEEPALIVE_TIME sec a ping for keeping the connection open.
ws = Faye::WebSocket.new(env, nil, { ping: KEEPALIVE_TIME })
ws.on :open do |event|
puts '***** WS OPEN *****'
##clients << ws
##test = ##test + 1
p [:test, ##test]
end
ws.on :message do |event|
puts '***** WS INCOMING MESSAGE *****'
p [:message, event.data]
p [:test, ##test]
##clients.each { |client| client.send(event.data.to_json) }
end
ws.on :close do |event|
puts '***** WS CLOSE *****'
p [:close, ws.object_id, event.code, event.reason]
##clients.delete(ws)
p [:test, ##test]
ws = nil
end
ws.on :error do |event|
puts '***** WS ERROR *****'
p [:close, ws.object_id, event.code, event.reason]
end
# Return async Rack response
ws.rack_response
else
#app.call(env)
end
end
end
terminal output:
we can see the ##testclass variable being 1 after a first connection opens, still 1 when the server gets a message from that one client, 0 when logged from within the on.message block and 1 again when that websocket connection is closed.
I am obviously missing something but cannot figure it out despite all the research and readings.

Faye and Ruby: have Rspec read Faye log or messages

How can I get Rspec or even Ruby to read a Faye message? They come through in Faye's log alright, but I can't seem to connect to Faye through Rspec:
it 'gets Faye message' do
EM.run do
client = Faye::Client.new('http://localhost:9292/faye')
sub = client.subscribe('/documents') do |message|
puts message
end
sub.callback do |message|
puts message
end
end
end
This just hangs. The messages come through in Faye's log. What am I doing wrong?
http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine.run
(Read the NOTE block)
I'd say the EM.run call blocks (never returns and waits for connections) and that's why your test hangs.
Not really seeing what your test is trying to do though, so I can't give you a pointer on how to improve this.
So I've solved my own problem. I'll post my solution here in the event that it helps someone else.
it 'generates a document process and gets a push response from faye' do
EM.run do
client = Faye::Client.new('http://localhost:9292/faye')
Thread.new { subject.perform(:generate, id) }
client.subscribe "/documents/#{id}/notify" do |response|
publication = client.publish("/documents/#{id}/notify", '0')
publication.callback do
if response != '0'
expect(response).to eq(id.to_s)
EM.stop
else
p "FAYE RESPONSE: #{response}" # diagnostic only
end
end
publication.errback { |error| p "FAYE RESPONSE: #{error.inspect}" }
end
end
end
My end game was simply to get Rspec to get the Faye messages sent from the subject.perform... process. Mission accomplished. Not the neatest thing in the world, but who cares.

How to send a keep-alive packet through websocket in ruby on rails

I want to send a
"Keep alive from client"
message every 30 seconds for my websocket connection. Here's what the code that I have in my websocket initializer looks like:
ws = WebSocket::Client::Simple.connect 'wss://bitcoin.toshi.io/'
ws.on :message do |msg|
rawJson = msg.data
message_response = JSON.parse(rawJson)
end
ws.on :open do
ws.send "{\"subscribe\":\"blocks\"}"
end
ws.on :close do |e|
puts "WEBSOCKET HAS CLOSED #{e}"
exit 1
end
ws.on :error do |e|
puts "WEBSOCKET ERROR #{e}"
end
Without any sort of 'keep alive', the connect closes in about 45 seconds. How should I send the 'heart-beat' packet? It seems that the connection is closed by their server, not mine.
You can use Websocket Eventmachine Client gem to send hearbeat:
require 'websocket-eventmachine-client'
EM.run do
ws = WebSocket::EventMachine::Client.connect(:uri => 'wss://bitcoin.toshi.io/')
puts ws.comm_inactivity_timeout
ws.onopen do
puts "Connected"
end
ws.onmessage do |msg, type|
puts "Received message: #{msg}"
end
ws.onclose do |code, reason|
puts "Disconnected with status code: #{code}"
end
EventMachine.add_periodic_timer(15) do
ws.send "{}"
end
end
You can setup timer for EventMachine with EM::add_periodic_timer(interval_in_seconds), and then send your heartbeat with it.
You can use the auto-ping feature (its default and can't be turned off) if you're using Iodine's Websocket client:
require 'iodine/http'
# prevents the Iodine's server from running
Iodine.protocol = :timer
# starts Iodine while the script is still running
Iodine.force_start!
# set pinging to a 40 seconds interval.
Iodine::Http::Websockets.default_timeout = 40
settings = {}
# set's the #on_open event callback.
settings[:on_open] = Proc.new do
write 'sending this connection string.'
end
# set's the #on_message(data) event callback.
settings[:on_message] = Proc.new { |data| puts "Received message: #{data}" }
# connects to the websocket
Iodine::Http.ws_connect 'ws://localhost:8080', settings
It's a fairly basic client, but also easy to manage.
EDIT
Iodine also includes some cookie and custom header's support, as now seen in Iodine's documentation. So it's possible to use different authentication techniques (authentication headers or cookies).

Resources