broadcasting data with event machine websocket - ruby

I have written a program for reading data from an html file to ruby program using websocket. I am including the code below:
EventMachine::WebSocket.run(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen { |handshake|
puts "WebSocket connection open #{ws}"
# Access properties on the EM::WebSocket::Handshake object, e.g.
# path, query_string, origin, headers
# Publish message to the client
# ws.send "Hello Client, you connected to #{handshake.path}"
}
ws.onclose { puts "Connection closed" }
ws.onmessage { |msg|
puts "Recieved message: #{msg}"
ws.send "#{msg}"
}
end
This is working properly.It recieves whatever data send from my html page. Now, what I need is to keep track of the connections to this server and send the recieved message to all the available connections.
The 'send' function here used can send only to a specified connection.

You asking for a basic chat server?
Just store the connections in a list (or hash).
People tend to include in a hash to make it easier to remove them
If this is in a class, use #connections instead of $connections
GL
$connections = {}
EventMachine::WebSocket.run(:host => "0.0.0.0", :port => 8080) do |ws|
ws.onopen
$connections[ws] = true
end
ws.onclose do
$connections.delete(ws)
end
ws.onmessage do |msg|
$connections.each { |c, b| c.send msg }
end
end

You could use EventMachine in combination with Faye and Faye/Websocket
You will need faye/websocket for your browser-based clients or just faye for your ruby clients.
The idea is that with Faye you create a channel and then you subscribe your clients to that channel. Then you push from your server the data you want to this channel and the clients subscribed will receive this data.

Related

How to broadcast or make connection in em-websocket?

I am using the em-websocket to make communication for clients (may 2 or more users).
In their introduction . https://github.com/igrigorik/em-websocket
I want to modify their simple echo server example to achieve my purpose.
but in their example , the handshake.path output always show "/" .
I cannot know where the client from .
Is there have any solution that can know the client source place and make a broadcast message to all of them ?
I found the answer in their example.
https://github.com/igrigorik/em-websocket/blob/master/examples/multicast.rb
EventMachine::WebSocket.start(:host => "0.0.0.0", :port => 8080, :debug => true) do |ws|
ws.onopen {
sid = #channel.subscribe { |msg| ws.send msg }
#channel.push "#{sid} connected!"
ws.onmessage { |msg|
#channel.push "<#{sid}>: #{msg}"
}
ws.onclose {
#channel.unsubscribe(sid)
}
}
end
But i still have problem that: How can I send message to specified clients?
(e.g.) two clients (No.1 and No.2) make their own communication.

Simple POST request failing with em-http-request

The following query works with requestmaker:
URI:
http://www.cleverbot.com/webservicemin/
Query:
start=y&icognoid=wsf&fno=0&sub=Say&islearning=1&cleanslate=false&stimulus=!!!%20there%20was%20an%20error%20!!!&icognocheck=af71393ce00d9126a247df2f53948e79
But it does not work with em-http-request:
require 'eventmachine'
require 'em-http-request'
uri = 'http://www.cleverbot.com/webservicemin/'
query = 'start=y&icognoid=wsf&fno=0&sub=Say&islearning=1&cleanslate=false&stimulus=!!!%20there%20was%20an%20error%20!!!&icognocheck=af71393ce00d9126a247df2f53948e79'
EM.run do
http = EM::HttpRequest.new(uri).post(query: query)
http.callback { puts http.response; EM.stop }
http.errback { puts 'There was an error'; EM.stop }
end
which prints There was an error. I feel stumped because this simple example works with any other method of sending a request and I've checked around to see if my usage was wrong but it doesn't seem to be.
Edit: Just for reference, this is not the correct way to use cleverbot. I made a second mistake by sending the data under :query. If you use http.post(body: query) it will work
Looks like a badly implemented server: it aborts the TCP connection without returning a proper HTTP status code, which is why you see "connection closed by server" when you query http.error.
If you change the default user agent to curl's UA string, you get a response:
http = EM::HttpRequest.new(uri).post({
:query => query,
:head => {'User-Agent' => 'curl/7.30.0'}
})

Accessing an EventMachine channel from a Sinatra route

I have a simple Sinatra App running on EventMachine, like this example.
The app is working, now I'd like to allow the routes I'm defining in Sinatra to access the websocket using the EventMachine channel that is created. I naively tried the following, but of course within the Sinatra App, the #channel variable isn't defined, so this doesn't work.
require 'em-websocket'
require 'sinatra'
EventMachine.run do
#channel = EM::Channel.new
class App < Sinatra::Base
get '/' do
erb :index
end
post '/test' do
#channel.push "Post request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = #channel.subscribe { |msg| socket.send msg }
#channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
#channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
#channel.unsubscribe(sid)
end
end
end
App.run! :port => 3000
end
How could I access the EventMachine channel I've got open within my Sinatra app?
In case others don't know what we're talking about in the comments, here's an example of using a class instance variable in the way I suggested. This runs, but I don't know if it does what's expected:
require 'em-websocket'
require 'sinatra'
require 'haml'
module Example
def self.em_channel
#em_channel ||= EM::Channel.new
end
EventMachine.run do
class App < Sinatra::Base
configure do
enable :inline_templates
end
get '/' do
haml :index
end
get '/test' do
Example.em_channel.push "Test request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = Example.em_channel.subscribe { |msg| socket.send msg }
Example.em_channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
Example.em_channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
Example.em_channel.unsubscribe(sid)
end
end
end
App.run!
end
end
__END__
## layout
%html
= yield
## index
%div.title Hello world.

ruby faye server with scala client

I have a Faye server running with ruby, and now, I need to send notifications to a client in Scala, but scala can't handle Bayeux, only WebSockets.
Is there a way to change my connection type from Bayeux to using websockets?
Some conf. files I have
faye.ru
require 'faye'
Faye::WebSocket.load_adapter('thin')
bayeux = Faye::RackAdapter.new(:mount => '/faye', :timeout => 25)
run bayeux
sending notifications
def broadcast(channel, data)
message = {:channel => channel, :data => data, :ext => {:auth_token => FAYE_TOKEN}}
uri = URI.parse("http://192.168.0.92:9292/faye")
Net::HTTP.post_form(uri, :message => message.to_json)
end
We migrate our scala app to ruby. Much better now :)

