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.
Related
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.
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.
I have an interesting situation that I need to fulfill. I need to have an EventMachine loop that sits and waits for messages in an AMQP queue but then interrupts that loop in order to send out a message to a separate AMQP queue on a regular interval. I'm new to EventMachine and this is what I have so far, except the EventMachine loop doesn't send the necessary message.
Right now I've made two procs:
listen_loop = Proc.new {
AMQP.start(connection_config) do |connection|
AMQP::Channel.new(connection) do |channel|
channel.queue("queue1", :exclusive => false, :durable => true) do |requests_queue|
requests_queue.once_declared do
consumer = AMQP::Consumer.new(channel, requests_queue).consume
consumer.on_delivery do |metadata, payload|
puts "[requests] Got a request #{metadata.message_id}. Sending a reply to #{metadata.reply_to}..."
response = "responding"
channel.default_exchange.publish(response,
:routing_key => metadata.reply_to,
:correlation_id => metadata.message_id,
:mandatory => true)
metadata.ack
end
end
end
end
end
Signal.trap("INT") { AMQP.stop { EM.stop } }
Signal.trap("TERM") { AMQP.stop { EM.stop } }
}
send_message = Proc.new {
AMQP.start(connection_config) do |connection|
channel = AMQP::Channel.new(connection)
queue = channel.queue('queue2')
channel.default_exchange.publish("hello world", :routing_key => queue.name)
EM.add_timer(0.5) do
connection.close do
EM.stop{ exit }
end
end
end
}
And then I have my EventMachine Loop:
EM.run do
EM.add_periodic_timer(5) { send_message.call }
listen_loop.call
end
I am able to receive messages in the listen loop but I am unable to send off any of the messages on the regular interval.
Figured out what I was doing wrong. The message loop wasn't able to open up a new connection to the RabbitMQ server because it was already connected. Consolidated everything into a single EventMachine loop and reused the connection and it works.
For those curious it looks like this:
EM.run do
AMQP.start(connection_config) do |connection|
channel = AMQP::Channel.new(connection)
EM.add_periodic_timer(5) { channel.default_exchange.publish("foo", :routing_key => 'queue2') }
queue = channel.queue("queue1", :exclusive => false, :durable => true)
channel.prefetch(1)
queue.subscribe(:ack => true) do |metadata, payload|
puts "[requests] Got a request #{metadata.message_id}. Sending a reply to #{metadata.reply_to}..."
response = "bar"
channel.default_exchange.publish(response,
:routing_key => metadata.reply_to,
:correlation_id => metadata.message_id,
:mandatory => true)
metadata.ack
end
end
Signal.trap("INT") { AMQP.stop { EM.stop } }
Signal.trap("TERM") { AMQP.stop { EM.stop } }
end
I am trying to create SOAPClient using Savon - rubygem.
Its a WCF soap service with WSSE auth over https. Here is the code that I tried:
require 'savon'
client = Savon::Client.new do
wsdl.document = "https://svc.sxxxxxify.com:8081/ConfSet.svc?wsdl"
config.soap_version = 2
wsse.credentials "aa5#xxasxsaxsh.com", "test123"
end
p client.wsdl.soap_actions
response = client.request :get_user_clients
p response
But I get this error:
http://www.w3.org/2005/08/addressing/soap/fault2012-10-26T06:07:42.247Z2012-10-26T06:12:42.247Zs:Sendera:DestinationUnreachableThe message with To '' cannot be processed at the
receiver, due to an AddressFilter mismatch at the EndpointDispatcher.
Check that the sender and receiver's EndpointAddresses
agree.
.
The message with To '' cannot be processed at the receiver, due to an
AddressFilter mismatch at the EndpointDispatcher. Check that the
sender and receiver's EndpointAddresses agree. (Savon::SOAP::Fault)
Please help me solve this problem
I had the some problem. I've solved the 'To' problem by providing a header entry and a new namespace. The 'Action' header was also necessary though, and I only discovered that after inspecting SoapUI logs. Here is what worked for me:
#service_url = 'https://svc.sxxxxxify.com:8081/ConfSet.svc/service'
#action = 'your_action'
#client = Savon.client(:wsdl => "#{#service_url}?wsdl", :soap_version => 2,
:namespaces => {"xmlns:x" => "http://www.w3.org/2005/08/addressing"},
:soap_header => {"x:To" => #service_url, "x:Action" => "http://tempuri.org/#{#action}"})
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 :)