I am writing on a web-application where I want to send JSON-Code from the client-side to the server-side over Tornado WebSockets with SSL. When I want to build a connection, Google Chrome shows in the console-log the error:
Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING State.
My python server looks like this:
from tornado import websocket, web, ioloop
import json
from sqlite3functions import *
class SocketHandler(websocket.WebSocketHandler):
def on_message(self, message):
handleRequest(self, json.loads(message), True)
print(message)
def handleRequest(obj, message, isWebsock):
...
def writeResponse(obj, message, isWebsock):
if (isWebsock):
obj.write_message(message)
else:
print(message)
obj.write(message)
print('msg sent')
app = web.Application([
(r'/w', SocketHandler)
])
if __name__ == "__main__":
app.listen(8888)
ioloop.IOLoop.instance().start()
My client:
var ws;
function connect() {
ws = new WebSocket('wss://127.0.0.1:8888/w');
ws.onopen = function()
{
ws.send("Message to send");
};
}
$(document).ready(function() {
connect();
$("#Button").on('click', function() {
...
ws.send(data);
});
});
Thanks
Don't let your socket send data until its ready state is connected.
These functions may help:
function sendMessage(msg) {
waitForSocketConnection(nvWS, function() {
ws.send(msg);
});
};
function waitForSocketConnection(socket, callback){
setTimeout(
function(){
if (socket.readyState === 1) {
if(callback !== undefined){
callback();
}
return;
} else {
waitForSocketConnection(socket,callback);
}
}, 5);
};
Related
This is a continuation of a question I had earlier, Using Socket IO and aiohttp for data transfer between node JS and Python, based on this tutorial, https://tutorialedge.net/python/python-socket-io-tutorial/.
I have an asynchronous tunnel that connects a Node JS client (send.js) and a python server (receive.py). Right now send.js outputs a random number and then sends it to the Python server (receive.py), which then sends the message back to the JS client.
The setup works, however, it takes a couple minutes for the server to start receiving data from send.js, and I do not know why.
The Node JS script will output data, but the server will not receive it for at least a couple minutes and even after it starts receiving data, it does not receive the data it did not get earlier, it will only receive the data from the moment the server and client can finally connect.
I am not sure if this has something to with the Python side, Node JS side, or something else.
I am using Node 8.16.1 and Python 3.7.3
The code is below:
send.js
const io = require('socket.io-client');
const socket = io('http://localhost:8080');
socket.on('reply', message => {
console.log('Got from server: ');
console.log(message);
});
function generateNumber() {
const n = Math.floor(Math.random() * 50);
return { number: n };
}
function sendMsg() {
const json = generateNumber();
console.log('Sending to server:');
console.log(json);
socket.emit('message', json);
}
function loop() {
const rand = Math.round(Math.random() * (3000 - 500)) + 500;
setTimeout(() => {
sendMsg();
loop();
}, rand);
}
socket.on('connect', () => {
console.log('Connected to server');
loop();
});
receive.py
from aiohttp import web
import socketio
# creates a new Async Socket IO Server
sio = socketio.AsyncServer()
# Creates a new Aiohttp Web Application
app = web.Application()
# Binds our Socket.IO server to our Web App
# instance
sio.attach(app)
# If we wanted to create a new websocket endpoint,
# use this decorator, passing in the name of the
# event we wish to listen out for
#sio.on('message')
async def print_message(sid, message):
# When we receive a new event of type
# 'message' through a socket.io connection
# we print the socket ID and the message
#print("Socket ID: " , sid)
print(message)
await sio.emit('reply', message)
# We kick off our server
if __name__ == '__main__':
web.run_app(app)
Let me know if more information is needed.
I don't know if you have to use the packages that you are using but here is my working version with ws package for node and asyncio and websockets packages for python. Have fun and nice question.
send.js
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8080')
console.log(ws)
function generateNumber() {
const n = Math.floor(Math.random() * 50);
return {
number: n
};
}
function sendMsg() {
const json = JSON.stringify(generateNumber());
console.log('Sending to server:');
console.log(json);
ws.send(json);
}
function loop() {
setTimeout(() => {
sendMsg();
loop();
}, 5000);
}
ws.on('open', function open() {
console.log('connect')
console.log(ws)
loop()
})
ws.on('message', function(data) {
console.log(data)
})
receive.py
import asyncio
import websockets
async def start(websocket, path):
print("connected")
while True:
data = await websocket.recv()
print(f"< {data}")
await websocket.send(data)
async def main():
server = await websockets.serve(start, 'localhost', 8080)
await server.wait_closed()
asyncio.run(main())
I'd like to write a vue-plugin to get handy WebSocket methods like connect() and subscribe() in my Vue application. I've got a problem with connecting to WebSocket, it only works when I call connect() method in the mounted hook and load the whole page (like with the browser refresh button). In another case, when I first load the page and then call the connect() method explicitly by the button click, the connection isn't established.
My vue-plugin code:
import SockJS from "sockjs-client";
import Stomp from "webstomp-client";
const WebSocketTester = {
install(Vue, options) {
console.log("websocket tester launched");
let connected = false;
const ws = {
connected: () => connected
};
const stompClient = getStompClient("http://localhost:8080/ws");
const connect = () => {
return new Promise((resolve, reject) => {
if (connected) {
reject("Already connected !");
return;
}
console.log("trying to connect websocket");
stompClient.connect({}, frame => {
console.log("got websocket frame:");
console.log(frame);
if (frame.command == "CONNECTED") {
connected = true;
resolve();
} else {
reject("Could not connect with " + url);
}
});
});
};
ws.connect = () => {
return connect();
};
Vue.prototype.$ws = ws;
}
};
const getStompClient = webSocketUrl => {
const socket = new SockJS(webSocketUrl);
return Stomp.over(socket);
};
export default WebSocketTester;
My vue component:
<template>
<div class="hello">
<button #click="connect">Connect with websocket</button>
</div>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
methods: {
connect() {
console.log("connecting...");
this.$ws.connect().catch(error => {
console.log("could not connect by click");
console.log(error);
});
}
},
mounted() {
// this works well
// this.$ws.connect().catch(error => {
// console.log("could not connect in mounted");
// console.log(error);
// });
}
};
</script>
In the case, I uncomment the mounted hook, after page load I see the console log like this:
websocket tester launched
trying to connect websocket
Opening Web Socket...
Web Socket Opened...
DEPRECATED: undefined is not a recognized STOMP version. In next major client version, this will close the connection.
>>> CONNECT
>>> length 52
<<< CONNECTED
connected to server undefined
got websocket frame:
Frame {command: "CONNECTED", headers: {…}, body: ""}
And everything works correct. But, if I comment the mounted hook and want to connect with the WebSocket by the button click, the console log looks like this:
websocket tester launched
connecting...
trying to connect websocket
Opening Web Socket...
and that's it, the connection isn't established. Why this happens and how to fix it?
OK I figured it out. The problem line was const stompClient = getStompClient("http://localhost:8080/ws"); in the plugin. I've moved it to the connect method and store as ws.object.
if (connected) {
reject("Already connected !");
return;
}
ws.stompClient = getStompClient("http://localhost:8080/ws");
console.log("trying to connect websocket");
ws.stompClient.connect({}, frame => {
Later, I use ws.stompClient and it works fine.
Relevant Info about Environment
Rails Version: 5.2
Devise Authentication
Code
Warden hooks for setting cookies
# config/initializers/warden_hooks.rb
Warden::Manager.after_set_user do |user,auth,opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = user.id
auth.cookies.signed["#{scope}.expires_at"] = 30.minutes.from_now
end
Warden::Manager.before_logout do |user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = nil
auth.cookies.signed["#{scope}.expires_at"] = nil
end
Connection parent class for authenticating. This works as it should and the connection is logged.
# application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.email
end
protected
USER_CLASSES = ['admin', 'agent', 'tech', 'client']
def find_verified_user
user_class = USER_CLASSES.find { |klass| cookies.signed["#{klass}.id"] }
user_id = cookies.signed["#{user_class}.id"]
verified_user = User.find_by(id: user_id)
if verified_user && cookies.signed["#{user_class}.expires_at"] > Time.now
verified_user
else
reject_unauthorized_connection
end
end
end
end
Client side code for setting up connection and sending messages.
// channels/conversations.js
$(document).on('turbolinks:load', function() {
var messages = $('#messages');
var projectId = messages.data('project-id');
var button = $('#new_message input[type=submit]');
var subscription = { channel: "ConversationsChannel", id: projectId };
App.conversation = App.cable.subscriptions.create(subscription, {
connected: function() {
console.log('Upgraded to websocket connection.');
},
disconnected: function() {
console.log('Websocket not connected.');
},
received: function(data) {
messages.append(data['message']);
},
sendMessage: function(message, project_id) {
return this.perform('send_message', {
message: message,
project_id: projectId
});
}
});
$('#message_body').on('keyup', function(e) {
var message = e.target.value;
if ($.trim(message).length > 0) {
button.prop('disabled', false);
} else {
button.prop('disabled', true);
}
});
$('#new_message').submit(function(e) {
e.preventDefault();
var textarea = $('#message_body');
App.conversation.sendMessage(textarea.val(), projectId);
textarea.val('');
button.prop('disabled', true);
});
});
Problem
The connection is registered on the server
but the state of the ActionCable.Connection on the client is never set to open, which causes the Connection.send method to fail because this.isOpen() returns false (so the client is never subscribed to the channel) - ActionCable source
If I put a break point in chrome inside send and pass the data to the webSocket.send method the subscription is successful.
I have to repeat that again if I want to send a message, as the state of the connection is still not set to open even after successfully establishing the subscription.
What I've Already Tried
Authenticating everyone
Returning the current_user in Connection#connect
Removing all code from the Connection class so no behavior is overridden
For the code below I get a "socket hang up" error when making the axios (get) request to the server that was created through app.listen, does anyone have any insight?
var server = app.listen(4000, () => {
if (getFeedId(sub) === 2) {
return axios.get('https://localhost:4000', (data) => {
try {
console.log('Success!');
var newData = JSON.parse(data);
}
catch(err) {
console.log(err);
}
});
}
});
I have a problem with socket.io. The client can emit message to server for connecting, but if I leave the connection and then try to reconnect, socket io doesn't emit the message ('joinPerformance'). Only if I refresh the page then I can emit messages.
#CLIENT
function loginSuccess(easyrtcid, noMedia) {
selfEasyrtcid = easyrtcid;
console.log('loginSuccess',selfEasyrtcid);
//arm socket events only once
if(!socket){
socket = easyrtc.webSocket;
streaming.armEvents(socket);
}
settings.isStarted=true;
if(typeof settings.bindings.loginSuccess==='function')
settings.bindings.loginSuccess(easyrtcid);
if(settings.isInitiator===true){
console.log('Telling server to start performance');
socket.emit('startPerformance', {performanceID: settings.performanceID});
}else{
console.log('Telling server to join performance');
socket.emit('joinPerformance', {performanceID: settings.performanceID});
}
}
#SERVER
socket.on('joinPerformance', function (data) {
console.log('joinPerformance', data);
if(validateData(data, socket))
onJoinPerformance(data, socket);
});
function onJoinPerformance(data, socket) {
console.log('Join performance', data.performanceID);
console.log('rooom info',socket.adapter.rooms[data.performanceID]);
if(!socket.adapter.rooms[data.performanceID] || !socket.adapter.rooms[data.performanceID][socket.id] )
socket.join(data.performanceID);
if (!streamingPerformances[data.performanceID]) {
console.log('No such performance is streaming');
error(socket, 'No such performance is streaming');
} else {
console.log('joined performance');
streamingPerformances[data.performanceID].activeViewers[data.userID] = data;
streamingPerformances[data.performanceID].activeViewersCount++;
socket.emit('joinedPerformance', streamingPerformances[data.performanceID]);
socket.broadcast.to(data.performanceID).emit('performanceState', streamingPerformances[data.performanceID]);
}
}