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.
Related
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
}
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
I am trying to create a simple WebSocket connection in JavaScript against my Rails app. I get the following:
WebSocket connection to 'ws://localhost:4000/' failed: Error during WebSocket handshake: 'Sec-WebSocket-Accept' header is missing
What am I doing wrong? Here is my code:
JavaScript:
var socket = new WebSocket('ws://localhost:4000');
socket.onopen = function() {
var handshake =
"GET / HTTP/1.1\n" +
"Host: localhost\n" +
"Upgrade: websocket\n" +
"Connection: Upgrade\n" +
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\n" +
"Sec-WebSocket-Protocol: quote\n" +
"Sec-WebSocket-Version: 13\n" +
"Origin: http://localhost\n";
socket.send(handshake);
};
socket.onmessage = function(data) {
console.log(data);
};
Ruby:
require 'rubygems'
require 'em-websocket-server'
module QuoteService
class WebSocket < EventMachine::WebSocket::Server
def on_connect
handshake_response = "HTTP/1.1 101 Switching Protocols\n"
handshake_response << "Upgrade: websocket\n"
handshake_response << "Connection: Upgrade\n"
handshake_response << "Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\n"
handshake_response << "Sec-WebSocket-Protocol: quote\n"
send_message(handshake_response)
end
def on_receive(data)
puts 'RECEIVED: ' + data
end
end
end
EventMachine.run do
print 'Starting WebSocket server...'
EventMachine.start_server '0.0.0.0', 4000, QuoteService::WebSocket
puts 'running'
end
The handshake headers are per Wikipedia.
I think that once the connection is open the request and response have already occurred, so sending headers at that point is too late. In addition, headers have to end with a blank line, which you omitted.
According to the demos, you don't even have to set headers in the client or the server--the ruby module automatically takes care of the headers on the server side, and html5 automatically takes care of the headers on the client side. I think this should work:
require "em-websocket-server"
class EchoServer < EM::WebSocket::Server
def on_connect
EM::WebSocket::Log.debug "Connected"
puts "I felt a connection."
end
def on_receive msg
puts "RECEIVED: #{msg}"
send_message msg
end
end
EM.run do
myhost = "0.0.0.0"
myport = 8000
puts "Starting WebSocket server. Listening on port #{myport}..."
EM.start_server myhost, myport, EchoServer
end
html file:
<!DOCTYPE html> <html> <head><title>Test</title>
<script type="text/javascript">
var myWebSocket = new WebSocket("ws://localhost:8000");
myWebSocket.onopen = function(evt) {
console.log("Connection open. Sending message...");
myWebSocket.send("Hello WebSockets!"); };
myWebSocket.onmessage = function(evt) {
console.log(evt.data);
myWebSocket.close(); };
myWebSocket.onclose = function(evt) {
console.log("Connection closed."); };
myWebSocket.onerror = function(err) {
alert(err.name + " => " + err.message); } </script>
</head> <body> <div>Hello</div> </body> </html>
And it does work in Safari 5.1.9 (which is an older browser): I see the expected output on both the server and the client. However, the code does not work in Firefox 21: I get the error message...
Firefox can't establish a connection to the server at ws://localhost:8000/.
var myWebSocket = new WebSocket("ws://localhost:8000");
I notice that in both Firebug and Safari Developer Tools, the server does not send a Sec-WebSocket-Accept header:
Response Headers
Connection Upgrade
Upgrade WebSocket
WebSocket-Location ws://localhost:8000/
WebSocket-Origin null
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control no-cache
Connection keep-alive, Upgrade
DNT 1
Host localhost:8000
Origin null
Pragma no-cache
Sec-WebSocket-Key r9xT+ywe533EHF09wxelkg==
Sec-WebSocket-Version 13
Upgrade websocket
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0
Nothing I tried would make the code work in Firefox 21.0. To check whether Firefox 21.0 even supports websockets, I went to:
http://www.websocket.org/echo.html
and it said my browser does support websockets.
Is there any reason you have to use the em-websocket-server module? The last modification for that module on github was three years ago. And whenever you see require rubygems in ruby code, that should alert you that the code is old. I tried the newer em-websocket module, and I was able to successfully transfer data back and forth using websockets on both Firefox 21.0 and Safari 5.1.9:
require 'em-websocket'
myhost = "0.0.0.0"
myport = 8000
EM.run {
puts "Listening on port #{myport}..."
EM::WebSocket.run(:host => myhost, :port => myport, :debug => false) do |ws|
ws.onopen do |handshake|
path = handshake.path
query_str = handshake.query
origin = handshake.origin
puts "WebSocket opened:"
puts "\t path \t\t -> #{path}"
puts "\t query_str \t -> #{query_str}"
puts "\t origin \t -> #{origin}"
end
ws.onmessage { |msg|
ws.send "Pong: #{msg}"
}
ws.onclose {
puts "WebSocket closed"
}
ws.onerror { |e|
puts "Error: #{e.message}"
}
end
}
Same client side code. Now the response headers include Sec-WebSocket-Accept:
Response Headers
Connection Upgrade
Sec-WebSocket-Accept LyIm6d+kAAqkcTR744tVK9HMepY=
Upgrade websocket
Request Headers
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control no-cache
Connection keep-alive, Upgrade
DNT 1
Host localhost:8000
Origin null
Pragma no-cache
Sec-WebSocket-Key pbK8lFHQAF+arl9tFvHn/Q==
Sec-WebSocket-Version 13
Upgrade websocket
User-Agent Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0
In your code, I don't think you are setting any headers. Instead, you are just sending messages back and forth that happen to contain characters that look like headers. Apparently, your browser requires the Sec-WebSocket-Accept header in the response before it will allow the connection, and when the em-websocket-server module fails to set that header in the response, your browser refuses the connection.
The relevant source code for em-websockets-server looks like this:
module EM
module WebSocket
module Protocol
module Version76
# generate protocol 76 compatible response headers
def response
response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
response << "Upgrade: WebSocket\r\n"
response << "Connection: Upgrade\r\n"
response << "Sec-WebSocket-Origin: #{origin}\r\n"
response << "Sec-WebSocket-Location: #{scheme}://#{host}#{path}\r\n"
if protocol
response << "Sec-WebSocket-Protocol: #{protocol}\r\n"
end
response << "\r\n"
response << Digest::MD5.digest(keyset)
response
end
As you can see, it doesn't set the Sec-WebSocket-Accept header. That code is in a module called Version76, and searching google for websockets version 76 yields an obsolete protocol(which contains an example of a request and response):
https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76
Here is the current websockets protocol(which also contains an example of a request and response):
https://www.rfc-editor.org/rfc/rfc6455
Conclusion: em-websockets-server is obsolete.
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);
}
}
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