AMQP creating subscribing to queues dynamically

I am trying to build a simple chat application using AMQP, Websockets and Ruby. I understand that this may not be the best use-case to understand AMQP but I would like to understand where i am going wrong.
The following is my amqp-server code
require 'rubygems'
require 'amqp'
require 'mongo'
require 'em-websocket'
require 'json'
class MessageParser
# message format => "room:harry_potter, nickname:siddharth, room:members"
def self.parse(message)
parsed_message = JSON.parse(message)
response = {}
if parsed_message['status'] == 'status'
response[:status] = 'STATUS'
response[:username] = parsed_message['username']
response[:roomname] = parsed_message['roomname']
elsif parsed_message['status'] == 'message'
response[:status] = 'MESSAGE'
response[:message] = parsed_message['message']
response[:roomname] = parsed_message['roomname'].split().join('_')
end
response
end
end
class MongoManager
def self.establish_connection(database)
#db ||= Mongo::Connection.new('localhost', 27017).db(database)
#db.collection('rooms')
#db
end
end
#sockets = []
EventMachine.run do
connection = AMQP.connect(:host => '127.0.0.1')
channel = AMQP::Channel.new(connection)
puts "Connected to AMQP broker. #{AMQP::VERSION} "
mongo = MongoManager.establish_connection("trackertalk_development")
EventMachine::WebSocket.start(:host => '127.0.0.1', :port => 8080) do |ws|
socket_detail = {:socket => ws}
ws.onopen do
#sockets << socket_detail
end
ws.onmessage do |message|
status = MessageParser.parse(message)
exchange = channel.fanout(status[:roomname].split().join('_'))
if status[:status] == 'STATUS'
queue = channel.queue(status[:username], :durable => true)
unless queue.subscribed?
puts "--------- SUBSCRIBED --------------"
queue.bind(exchange).subscribe do |payload|
puts "PAYLOAD : #{payload}"
ws.send(payload)
end
else
puts "----ALREADY SUBSCRIBED"
end
# only after 0.8.0rc14
#queue = channel.queue(status[:username], :durable => true)
#AMQP::Consumer.new(channel, queue)
elsif status[:status] == 'MESSAGE'
puts "********************* Message- published ******************************"
exchange.publish(status[:message)
end
end
ws.onclose do
#sockets.delete ws
end
end
end
I use the status to indicate whether the incoming message is a message for ongoing chat or for a status message requiring me to handle chores like subscribing to the queue.
The problem i face is that when I send a message like
socket.send(JSON.stringify({status:'message', message:'test', roomname:'Harry Potter'}))
The exchange.publish' is called but it still doesn't get pushed via thews.send` to the browser.
Is there something fundamentally wrong with my understanding of EventMachine and AMQP?
Here is the pastie for the same code http://pastie.org/private/xosgb8tw1w5vuroa4w7a
My code seems to work as desired when i remove the durable => true from queue = channel.queue(status[:username], :durable => true)
The following is a snippet of my Rails view which identifies the user's username and the roomname and sends it as part of message via Websockets.
Though the code seems to work when i remove the durable => true I fail to understand why that affects the message being delivered. Please Ignore the mongo part of as it does not play any part yet.
I would also like to know if my approach to AMQP and its usage is correct
<script>
$(document).ready(function(){
var username = '<%= #user.email %>';
var roomname = 'Bazingaa';
socket = new WebSocket('ws://127.0.0.1:8080/');
socket.onopen = function(msg){
console.log('connected');
socket.send(JSON.stringify({status:'status', username:username, roomname:roomname}));
}
socket.onmessage = function(msg){
$('#chat-log').append(msg.data);
}
});
</script>
<div class='block'>
<div class='content'>
<h2 class='title'><%= #room.name %></h2>
<div class='inner'>
<div id="chat-log">
</div>
<div id="chat-console">
<textarea rows="5" cols="40"></textarea>
</div>
</div>
</div>
</div>
<style>
#chat-log{
color:#000;
font-weight:bold;
margin-top:1em;
width:900px;
overflow:auto;
height:300px;
}
#chat-console{
bottom:10px;
}
textarea{
width:100%;
height:60px;
}
</style>
I think your problem might be the queue hangs around on the broker between invocations of ws.onmessage. When the client reconnects the queue and binding already exists so ws.send() doesn't get called.
By default when you create a queue, it and any bindings it has, hangs around until the broker restarts, or you explicitly tell the broker to delete it.
There are two ways to change this:
Adding the durable flag when you create the queue, which will cause the queue to stick around even if the broker restarts
Adding the auto_delete flag, which will cause the broker to automatically delete the entity after a short amount of time of not being having a consumer attached to it
If you have control over the broker you are using the rabbitmq broker, an easy way to introspect what is happening on the broker is to install the management plugin, which provides a web interface to exchanges, bindings and queues on the broker.
On the first look the AMQP bits seem to be OK, but I don't want to set up all the dependencies. If you provide a minimal example with just the AMQP part, I will check it.

Resources