Flask-socketio doesn't recieve message from client - python-3.x

I'm trying to write a basic Socket.io program where the python client (python-socketio[asyncio_client] 4.6.0) emits a single string message to the flask server (with Flask-SocketIO 4.3.1 and eventlet).
The client appears to connect and send the message properly, but there is no output seen at the Flask server.
Server code:
from flask import Flask
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
#socketio.on('connect')
def test_connect():
print('connected')
#socketio.on('disconnect')
def test_disconnect():
print('Client disconnected')
#socketio.on('message')
def handle_message(msg):
print('Recieved',msg)
#socketio.on('json')
def handle_json(json):
print(str(json))
if __name__ == '__main__':
socketio.run(app,debug=True)
Client code:
import asyncio
import socketio
sio = socketio.AsyncClient()
#sio.event
def connect():
print('connection established')
#sio.event
def disconnect():
print('disconnected from server')
async def main():
await sio.connect('http://localhost:5000')
await sio.emit('message',data='detection')
print('message sent')
await sio.disconnect()
if __name__ == '__main__':
asyncio.run(main())
Server output:
PS C:\Users\daksh\sih\sihPython> python .\test_socketio.py
* Restarting with stat
* Debugger is active!
* Debugger PIN: 101-561-255
(16664) wsgi starting up on http://127.0.0.1:5000
(16664) accepted ('127.0.0.1', 59497)
connected
127.0.0.1 - - [23/Jul/2020 20:38:42] "GET /socket.io/?transport=polling&EIO=3&t=1595516920.71801 HTTP/1.1" 200 367 0.004934
Client disconnected
127.0.0.1 - - [23/Jul/2020 20:38:42] "GET /socket.io/?transport=websocket&EIO=3&sid=88790300120f4b899e019ae7cc16ee87&t=1595516922.7757218 HTTP/1.1" 200 0 0.010027
Client output:
PS C:\Users\daksh\sih\sihPython> python .\socketio-client.py
connection established
message sent
The print statement from handle_message() is missing in the server output.
I've gone through multiple tutorials online, and I've tried it with and without namespaces. Have not been able to figure out what's wrong.
Any help is appreciated.
(I'm using Python 3.8.3 on Windows 10)
UPDATE: It works if I change the client code to use socketio.Client() instead of AsyncClient(), however I need the client to connect using AsyncClient.

The problem is that your async client is obviously asynchronous, you can't just send and exit because you don't give the background tasks that support the Socket.IO protocol time to do their thing.
Here is a more robust version of the client that lets the event go through before exiting:
import asyncio
import socketio
sio = socketio.AsyncClient()
#sio.event
async def connect():
print('connection established')
await sio.emit('message',data='detection', callback=done)
print('message sent')
#sio.event
def disconnect():
print('disconnected from server')
async def done():
await sio.disconnect()
async def main():
await sio.connect('http://localhost:5000')
await sio.wait()
if __name__ == '__main__':
asyncio.run(main())
The trick here is to use a callback on the emit. When the callback is invoked you are sure the message was delivered, so at that point it is safe to disconnect.

Related

Is it possible for the same client to request 2 or more topics to a websocket server without needing to disconnect?

I need only 1 client to connect to my websocket server, and when it sends a message with "pattern 1" my server will send all messages to it for that pattern. If he now needs a "pattern 2" he sends the message to the server, and the server needs to keep sending the "pattern 1" and start sending the "pattern 2" messages. Without disconnecting from my server. It's possible?
When I use the while loop, the server always gets stuck on "pattern 1" and when I take the while True, it always needs messages to enter the "pattern 1" function again.
What I need is, if you received the "pattern 1" message, stay in the function and always receive the updates, if you receive the "pattern 2" message from the client, enter the "pattern 2" function and continue to receive "pattern 1" and "pattern 2" at the same time.
app.py
import websockets
import asyncio
import warnings
import json
import datetime
import concurrent.futures
warnings.filterwarnings("ignore", category=DeprecationWarning)
PORT = 7890
print("Server listening on Port: {}".format(PORT))
async def pattern_1(websocket):
while True:
await websocket.send(str("Pattern 1"))
async def pattern_2(websocket):
while True:
await websocket.send(str("Pattern 2"))
async def handler(websocket):
try:
async for message in websocket:
message = message.replace("\'", "\"")
event = json.loads(message)
print(event)
if (event['user_id'] == 1) and (event['pattern'] == "1"):
await asyncio.create_task(pattern_1(websocket))
if (event['user_id'] == 1) and (event['pattern'] == "2"):
await asyncio.create_task(pattern_2(websocket))
except Exception as e:
print("Error: {}".format(e))
# finally:
async def main():
# Start the server
async with websockets.serve(handler, "localhost", PORT):
await asyncio.Future()
asyncio.run(main())
client.py
import websockets
import asyncio
import warnings
import json
warnings.filterwarnings("ignore", category=DeprecationWarning)
async def Candle_Pattern():
url = "ws://127.0.0.1:7890"
#connect to the server
async with websockets.connect(url) as ws:
await ws.send(json.dumps({"user_id":1, "pattern":"1"}))
#I need to connect to pattern 2 also
await ws.send(json.dumps({"user_id":1, "pattern":"2"}))
while True:
msg = await ws.recv()
print(msg)
await asyncio.sleep(2)
async def main():
await asyncio.gather(
Candle_Pattern())
asyncio.run(main())

Python flask mqtt socketio and subscribing on startup

When starting a flask, mqtt and socketio app how exactly can I subscribe to topics when the app starts but before the browser connects for the first time?
I had assumed I could use before_first_request but that only gets called on first request, also assume I could use mqtt.on_connect but that is never called when using socket io and if I subscribe before starting the app in __main__ then I get two threads subscribed rather than one.
#!/usr/bin/env python3
import json
from flask import Flask, render_template
from flask_mqtt import Mqtt
from flask_socketio import SocketIO
from flask_bootstrap import Bootstrap
# Flask App
app = Flask(__name__)
app.config['MQTT_BROKER_URL'] = '192.168.109.135'
print('Configured MQTT IP Address: ' + app.config['MQTT_BROKER_URL'])
mqtt = Mqtt(app)
socketio = SocketIO(app)
#app.route("/")
def roots():
return render_template('index.html')
#app.route('/mqtt')
def mqtt_index():
return render_template('mqtt.html')
#socketio.on('subscribe')
def handle_subscribe(json_str):
print('Subscribe ' + json_str)
#socketio.on('unsubscribe_all')
def handle_unsubscribe_all():
print('Socket IO unsubscribe all')
mqtt.unsubscribe_all()
#socketio.on('connect')
def handle_connect():
print('Socket IO Connected')
#socketio.on('discconnect')
def handle_connect():
print('Socket IO Discconnect')
#mqtt.on_connect()
def handle_mqtt_connect():
print('MQTT Connected')
#mqtt.on_message()
def handle_mqtt_message(client, userdata, message):
print('MQTT Message')
data = dict(
topic=message.topic,
payload=message.payload.decode(),
qos=message.qos,
)
print(mqttresponse)
socketio.emit('mqtt_message', data=data)
#mqtt.on_log()
def handle_logging(client, userdata, level, buf):
print('MQTT log', level, buf)
pass
#app.before_first_request
def before_first_request():
print("before_first_request")
mqtt.subscribe('homeassistant/+/+/set', 0)
if __name__ == "__main__":
# Main http web server for firmware downloading and the main frontend.
socketio.run(app, host='0.0.0.0', port='6080', use_reloader=True)
Any ideas where the mqtt.subscribe should go so it subscribes to the topics I want before the first connect to the webserver?
I found there is already a related issue which is the on_connect callback doesn't get called, and a question on here too. So this is a duplicate.
Flask MQTT on_connect is never called when used with SocketIO
https://github.com/stlehmann/Flask-MQTT/issues/82

How to send data in infinite loop from server to client throught python socket.io?

I want to send data (like endless stream) from one vds machine to another. I've read that it's possible to do with python-socket.io. First of all, I try to do it on my laptop (server script runs in one terminal, client - in another). And send numbers from "server" to "client" in infinite loop. I want to get data on client side in real-time. (Server sent "1", client got "1", etc) But, when I run both scripts I see that server is sending data and client gets nothing. Only when I stop (ctrl+c) server, all sent data is printing on client's terminal.
How to fix code to get real-time connection?
server.py
import eventlet
import socketio
import time
sio = socketio.Server()
app = socketio.WSGIApp(sio)
#sio.event
def connect(sid, environ):
print('connect ', sid)
my_message(sid, "Client connected")
# f(sid)
#sio.event
def my_message(sid, data):
sio.send(data)
print('Send message ', data)
#sio.event
def disconnect(sid):
print('disconnect ', sid)
#sio.on('subscribe_to_data')
def subscribe(sid, data):
counter = 0
while True:
sio.send(counter)
print('Send message from server ', counter)
counter += 1
# my_message(sid, i)
time.sleep(1)
eventlet.wsgi.server(eventlet.listen(('', 5000)), app)
client.py
import socketio
sio = socketio.Client()
#sio.event
def connect():
print('connection established')
sio.emit('subscribe_to_data', "I want to subscribe")
#sio.event
def message(data):
print('message received with ', data)
#sio.event
def disconnect():
print('disconnected from server')
sio.connect('http://localhost:5000')
sio.wait()
A better implementation will be to maintain a list of connected clients and do something like
while sid in CONNECTED_CLIENTS:
pass
Also instead of time.sleep() use sio.sleep()

How to call async code from sync code in another thread?

I'm making a Discord bot which send PM when it receive a Github hook.
It use Discord.py and BottlePy, the last one run in a dedicated thread.
Because both frameworks have a blocking main loop.
In BottlePy callback, I call some Discord.py async code.
I wasn't knowing what is Python async, this appear to be complicated when mixed with synchronous code...
Here's the full source code :
import discord
import bottle
import threading
import asyncio
client = discord.Client()
server = bottle.Bottle()
async def dm_on_github_async(userid,request):
print("Fire Discord dm to "+str(userid))
global client
user = client.get_user(userid)
if (user==None):
abort(500, "User lookup failed");
dm_channel = user.dm_channel
if (dm_channel==None):
dm_channel = await user.create_dm()
if (dm_channel==None):
abort(500, "Fail to create DM channel");
print("DM channel is "+str(asyncio.wait(dm_channel.id)))
await dm_channel.send("There's a Github shot !")
await dm_channel.send(str(request.body))
return
#server.post("/dm_on_github/<userid:int>")
def dm_on_github(userid):
return asyncio.run(dm_on_github_async(userid,bottle.request))
#client.event
async def on_ready():
print('We have logged in as {0.user} '.format(client))
##client.event
#async def on_message(message):
# if message.author == client.user:
# return
#
# if message.content.startswith('$hello'):
# await message.channel.send('Hello!')
# # This sample was working very well
class HTTPThread(threading.Thread):
def run(self):
global server
server.run(port=8080)
server_thread = HTTPThread()
print("Starting HTTP server")
server_thread.start()
print("Starting Discord client")
client.run('super secret key')
print("Client terminated")
server.close()
print("Asked server to terminate")
server_thread.join()
print("Server thread successful join")
I want that my Python bot send the body of the HTTP request as PM.
I get a RuntimeError: Timeout context manager should be used inside a task at return asyncio.run(dm_on_github_async(userid,bottle.request)).
I think I'm not doing this mix in the right way...
After a night, I found the way.
To call async code from sync code in another thread, we ask the loop (here this one from Discord.py) to run the callback with asyncio.run_coroutine_threadsafe(), this return a Task() and we wait for his result with result().
The callback will be run in the loop thread, in my case I need to copy() the Bottle request.
Here's a working program (as long you don't mind to stop it...) :
import discord
import bottle
import threading
import asyncio
client = discord.Client()
server = bottle.Bottle()
class HTTPThread(threading.Thread):
def run(self):
global server
server.run(port=8080)
async def dm_on_github_async(userid,request):
user = client.get_user(userid)
if (user==None):
abort(500, "User lookup failed");
dm_channel = user.dm_channel
if (dm_channel==None):
dm_channel = await user.create_dm()
if (dm_channel==None):
abort(500, "Fail to create DM channel");
# Handle the request
event = request.get_header("X-GitHub-Event")
await dm_channel.send("Got event "+str(event))
#await dm_channel.send(str(request.body)) # Doesn't work well...
return
#server.post("/dm_on_github/<userid:int>")
def dm_on_github(userid):
request = bottle.request
asyncio.run_coroutine_threadsafe(dm_on_github_async(userid,request.copy()),client.loop).result()
#client.event
async def on_ready():
print('We have logged in as {0.user} '.format(client))
# Wait for the old HTTP server
if hasattr(client,"server_thread"):
server.close()
client.server_thread.join()
client.server_thread = HTTPThread()
client.server_thread.start()
##client.event
#async def on_message(message):
# if message.author == client.user:
# return
#
# if message.content.startswith('$hello'):
# await message.channel.send('Hello!')
print("Starting Discord client")
client.run('super secret key')
print("Client terminated")
server.close()
print("Asked server to terminate")
server_thread.join()
print("Server thread successful join")

