Custom handshake data with Flask-SocketIO - python

I have a Python server using Flask, that has a websocket connection using Flask-SocketIO . There is a similar question :
Send custom data along with handshakeData in socket.io? The idea is to do the same thing but instead of using Node, using Flask. I want the client to send some data in the connect event, for example:
var socket = io("http://127.0.0.1:3000/", { query: "foo=bar" });
I haven't been able to get that custom data, and I can't rely on cookies due to the client's framework. A working solution is to have the connect event as usual, and then, in a custom event, get that information as a payload. But what we would want is to only have to do a connect event. Thanks!

As Miguel suggested in his comment you can simply do
from flask import Flask
from flask import request
app = Flask(__name__)
socketio = SocketIO(app)
#socketio.on('connect')
def connect():
foo = request.args.get('foo')
# foo will be 'bar' when a client connects
socketio.run(app)
on the server side.

The global connect method gets called on server side. But not my connect method for a specific namespace. Are namespaces supporting query parameter?
Client Code
const socket = io.connect('http://127.0.0.1:5000/ds', { query: {name: "Test"} });
Server Code
#socketio.on('connect', namespace='/ds')
def test_connect():
foo = request.args.get('foo')
print("CONNECT DS")

as of this time in 2021 you can send Auth data from python-socketio client with
sio.connect('http://localhost:6000/',auth={"authToken":"some auth token"})

Related

Flask socket IO emit from another module

I have almost read every piece of article available on the internet but nothing seems to work for my case. I have installed flask-socketio and everything works fine until I emit the messages from a module other than app.py.
I have tried several ways to accomplish this and I have also read in the doc about it by using Redis but it also did not work for me. Here are the code snippets that I have.
app.py
from flask import Flask
from flask import request
from flask_socketio import send, SocketIO, emit, join_room
app = Flask(__name__)
# This is to stop force sorting in response, by default jsonify sorts the response keys alphabetically
app.config["JSON_SORT_KEYS"] = False
socketio = SocketIO(app, cors_allowed_origins="*")
#socketio.on('join')
def client_join_room(data):
print(type(data))
room = data['room']
join_room(room)
send('you have entered the room.', room=room)
#app.route('/msg')
def send_message():
socketio.emit("message", "Server message", room='my_room')
return "I got you."
if __name__ == '__main__':
socketio.run(host="0.0.0.0", port=5001, debug=True, app=app)
my_module.py
def some_method():
import app
app.socketio.emit("message", "Some information about process", room='my_room', broadcast=True)
Note that I have imported app inside the method because app.py also imports my_module.py
I am able to join room.
When I call localhost:5001/msg it does emit to 'my_room'.
The emit does not work inside my_module.py and I have no idea why.
I am consoling the messages that I get from the server at the front-end so I know for sure which messages are received and which are not.
Also, the some_method() here is called by an API request from app.py. Just in case if that is relevant.
I have made logger=True and then I get this message printed on the terminal for each emit call. Even with the one inside some_method()
emitting event "message" to my_room [/]
Does that mean message is actually sent? If yes, then why am I not getting it in the jquery at front-end.
This is what I am doing in html page
$(document).ready(function () {
// start up the SocketIO connection to the server
var socket = io.connect('http://localhost:5001/');
// this is a callback that triggers when the "message" event is emitted by the server.
socket.on('message', function(msg){
console.log(msg)
});
socket.emit('join', {room: 'my_room'});
});
Please try and install Redis and eventlet for asynchronous calls and to send messages from other modules. As described in the documentation then you can change your line in app.py to
socketio = SocketIO(app, cors_allowed_origins="*", message_queue='redis://', async_mode='eventlet')

How to setup SocketIO connection to NodeJS websocket from Python client?

I've got a simple nodejs server that runs socketio and I can do 2 way communication with the client from the HTML. Now I'm also trying to connect to the same nodejs websocket from a Python script, but not getting connected.
Simplified Node JS webserver:
var http = require('http').createServer(handler);
var fs = require('fs');
var io = require('socket.io')(http)
http.listen(8080);
function handler (req, res) {
fs.readFile(__dirname + '/public/index.html', function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
return res.end();
});
}
io.sockets.on('connection', function (socket) {
setInterval(() => {
socket.emit('temp', 'tempfromserver')
}, 1000)
});
The Python script that wants to connect to the nodejs with websocket:
import socketio
sio = socketio.Client();
sio.connect('http://localhost:8080')
#sio.event
def message(data):
print('received message')
def connect():
print('socketio connected')
Is it possible to connect with a websocket via socketio to the nodejs server? If so, what am I doing wrong?
End goal is to collect sensor data from python script on raspberry pi and send to nodejs server which in its turn saves it to DB and sends through to HTMl client. Perhaps there is a better setup to do this.
I believe I asked a similar question and got an answer. My setup was a little different, and used a web-socket, but it will at least allow you to connect between Node JS and Python, and then maybe you can build from there, Using Socket IO and aiohttp for data transfer between node JS and Python. The setup was also originally based off a html script on the node JS side, so that may help as well in comparing it to your setup.

Flask Client-Server Connection Timeout after given time

