Ruby server side Websocket client - ruby-on-rails

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

Related

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.

Rails Faye Websocket client just to send one message

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

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.

Websocket can't connect to ActionController::Live - failed with 200

class MessagesController < ApplicationController
include ActionController::Live
def events
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new response.stream
redis = Redis.new
redis.subscribe(redis_channel) do |on|
on.message do |event, data|
sse.write(data, event: 'messages.create')
end
end
#render nothing: true
rescue IOError
# disco bro!
ensure
redis.quit
sse.close
end
end
// also tried without the SSE.new, so just plain response.stream.write(data)
My Javascript is simple easy
var socket = new WebSocket("ws://localhost:3000/events")
socket.onmessage = function (event) {
console.log(event);
}
When i call /events in my browser and send some messages - its outputting. but if we connect the socket with JS we get
WebSocket connection to 'ws://localhost:3000/petra/events' failed:
Error during WebSocket handshake: Unexpected response code: 200
can anybody light me up? Are we missing something, or do i have a wrong understanding of what i'm trying to do.
WebSockets and Server Send Events are different protocols and can't inoperate in the manner you're trying.
The main js API for SSE is EventSource(url) (introductory article).
If you want bidirectional communication you would switch the server side to use WebSockets instead. For ruby on rails, websocket-rails is a popular library to do this.

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