How to Send a Keep-Alive Packet Through Websocket in Ruby on Rails

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

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.

Keeping the WebSocket connection alive

The connection is closed each time after handle. You should rather stay there reading incoming data:

# incoming connection
def setup(self):
print "connection established", self.client_address

def handle(self):
while 1:
try:
self.data = self.request.recv(1024).strip()

# incoming message
self.headers = self.headsToDict(self.data.split("\r\n"))

# its a handshake
if "Upgrade" in self.headers and self.headers["Upgrade"] == "websocket":
key = self.headers["Sec-WebSocket-Key"]
accept = b64encode(sha1(key + MAGIC).hexdigest().decode('hex'))
response = "HTTP/1.1 101 Switching Protocols\r\n"
response += "Upgrade: websocket\r\n"
response += "Connection: Upgrade\r\n"
response += "Sec-WebSocket-Accept: "+accept+"\r\n\r\n"
print response
self.request.send(response)
# its a normal message, echo it back
else:
print self.data
self.request.send(self.data)
except:
print "except"
break

how can I inject a keep-alive into a websocket handler in akka-http?

The only way to control how connections are dropped server-side is to change the idle-timeout (see the docs here and here).

You can set

idle-timeout = infinite

to completely disable the timeout.

If you want to use keep alive messages, they need to be sent from the client side. The .keepAlive combinator is the easiest way to get them if your client is based on Akka Streams.

Passing progress messages from server to frontend in Rails 4

Your question was relatively vague, so apologies if my answer is equally:


WebSockets Or SSE (Server Sent Events)

We just implemented something similar to this with an app which deletes multiple records, and sends updates on which have been deleted

You're right with pub/sub, and I'll attempt to explain the underlying tech. It's actually a similar process for SSE or WebSockets


Receiving "Live" Data

The process of receiving "live" data from the server is to use the ActionController::Live controller inside Rails. This works with the response.headers['Content-Type'] = 'text/event-stream' HTTP mime-type to send small packets of data to a Javascript "listener" (which you initialize on your front-end)

Whether you use SSE or WebSockets, you have to have a "live" controller action (which creates the live-information), and also have a Javascript EventListener in place to capture the real-time data in the browser

The way you do this changes with each technology, but it's actually a similar process for both SSE & WebSockets


SSE's

Server Sent Events is an HTML5 technology which basically allows you to receive (not send) updated data by opening an asynchronous connection to an endpoint (your Rails ActionController::Live action) on your server, which will publish the data you need

You'd typically set up this using Redis (to compile the data into JSON) and send it using Rails' ActionController::Live functionality. Your Javascript will "listen" for the event being triggered, thus allowing you to capture the real-time data sent from the server & manipulate it on the front end

The problem with SSE's is that it uses long-polling to continually "ping" the endpoint in an attempt to get the latest information. Although Puma allows for multi-threading, we were unable to get it to run the connection concurrently, degrading performance significantly (basically, sending a request every second eats up your connections)


WebSockets

Websockets are a similar technology to SSE's, but with a major difference -- they allow a perpetual connection:

WebSocket is a protocol providing full-duplex communications channels
over a single TCP connection

This means that you can send and receive data, as well as only connecting once & maintaining the connectivity, regardless of what you do interim. We found this to be a far more stable solution than SSE's, as it removes the requirement for long-polling, and, if you can keep authentication correct, gives you the ability to receive data correctly

All the big-hitters use WebSockets (including StackOverflow), and it's actually relatively simple to set up:


"Real Time" Setup

You need:

  1. Rails ActionController::Live Function & Endpoint (route)
  2. Third-Party Websocket Integration (Pusher)
  3. Javascript EventListeners (to handle returned data)

Here's some live code we use:

#app/controllers/resources_controller.rb (inherited resources)
def destroy
element = @resource.find(params[:id])
element.destroy
Pusher['private-user-' + current_user.id.to_s].trigger('my_event', {
message: "Removed #{element.title}"
})
end

#app/assets/javascripts/users.js.coffee.erb
pusher = new Pusher("******************",
cluster: 'eu'
)

channel = pusher.subscribe("private-user-#{gon.user_id}")
channel.bind "my_event", (data) ->
alert data.message

This opens a persisting connection to the pusher app, which then handles our data. Whenever pusher receives our data, it sends the update to us, allowing us to alert the user to the message

I know it's a simplistic overview, but I hope it helps

How to use ActionCable as API

While mwalsher's solution was extremely helpful to me, I recently found a pull request on the Rails repository with an official solution to my question.

https://github.com/rails/rails/pull/24991

I assume, in the near future this will be added to the main documentation. Here is the link to the official actioncable npm package: https://www.npmjs.com/package/actioncable

You can use it similar to mwalsher's solution with any JS app. Just install the npm package:

npm install actioncable --save

Here the JS example from the documentation:

ActionCable = require('actioncable')

var cable = ActionCable.createConsumer('wss://RAILS-API-PATH.com/cable')

cable.subscriptions.create('AppearanceChannel', {
// normal channel code goes here...
});

Edit: The pull request has been merged for a while, now and the description is part of the official Readme - just not yet in the Rails Guides.

Websocket client (browser) receives a zero-length message - what is it?

I think this is just a Safari annoyance. The current released versions of Safari implement the older WebSockets protocol version and the implementation works but it is a bit lacking. The 0 length messages issue is pretty tame. A more serious issue is the inability to properly close a connection (basically doesn't close the actual socket until the page is reloaded).

Try the same test with a recent Chrome. If you don't see the problem there then it's just a Safari issue. If you still see 0 length messages, then it is likely that the PHP websocket server is sending 0 length messages (perhaps as a sort of poor man keep-alive).



Related Topics



Leave a reply



Submit