Cannot create with async sub process for each socket connection with Python 3.5 asyncio

I am trying to create a socket server with asyncio where I would asynchronous listen for connection and get each connections incomming message. Howeven I cannot get it working.
Here is my code for server:
import asyncio
import socket, sys
from concurrent.futures import ProcessPoolExecutor
def print_output(csock, loop):
while 1:
print('gotcha')
msg = csock.recv(1024)
if not msg:
pass
else:
print ("Client send: " + msg)
def s_listen(loop):
while True:
(csock, adr) = sock.accept()
print('start another process')
asyncio.ensure_future(loop.run_in_executor(executor, print_output, csock, loop))
print('done')
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #reuse tcp
sock.bind(('', 12345))
sock.listen(5)
executor = ProcessPoolExecutor()
loop = asyncio.get_event_loop()
listener = asyncio.ensure_future(loop.run_in_executor(executor,s_listen,loop))
print('here')
While this is my code for client
import socket, sys
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('', 12345))
sock.send(b"Hello I'm Client.\r\n")
I can get the function "s_listen" running asynchronously but the code got blocked by "print_output" function.
I am new to asyncio, can anyone help?
Thanks!
Asyncio provides a coroutine-based API called stream to manage socket clients and servers. Here's a modified version of the tcp echo server from the user documentation:
import asyncio
# Client handler
async def handle_echo(reader, writer):
while not reader.at_eof():
data = await reader.read(100)
message = data.decode().strip()
print('Client sent: ' + message)
writer.close()
# Start the server
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_echo, '', 12345, loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
You can test this example with the netcat client:
# Client
$ ncat localhost 12345
hello,
world!
# Server
$ python3.5 server.py
Serving on ('0.0.0.0', 12345)
Client sent: hello,
Client sent: world!

Resources