Avoid server stuck on SSE (Server Sent Event) - ruby-on-rails

My server will get stuck, when users open SSE many times,
Because it seems Redis has some bugs with SSE.
The stream won't be closed even clients close browser or go to another page.
By the way I don't know when where the
logger.info "Stream closed"
logger.info "Client disconnected"
will be invoked ? (it doesn't be invoked when I close the browser)
Is it some workaround to avoid this issue ?
def new_prizes_stream
# http://ngauthier.com/2013/02/rails-4-sse-notify-listen.html
begin
response.headers.delete('Content-Length')
response.headers['Cache-Control'] = 'no-cache'
response.headers['Content-Type'] = 'text/event-stream'
logger.info "New stream starting, connecting to redis"
redis = Redis.new
redis.subscribe('messages.create', 'heartbeat') do |on|
on.message do |event, data|
if event == 'messages.create'
response.stream.write "event: #{event}\n"
response.stream.write "data: #{data}\n\n"
elsif event == 'heartbeat'
response.stream.write("event: heartbeat\ndata: heartbeat\n\n")
end
end
end
rescue IOError
logger.info "Stream closed"
rescue ActionController::Live::ClientDisconnected
logger.info "Client disconnected"
ensure
ap "close a live stream"
redis.quit
response.stream.close
end
end

My recommendation is that you do not create a connection on each request/SSE, and benchmark the results. Everytime you execute:
redis = Redis.new
If you can create the connection (singleton or factory patterns), instead of running this you would instead do something like:
redis = myPoolObj.getRedisConnection()
You then decide what you want to do on that Pool and how many connections you want to use. I checked the docs at redis-db but I did not see a built-in API for this like I saw when inspecting the python one.
You can open an issue on the repo asking if there's a built-in way to execute this without managing your own pool.
Did this help?

Related

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).

EventMachine::WebSocketClient.connect causes onclose to trigger on server side

There are three parts in my Rails application:
A part which listens for HTML5 web sockets (em-websocket)
a piece of JavaScript which connects to them
and a part which task is to connect to these sockets from inside of the same web application (em-websocket-client) (Yes, I am trying to do some IPC in Phusion Passenger environment)
JavaScript code connects fine and Web sockets server is happy with such a client, but when I connect from em-websocket-client a strange thing is happening: onclose handler is being called without calling onopen, and moreover - it is called for a socket which had been opened by web browser, not em-websocket-client.
The same em-websocket-client code, when executed in separate Ruby script through command line, works as planned. Here is the sample of em-websocket-client code:
require 'em-websocket-client'
class WebSocketsClient
def initialize
Thread.new do
log 'In a thread'
EventMachine.run do
log 'EM run'
#conn = EventMachine::WebSocketClient.connect("ws://localhost:5050?user_id=1&page_token=JYUTbfYDTTliglififi")
#conn.callback do
log 'Callback'
#conn.send_msg({ message_type: 'phone_call', user_id: 1, order_id: 1}.to_json)
#conn.close_connection
end
#conn.errback do |e|
log 'Errback'
puts "Got error: #{e}"
end
#conn.stream do |msg|
#log 'Stream'
#puts "<#{msg}>"
#if msg.data == 'done'
# #conn.close_connection
#end
end
#conn.disconnect do
puts 'gone'
EventMachine::stop_event_loop
end
end
end
end
def send_phone_call(order_id, user_id)
#conn.send_msg({ message_type: 'phone_call', user_id: user_id, order_id: order_id}.to_json)
end
def log(text)
puts "WebSocketsClient: #{text}\n"
end
end
WebSocketsClient.new
onclose on server side is being callsd as soon as EventMachine::WebSocketClient.connect is executed on client side. It doesn't even come to #conn.disconnect call.
One more thing I can conjecture is that this behaviour is due to using same EventMachine mechanism by server and client inside of same Rails application.

Automatically handle missing database connection in ActiveRecord?

With the launch of Amazon's Relational Database Service today and their 'enforced' maintenance windows I wondered if anyone has any solutions for handling a missing database connection in Rails.
Ideally I'd like to be able to automatically present a maintenance page to visitors if the database connection disappears (i.e. Amazon are doing their maintenance) - has anyone ever done anything like this?
Cheers
Arfon
You can do this with a Rack Middleware:
class RescueFromNoDB < Struct.new(:app)
def call(env)
app.call(env)
rescue Mysql::Error => e
if e.message =~ /Can't connect to/
[500, {"Content-Type" => "text/plain"}, ["Can't get to the DB server right now."]]
else
raise
end
end
end
Obviously you can customize the error message, and the e.message =~ /Can't connect to/ bit may just be paranoia, almost all other SQL errors should be caught inside ActionController::Dispatcher.

Resources