I have a flask app. I want the client-server connection to terminate if the server does not respond within a stipulated time (say 20 seconds). I read here that the session.permanent = True can be set. I am a bit unclear where this goes in the server side code (if at all this is the way??).
For simplicity I am including the minimal server side code I have. Actually the server is performing a File Read/Write operation and returning a result to the client.
from flask import Flask, session, app
from flask_restful import Api, Resource
from datetime import timedelta
app = Flask(__name__)
api = Api(app)
class GetParams(Resource):
def get(self):
print ("Hello.")
return 'OK'
api.add_resource(GetParams, '/data')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=5002)
Can anyone tell me what should I do here so that the connection between my client and server is terminated if the server does not respond i.e., send data back to the client within 20 seconds?
Long running tasks should be dealt with in a different design because, if you allow your server to keep a request alive for 50 minutes, you can't force user browser to do so.
I would recommend implementing the long running task as a thread that notifies the user once it's done.
For more readings about the problem statement and suggested solutions:
timeout issue with chrome and flask
long request time patterns
I believe that the only thing you need is to put your connexion statement in a try/except block. So that you will be able to handle any kind of connexion error.
Furthermore, a session timeout and a connexion fail/unreachable server are different things. A session timeout disconnect a user from a server which is here for too long (usually used to avoid a user to forgot a session open). Whereas when a server is unreachable the user isn't connected so there is no session timeout.
from flask import Flask, session, app
from flask_restful import Api, Resource
from datetime import timedelta
app = Flask(__name__)
api = Api(app)
class GetParams(Resource):
def get(self):
print ("Hello.")
return 'OK'
api.add_resource(GetParams, '/data')
if __name__ == '__main__':
try:
app.run(host='130.0.1.1', port=5002)
except:
print("unexcepted error")
you could qualify the received exception, but you'll have to read a bit of doc http://flask.pocoo.org/docs/1.0/quickstart/#what-to-do-if-the-server-does-not-start

(flask + socket.IO) Result of emit callback is the response of my REST endpoint

Just to give a context here, I'm a node.JS developer, but I'm on a project that I need to work with Python using Flask framework.
The problem is, when a client request to an endpoint of my rest flask app, I need to emit an event using socket.IO, and get some data from the socket server, then this data is the response of the endpoint. But I didn't figured out how to send this, because flask needs a "return" statement saying what is the response, and my callback is in another context.
Sample of what I'm trying to do: (There's some comments explaining)
import socketio
import eventlet
from flask import Flask, request
sio = socketio.Server()
app = Flask(__name__)
#app.route('/test/<param>')
def get(param):
def ack(data):
print (data) #Should be the response
sio.emit('event', param, callback=ack) # Socket server call my ack function
#Without a return statement, the endpoint return 500
if __name__ == '__main__':
app = socketio.Middleware(sio, app)
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
Maybe, the right question here is: Is this possible?
I'm going to give you one way to implement what you want specifically, but I believe you have an important design flaw in this, as I explain in a comment above. In the way you have this coded, your socketio.Server() object will broadcast to all your clients, so will not be able to get a callback. If you want to emit to one client (hopefully not the same one that sent the HTTP request), then you need to add a room=client_sid argument to the emit. Or, if you are contacting a Socket.IO server, then you need to use a Socket.IO client here, not a server.
In any case, to block your HTTP route until the callback function is invoked, you can use an Event object. Something like this:
from threading import Event
from flask import jsonify
#app.route('/test/<param>')
def get(param):
ev = threading.Event()
result = None
def ack(data):
nonlocal result
nonlocal ev
result = {'data': data}
ev.set() # unblock HTTP route
sio.emit('event', param, room=some_client_sid, callback=ack)
ev.wait() # blocks until ev.set() is called
return jsonify(result)
I had a similar problem using FastAPI + socketIO (async version) and I was stuck at the exact same point. No eventlet so could not try out the monkey patching option.
After a lot of head bangings it turns out that, for some reason, adding asyncio.sleep(.1) just before ev.wait() made everything work smoothly. Without that, emitted event actually never reach the other side (socketio client, in my scenario)

How to handle multiple clients asynchronously , on a web socket using python?

I'm basically building a visual trace route application. The trace route is basically done using a python code and the results are send to the HTML page in real time using web socket. I basically need to do long polling( the server receives one request, process it and sent a maximum of 30 replies to each client at regular or irregular intervals), as well as handle multiple clients. I basically manipulated the below code to work for my application. I found the code from Asynchronous Bottle Framework
from bottle import request, Bottle, abort
app = Bottle()
#app.route('/websocket')
def handle_websocket():
wsock = request.environ.get('wsgi.websocket')
if not wsock:
abort(400, 'Expected WebSocket request.')
while True:
try:
message = wsock.receive()
wsock.send("Your message was: %r" % message)
except WebSocketError:
break
from gevent.pywsgi import WSGIServer
from geventwebsocket import WebSocketHandler, WebSocketError
server = WSGIServer(("0.0.0.0", 8080), app,
handler_class=WebSocketHandler)
server.serve_forever()
It does work on a single request. When I issue the second one.. 'wsock.send()' fails... it shows socket dead error. Could someone guide me on, how to handle multiple clients as well. Like, should I spawn a different process for each client ? What if a client requests trace for one domain, and again(before the full result is provided to him) requests for another. Thanks in advice
Client side code :
<script type="text/javascript">
var ws = new WebSocket("ws://example.com:8080/websocket");
ws.onopen = function() {
ws.send("Hello, world");
};
ws.onmessage = function (evt) {
alert(evt.data);
};

Categories