WebSockets: How to notify all subscribers when a client connection has dropped? - ruby

I have a little WebSocket chat demo that I am working on (based on this code). However, the part that doesn't seem to be working is when a connection is closed between a client and the server, I want to notify all the subscribers that the user has "left the chatroom". I thought that the server would be notified/run the onclose function when the client connection was dropped, but maybe that's not how WebSockets work.
Here's my EventMachine code:
ws.onclose do
puts "Connection closed"
ws.send ({:type => 'status', :message => "#{#subscribers[subscriber_id]} has left the chatroom"}.to_json)
#main_channel.unsubscribe(subscriber_id)
end

You are trying to send data to a WebSocket that was just closed, that won't work. You probably want to just push a message to the Queue like:
ws.onclose do
puts "Connection closed"
msg = {:type => 'status', :message => "#{#subscribers[subscriber_id]} has left the chatroom"}.to_json
#main_channel.push(msg)
#main_channel.unsubscribe(subscriber_id)
end
That way the message will be send to all subscribers.
Best regards
Tobias

Related

API Gateway Websockets not sending to all clients

I'm using websockets with API Gateway and dynamoDB to maintain all connection IDs.
What I'm trying to do is when a new message comes in, a message is sent to all connected clients.
For example, inside the $default route a message will be received from the client. I'm then querying dynamo to get all connected ids and sending that message.
Querying dynamo:
const params = {
TableName: process.env.CONNECTIONS_TABLE,
IndexName: process.env.CONNECTIONS_COMPANY_INDEX,
KeyConditionExpression: "company = :company",
ExpressionAttributeValues: {
":company": data.company,
},
};
const response = await dynamodb.query(params).promise();
response.Items.forEach(async (item) => {
try {
await apig
.postToConnection({
ConnectionId: item.connectionId,
Data: JSON.stringify(data),
})
.promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(e)
}
}
});
The issue I'm having is that it's only received on the clients after the second attempt. So the client sends a message "123" (the above code is run successfully and I've verified it's getting all connections, no errors) but nothing is received by the client. The client sends another message - "456", now both clients will receive the "123" message.
Any ideas why this would happen? I'd expect that each message sent, would receive the same message to all connected clients, not delayed and always one behind.
Thank you!
i faced with exact same issue. Then i just Remove integration response from the API gateway and leave the function return {}
https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api-routes-integrations.html#apigateway-websocket-api-overview-integrations-differences
In the HTTP protocol, in which requests and responses are sent
synchronously; communication is essentially one-way. In the WebSocket
protocol, communication is two-way. Responses are asynchronous and are
not necessarily received by the client in the same order as the
client's messages were sent. In addition, the backend can send
messages to the client.

How to communicate with IoT device from webhook using rails

