Actually my question is not about console only. I haven't found any libraries for testing LiteCable, and I try to implement some. The problem I faced now, is when I call in console something like (exactly same as official example):
LiteCable.broadcast "chat_#{chat_id}", user: user, message: data["message"], sid: sid
It returns nil. I've tried to put byebug in the code, and when I call this method with absolutely same parameters there, I got pretty big sheet with some unreadable parameters. And the main problem is I don't see messages I try to send from console.
Can someone explain why is that and how can I implement sending requests from console? The point is I working on api-only application, and try to cover this functionality with proper tests. Thank you very much in advance!
P.S: the project is on pure ruby, without rails
Since you don't have the entire code, I am unable to get a clear sense of your setup. The default broadcast_adapter for LiteCable is memory, so if you were to broadcast from your console, processes outside your console will not receive such streams. If you use the anycable adapter which I believe queues broadcasts in redis, then you should be able to broadcast from your console provided you have the correct connection.
Here is a simple implementation of a WS client and a server using LiteCable without Rails
# ~/app/server.rb
# A simple WS server that evaluates math expression literally using ruby eval
# Run this server with ruby server.rb
require "rack"
require "rack/handler/puma"
require "lite_cable"
require "lite_cable/server"
class CalculatorConnection < LiteCable::Connection::Base
identified_by :token
def connect
self.token = request.params["token"]
$stdout.puts "Session #{token} is connected to the server"
end
def disconnect
$stdout.puts "Session #{token} disconnected from the server"
end
end
class CalculatorChannel < LiteCable::Channel::Base
identifier :calculator
def subscribed
stream_from "calculator"
end
def evaluate(payload)
$stdout.puts "Data received from the client"
LiteCable.broadcast "calculator", result: eval(payload['expression'])
end
end
app = Rack::Builder.new
app.map "/cable" do
use LiteCable::Server::Middleware, connection_class: CalculatorConnection
run(proc { |_| [200, {"Content-Type" => "text/plain"}, ["OK"]] })
end
Rack::Handler::Puma.run app
// ~/app/client.js
// A client that talks to the WS server.
// To run, Open chrome console and paste this code
let socket = null;
(function init() {
socket = new WebSocket("ws://0.0.0.0:9292/cable?token=abc123");
socket.onopen = function (e) {
console.info("[connected] : Connection established to the server");
};
socket.onmessage = function (e) {
console.info("[message] : Message from the server", e.data);
};
socket.onerror = function (e) {
console.error("[error] : Error establishing connection", e.message);
};
})();
// subscribe to the calculator channel
socket.send(
JSON.stringify({
command: "subscribe",
identifier: JSON.stringify({
channel: "calculator",
}),
})
);
// call the evaluate method on the Calculator channel
socket.send(
JSON.stringify({
command: "message",
identifier: JSON.stringify({
channel: "calculator",
}),
data: JSON.stringify({
expression: "150 * 4 - 3 / (2 + 2)",
action: "evaluate",
}),
})
);
There's nothing special going on here just a simple rack app that uses Puma which adds support for rack socket hijacking. This should guide you in the right direction. Make sure you keep both your server and chrome console open to see the output. Try sending additional message to the server to see the output.
If you are looking at implementing a WebSocket client in ruby instead of js using LiteCable, check the client socket class in the LiteCable repo
Related
I'm developing an application that uses multiple threads for various communication channels. One of them is websocket for which I'm using asyncio loop to run:
class Rotator():
def __init__(self):
# some irrelevant stuff here...
self._readThread = threading.Thread(target=self._ReadThread, daemon=True)
self._usbThread = threading.Thread(target=self._UsbThread, daemon=True)
self._webThread = threading.Thread(target=self._WebThread, daemon=True)
self._socketThread = threading.Thread(target=self._SocketThread, daemon=True)
self.InitializeRotator()
def InitializeRotator(self):
# some more irrelevant stuff here
self._readThread.start()
self._usbThread.start()
self._webThread.start()
self._socketThread.start()
# not including _ReadThread() and _UsbThread() as they are irrelevant in this context
def _WebThread(self): # including _WebThread() as illustration
handler_object = server.WebHttpRequestHandler
_httpServer = socketserver.TCPServer(("",self.webPort), handler_object)
_httpServer.serve_forever()
async def ws_handler(websocket, path):
data = await websocket.recv()
reply = f"Data recieved as: {data}!"
await websocket.send(reply)
def _SocketThread(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
start_server = websockets.serve(self.ws_handler, str(self.DHCPIP), 8000)
loop.run_until_complete(start_server)
loop.run_forever()
The frontend JS code that communicates with this is:
var ws;
function startWebsocket() {
ws = new WebSocket('ws://192.168.200.76:8000')
ws.onopen = function(e){
console.log('Connection established')
ws.send('Connection established')
}
ws.onmessage = function(e){
console.log('websocket message event:', e)
}
ws.onclose = function(){
// connection closed, discard old websocket and create a new one in 5s
console.log('Connection lost, will reconnect in 5s...')
ws = null
setTimeout(startWebsocket, 5000)
}
}
startWebsocket();
The web page that contains the frontend is served by the server run by _WebThread(). The issue is that the connection is being closed immediately after being established. I'm assuming the issue is caused by some conflict in the asyncio/thread combination, because I tested the server side code on its own and it worked like a charm. What am I doing wrong?
I recreated your example and managed to fix it using two changes:
ws_handler is missing the self parameter. Since it's an instance method, the instance itself will be passed as the first parameter. The actual websocket object will be the second one.
in ws_handler I added a while True loop. I followed the example in the docs here https://websockets.readthedocs.io/en/stable/intro/tutorial1.html#bootstrap-the-server
final result:
async def ws_handler(self, websocket, path):
while True:
data = await websocket.recv()
reply = f"Data recieved as: {data}!"
await websocket.send(reply)
Other changed that I made were to use 8000 as a port for the http server, 8001 for the websocket server and 'localhost' instead of 192.168.200.76. But I'm pretty sure that's not relevant
I have an Electron app which tries to connect to a device over a web socket. The connection is encrypted (i.e. wss) but the SSL certificate is self signed and thus, untrusted.
Connecting inside Chrome is ok and it works. However inside Electron I run into problems. Without putting any certificate-error handlers on the BrowserWindow or on the app I receive the following error in the console output:
WebSocket connection to 'wss://some_ip:50443/' failed: Error in connection establishment: net::ERR_CERT_AUTHORITY_INVALID
Then shortly after:
User is closing WAMP connection.... unreachable
In my code, to make the connection I run the following.
const connection = new autobahn.Connection({
realm: 'some_realm',
url: 'wss://some_ip:50443'
});
connection.onopen = (session, details) => {
console.log('* User is opening WAMP connection....', session, details);
};
connection.onclose = (reason, details) => {
console.log('* User is closing WAMP connection....', reason, details);
return true;
};
connection.open();
// alternatively, this also displays the same error
const socket = new WebSocket(`wss://some_ip:50443`);
socket.onopen = function (event) {
console.log(event);
};
socket.onclose = function (event) {
console.log(event);
};
NOTE: Autobahn is a Websocket library for connecting using the WAMP protocol to a socket server of some sort. (in my case, the device) The underlying protocol is wss. Underneath the code above, a native JS new WebSocket() is being called. In other words:
As I mentioned, I've tested this code in the browser window and it works. I've also built a smaller application to try and isolate the issue. Still no luck.
I have tried adding the following code to my main.js process script:
app.commandLine.appendSwitch('ignore-certificate-errors');
and
win.webContents.on('certificate-error', (event, url, error, certificate, callback) => {
// On certificate error we disable default behaviour (stop loading the page)
// and we then say "it is all fine - true" to the callback
event.preventDefault();
callback(true);
});
and
app.on('certificate-error', (event, webContents, link, error, certificate, callback) => {
// On certificate error we disable default behaviour (stop loading the page)
// and we then say "it is all fine - true" to the callback
event.preventDefault();
callback(true);
});
This changed the error to:
WebSocket connection to 'wss://some_ip:50443/' failed: WebSocket opening handshake was canceled
My understanding is that the 'certificate-error' handlers above should escape any SSL certificate errors and allow the application to proceed. However, they're not.
I've also tried adding the following to main.js:
win = new BrowserWindow({
webPreferences: {
nodeIntegration: true,
webSecurity: false
}
});
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = '1';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
With Election, how do I properly deal with a certificate from an untrusted authority? i.e. a self signed cert.
Any help would be much appreciated.
I had the same problem , all i added was your line:
app.commandLine.appendSwitch('ignore-certificate-errors');
I use socket.io, but i think its the same principal.
I do however connect to the https protocol and not wss directly.
This is what my connection looks like on the page:
socket = io.connect(
'https://yoursocketserver:yourport', {
'socketpath',
secure: false,
transports: ['websocket']
});
That seems to have done the trick.
Thank you for the help :) i hope this answer helps you too.
Is it possible to connect any external application to my sails.js application via WebSockets?
I can use the underlying socket.io embedded in sails.js to talk between the client and server, that's not a problem.
But, I would like to connect another, separate, application to the sails.js server via websockets, and have those two communicate with each other that way, and I am wondering if this is possible?
If so, how can we do this?
Thanks.
Based on SailsJS documentation, we have access to the io object of socket.io, via sails.io.
From that, I just adjusted boostrap.js:
module.exports.bootstrap = function (cb) {
sails.io.on('connection', function (socket) {
socket.on('helloFromClient', function (data) {
console.log('helloFromClient', data);
socket.emit('helloFromServer', {server: 'says hello'});
});
});
cb();
};
Then, in my other nodejs application, which could also be another SailsJS application and I will test that later on, I simply connected and sent a message:
var io = require('socket.io-client');
var socket = io.connect('http://localhost:3000');
socket.emit('helloFromClient', {client: 'says hello'});
socket.on('helloFromServer', function (data) {
console.log('helloFromServer', data);
});
And here are the outputs.
In SailsJS I see:
helloFromClient { client: 'says hello' }
In my client nodejs app I see:
helloFromServer { server: 'says hello' }
So, it seems to be working just fine.
I have a Sinatra app that uses Websockets.
My app works when I run it with ruby app.rb, but doesn't when I'm trying to run it with shotgun app.rb.
This is in my sending_out.erb:
<script>
$(document).ready(function(){
connection = new WebSocket('ws://' + window.location.host + window.location.pathname);
connection.onopen = function(){
$("#msgs").append('Connection opened'+"<br>")
};
connection.onmessage = function(e){
$("#msgs").append(e.data+"<br>");
};
connection.onclose = function() {
$("#msgs").append('Connection closes from view'+"<br>");
};
$("form").submit(function(){
connection.send( $("input").val() );
});
});
</script>
And this is in my app.rb:
require 'sinatra-websocket'
set :sockets, []
get '/sending_out' do
request.websocket do |connection|
connection.onopen do
connection.send("Hello World!")
settings.sockets << connection
connection.send("opened")
connection.send("went")
end
connection.onmessage do |msg|
EM.next_tick { settings.sockets.each{|s| s.send(msg) } }
end
connection.onclose do
warn("websocket closed")
settings.sockets.delete(ws)
end
end
end
It must show
Connection opened
Hello World!
opened
went
when I go to the page. But it only shows
Connection closes from view
with Shotgun.
And in the console it says WebSocket connection to 'ws://127.0.0.1:9393/sending_out' failed: Error during WebSocket handshake: Unexpected response code: 500.
Is there an issue running Websockets with Shotgun?
The main feature of Shotgun is that it automatically reloads your whole code every request and I think that could be also the problem you are facing.
Shotgun should be used just for development.
For production purposes you have plenty of other options available:
Puma
Rainbows
Unicorn
Thin
others
Comparison of ruby servers can be found on https://www.digitalocean.com/community/tutorials/a-comparison-of-rack-web-servers-for-ruby-web-applications
I'm trying to implement autobahn 0.9.5 on my SPA project using DurandalJS.
var ab = require('autobahn');
live = new ab.Connection(
{
url: 'ws://localhost:8080',
realm: 'realm1'
});
live.onopen = function(session, details)
{
console.log('Autobahn open successfully!', session);
};
live.onclose = function(reason, details)
{
console.log('Autobahn connection lost', reason + ' - ' + details);
};
live.open();
i received an error on firefox and chrome browser
Firefox:
InvalidAccessError: A parameter or an operation is not supported by the underlying object
websocket.close(code, reason);
Chrome:
WebSocket connection to 'ws://localhost:8080/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
I have no idea what happened..
BEFORE I STARTED WITH - autobahn 0.9.5
I have write simple test on test.html to see if everything setup in backend is correct.
But on this test i currently used autobahn 0.8.2
test.html
<script src="http://autobahn.s3.amazonaws.com/js/autobahn.min.js"></script>
<script>
var conn = new ab.Session(
// Websocket host
'ws://localhost:8080',
// Callback on connection established
function() {
// Once connect, subscribe to channel
conn.subscribe('3', function(topic, data) {
console.log(topic, data);
});
},
// Callback on connection close
function() {
console.warn('WebSocket connection closed');
},
// Additional AB parameters
{'skipSubprotocolCheck': true}
);
</script>
This test working perfectly as what i need, but after I try to implement it inside real project, I can't make autobahn 0.8.2 loaded using requireJS, It keep give me an error ab not defined.
I don't really understand what is happening, according of autobahn getting started, it should work.
and here is how I define it on main.js (requirejs paths and shim config)
requirejs.config({
paths: {
'autobahn' : 'https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min',
'when' : 'https://cdnjs.cloudflare.com/ajax/libs/when/2.7.1/when'
},
shim: {
'autobahn': {
deps: ['when']
}
}
});
Hopefully somebody can help me, I really love to make it working !
Any help will be greatly appreciated!
Thanks
Probably late, but for further reference.
This is probably not a complete answer to SO question.
First of all, everything should be written either for AutobahnJS v0.8.2 (which supports WAMPv1) or for AutobahnJS v0.9.5 (WAMPv2).
Check API documentation.
WAMP v1
AutobahnJS v0.8.2 API - http://autobahn.ws/js/reference_wampv1.html
it loads from http://autobahn.s3.amazonaws.com/js/autobahn.js
global variable for autobahn is ab
connecting with var conn = new ab.Session(wuri, onOpenCallback, onCloseCallback, options);
WAMP v2
AutobahnJS v0.9.5 API - http://autobahn.ws/js/reference.html
it loads from https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz
global variable for autobahn is autobahn
connecting with new autobahn.Connection({url: wuri, realm: yourRealm});