I am learning Google Home Integration with IoT device. I have IoT device which reponds only TCP/WS protocol to get/update things in the device. I am running webhook in rails application. Rails web service can get request from google assistant and respond back. Now to fullfill the request received from google assistant, I have to make TCP/WS request to the device. I am running client web socket program in the device to get request. server web socket program is in server side. my problem (same) is how to forward received request from google assistant to the device using web socket and get response from device and forward to google assistant.
Here is sample controller code.
class WebhookController < ApplicationController
def create
value = params[:result][:parameters][:value].first
action = params[:result][:parameters]['action'].first
/*
Here I have to access the following web socket connection and send the request and need to get response back and update to google assistant.
*/
res = { 'speech' => "temperature #{value} successfully changed.", 'displayText' => 'success' }
render json: res
end
end
Sample web socket server which is already connected with client which is running in the device.
require 'em-websocket'
EM.run {
EM::WebSocket.run(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen { |handshake|
puts "WebSocket connection open"
ws.send "Hello Client, you connected to #{handshake.path}"
}
ws.onclose { puts "Connection closed" }
ws.onmessage { |msg|
puts "Recieved message: #{msg}"
ws.send msg
/*
Here It should get msg from controller and forward to the client and get response from client and send to server controller.
*/
}
end
}

How to return from a Rails rack call?

I am using Faye websocket implementation. When a websocket is detected I want to make sure the user sent a given header before I open the connection, or else I want to return an error.
I tried returning error 401 using [] but I keep oberving that the code continues execution and the websocket is created anyway. I managed to prevent it by adding return after it, but I am not sure this is the right way. On the client side I am implementing it in python using ws4py and when I return I get an exception raised indicating it received a 302 code, not the 401 I was expecting to send.
My ruby code (without the websocket events code) follows:
App = lambda do |env|
if Faye::WebSocket.websocket?(env)
unless env['HTTP_MY_HEADER']
[401, {'Content-Type' => 'application/json'}, '{"message": "I was expecting a header here"}']
#return (??)
end
ws = Faye::WebSocket.new(env)
# Rest of websocket call handling
else
# Normal HTTP request
[200, {'Content-Type' => 'text/plain'}, ['Hello']]
end
end

em-websocket and javascript client connection

I've got a simple eventmachine web socket server (eventmachine 1.0.0):
EM.run {
# WebSocket Server
EM::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen do
sid = #channel.subscribe{|msg| ws.send msg }
puts "* new WebSocket client <#{sid}> connected!"
end
ws.onmessage do |msg|
puts "* websocket client <#{#sid}> : #{msg}"
end
ws.onclose do
#channel.unsubscribe(sid)
puts "* websocket client <#{#sid}> closed"
end
end
}
I'm trying to connect to it through a javascript client with the following code:
socket = new WebSocket("ws://localhost:8080");
socket.onopen = function(e) {
socket.send('Connesso');
};
socket.onmessage = function(mess) {
if (mess) {
socket.send(mess);
}
};
socket.onclose = function(e) {
socket.send('Disconnesso');
};
With previous versions of safari it was working flawlessly with the latest one the client is not connecting to the server.
I tried it also with last Chrome Dev stable version but it's not working.
The web socket header is sent but it remains in pending status.
If I send a text message to the web socket I receive the INVALID_STATE_ERR: DOM Exception 11.
I saw that there has been a draft change but I thought em-websocket 0.3.8 already implemented it.
Can you help me solve this issue?
Thanks a lot
INVALID_STATE_ERR: DOM Exception 11 means your websocket is not in ready state yet.
you can check state of websocket object by socket.readyState
you are able to send messages when socket.readyState == 1
I created a turnaround for this by using timeout
timerId = setInterval(sendDataWhenReady, 1000);
function sendDataWhenReady(){
if(socket.readyState == 1){
ws.send(JSON.stringify({"type": 'STATUS', "status": status, "username": logged_in_user}))
clearInterval(timerId);
}
}

socket.io and eventmachine in ruby

I am trying out a very basic server/client demo. I am using socket.io on the client(a user in a browser) and eventmachine Echo example for server. Ideally socket.io should send a request to server and server will print the received data. Unfortunately, something is not working as I expect it to.
Source is pasted here:
socket = new io.Socket('localhost',{
port: 8080
});
socket.connect();
$(function(){
var textBox = $('.chat');
textBox.parent().submit(function(){
if(textBox.val() != "") {
//send message to chat server
socket.send(textBox.val());
textBox.val('');
return false;
}
});
socket.on('message', function(data){
console.log(data);
$('#text').append(data);
});
});
and here is ruby code:
require 'rubygems'
require 'eventmachine'
require 'evma_httpserver'
class Echo < EM::Connection
def receive_data(data)
send_data(data)
end
end
EM.run do
EM.start_server '0.0.0.0', 8080, Echo
end
You client code is trying to connect to a server using the websockets protocol. However, your server code isn't accepting websockets connections - it's only doing HTTP.
One option is to use the event machine websockets plugin:
https://github.com/igrigorik/em-websocket
EventMachine.run {
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen {
puts "WebSocket connection open"
# publish message to the client
ws.send "Hello Client"
}
ws.onclose { puts "Connection closed" }
ws.onmessage { |msg|
puts "Recieved message: #{msg}"
ws.send "Pong: #{msg}"
}
end
}
I'd look into using Cramp. It's an async framework with websockets support, built on top of EventMachine. I've played around with the samples and I have to admit that the API looks elegant and clean.
I would look into Plezi.
Your server side echo code could look something like this:
require 'plezi'
class EchoCtrl
def index
redirect_to 'http://www.websocket.org/echo.html'
end
def on_message data
# to broadcast the data add:
# broadcast :_send_message, data
_send_message data
end
def _send_message data
response << data
end
end
listen
# you can add, a socket.io route for JSON with socket.io
route '/socket.io', EchoCtrl
route '/', EchoCtrl
just type it in IRB and the echo server will start running once you exit IRB using the exit command.
Plezi is really fun to work with and support Websockets, HTTP Streaming and RESTful HTTP requests, so it's easy to fall back on long-pulling and serve static content as well as real-time updates.
Plezi also has built in support for Redis, so it's possible to push data across processes and machines.

Resources