I'm new to asynchronous programming in python and I'm trying to write a script that starts a websocket server, listens for messages, and also sends messages when certain events (e.g. pressing the 's' key) are triggered in a gtk window. Here's what I have so far:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import asyncio
import websockets
import threading
ws = None
async def consumer_handler(websocket, path):
global ws
ws = websocket
await websocket.send("Hello client")
while True:
message = await websocket.recv()
print("Message from client: " + message)
def keypress(widget,event):
global ws
if event.keyval == 115 and ws: #s key pressed, connection open
asyncio.get_event_loop().create_task(ws.send("key press"))
print("Message to client: key press")
def quit(widget):
Gtk.main_quit()
window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
window.connect("destroy", quit)
window.connect("key_press_event", keypress)
window.show()
start_server = websockets.serve(consumer_handler, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
wst = threading.Thread(target=asyncio.get_event_loop().run_forever)
wst.daemon = True
wst.start()
Gtk.main()
And here's the client webpage:
<!DOCTYPE html>
<html>
<head>
<title>Websockets test page</title>
<meta charset="UTF-8" />
<script>
var exampleSocket = new WebSocket("ws://localhost:8765");
function mylog(msg) {
document.getElementById("log").innerHTML += msg + "<br/>";
}
function send() {
mylog("Message to server: Hello server");
exampleSocket.send("Hello server");
}
exampleSocket.onopen = function (event) {
mylog("Connection opened");
};
exampleSocket.onmessage = function (event) {
mylog("Message from server: " + event.data);
}
</script>
</head>
<body>
<p id="log"></p>
<input type="button" value="Send message" onclick="send()"/>
</body>
</html>
Run the python code, then load the webpage in a browser, now any messages the browser sends show up in the python stdout, so far so good. But if you hit the 's' key in the gtk window, python doesn't send the message until another message is received from the browser (from pressing the 'Send message' button). I thought that await websocket.recv() was meant to return control to the event loop until a message was received? How do I get it to send messages while it's waiting to receive?
But if you hit the 's' key in the gtk window, python doesn't send the message until another message is received from the browser
The problem is in this line:
asyncio.get_event_loop().create_task(ws.send("key press"))
Since the asyncio event loop and the GTK main loop are running in different threads, you need to use run_coroutine_threadsafe to submit the coroutine to asyncio. Something like:
asyncio.run_coroutine_threadsafe(ws.send("key press"), loop)
create_task adds the coroutine to the queue of runnable coroutines, but fails to wake up the event loop, which is why your coroutine is only run when something else happens in asyncio. Also, create_task is not thread-safe, so calling it while the event loop itself is modifying the run queue could corrupt its data structures. run_coroutine_threadsafe has neither of these problems, it arranges for the event loop to wake up as soon as possible, and it uses a mutex to protect the event loop's data structures.
Related
I am setting up a web application using Flask and Flask-socketio for Websockets, using eventlet as recommended in the documentation.
The purpose is to create a coding platform where users can subscribe, join the current challenge and for each exercise can submit their own solution (a piece of code) which will then be assessed for correctness on the server via test cases (they write a function, given some input it must give a specific output, if it doesn't count zero score).
The issue is that some server-side computation in executing the uploaded code may take several seconds to complete before returning the output to the client. In this case, the client disconnects upon receiving the data from the server, after the long wait. You can see below some code which reproduces the issue, the time.sleep represents the long server side computation, which will cause the client to disconnect and reconnect.
Note: in the code I put 40 seconds of sleep, that is to have the client disconnect every time, for a smaller time (say between 10 and 20), sometimes it works fine and sometimes it disconnects.
Why is that happening? How can I fix it?
from flask import Flask
from flask_socketio import SocketIO
from flask_socketio import emit, disconnect
import time
import random
flask_app = Flask(__name__)
socketio = SocketIO(flask_app, async_mode='eventlet')
webpage = '''
<html>
<body>
<p id="demo">Some content</p>
<button type="button" onclick="do_on_server()">Do server-side computation</button>
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
function do_on_server(){
socket.emit('do_on_server', {});
}
socket.on('feedback', feedback);
function feedback(data){
document.getElementById("demo").innerHTML = data["msg"];
}
</script>
</body>
</html>
'''
#flask_app.route('/')
def index():
return webpage
#socketio.on('do_on_server')
def do_on_server(json):
print('starting computation')
#long computation
time.sleep(40)
print('done computing')
emit('feedback', {'msg': random.random()})
#socketio.on('connect')
def on_connect():
print('Connected')
#socketio.on('disconnect')
def on_disconnect():
print('Disconnecting')
if __name__ == '__main__':
socketio.run(flask_app)
Console output:
>python socket_timeout.py
Connected
starting computation
done computing
Disconnecting
Connected
There are many tutorials available for Flask and SocketIO, I could not find any for a simple threaded approach that I understood. But I did read and followed many of them.
I'd like to show my Python application on a web page using websockets so it's kind-of real-time monitoring. This is me trying to understand how to implement this.
The code I currently have is working for except the emit part. There does not seem to be any transfer of data. I'd like to know why.
The socket.on('tingeling' ... is not being triggered.
My Python code, mostly taken from https://codeburst.io/building-your-first-chat-application-using-flask-in-7-minutes-f98de4adfa5d
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = ''
socketio = SocketIO(app)
thread = None
def counter():
print("counter ding")
counting = 0
while True:
counting += 1
socketio.sleep(2)
socketio.emit('tingeling', counting, namespace='')
print(f"Counter: {counting}")
#app.route('/')
def sessions():
print('route')
return render_template('index.html')
#socketio.on('my event')
def connected(data):
print('my event')
#socketio.on('connect')
def starten():
print("connect")
socketio.emit('tingeling', 'start')
global thread
if thread is None:
print('thread ding')
thread = socketio.start_background_task(target=counter())
return render_template('index.html')
if __name__ == '__main__':
socketio.run(app, debug=True)
And my HTML template:
<!DOCTYPE html>
<html lang="en">
<head>
<title>fristi</title>
</head>
<body>
<h3 style='color: #ccc;font-size: 30px;'>waiting</h3>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.min.js"></script>
<script type="text/javascript">
var socket = io.connect('http://' + document.domain + ':' + location.port);
console.log('doet iets')
socket.on( 'connect', function() {
socket.emit( 'my event', {
data: 'User Connected'
})
})
socket.on('tingeling', function(msg) {
console.log('iets komt binnen')
console.log(msg)
})
</script>
</body>
</html>
My error is on the line: thread = socketio.start_background_task(target=counter())
There I reference the function to run as a background task but I use the notation with () and that is not allowed because is runs the function and does not provide the start_background_task with a reference to this function.
I have the following Python script which is using Flask-socketio
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
from time import sleep
app = Flask(__name__)
app.config['SECRET_KEY'] = 'P#ssw0rd'
socketio = SocketIO(app)
#app.route('/')
def index():
return render_template('index.html')
#socketio.on('connect')
def on_connect():
payload1 = 'Connected!!!'
payload2 = 'Doing thing 1'
payload3 = 'Doing thing 2'
emit('send_thing', payload1, broadcast=True)
sleep(2)
emit('send_thing', payload2, broadcast=True)
sleep(2)
emit('send_thing', payload3, broadcast=True)
if __name__ == '__main__':
socketio.run(app)
And here is the corresponding index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>SocketIO Python</title>
</head>
<body>
<div id="my-div"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.5/socket.io.js"></script>
<script>
(function init() {
var socket = io()
var divElement = document.getElementById('my-div')
socket.on('send_thing', function(payload) {
var dataElement = document.createElement('inner')
dataElement.innerHTML = payload
divElement.appendChild(dataElement)
})
})()
</script>
</body>
</html>
What I am trying to achieve is that when a client connects, it first says 'Connected!!!' and then 2 seconds later a new 'inner' element appears that says 'Doing thing 1' followed by 2 seconds later a new 'inner' element appears that says 'Doing thing 2' etc.
But what is happening is that when a client connects, it sends all 3 lines at the same time (after 4 seconds which is both sleep statements). This is the first time using SocketIO so I'm sure I've done something wrong.
When you use eventlet or gevent, the time.sleep() function is blocking, it does not allow any other tasks to run.
Three ways to address this problem:
Use socketio.sleep() instead of time.sleep().
Use eventlet.sleep() or gevent.sleep().
Monkey patch the Python standard library so that time.sleep() becomes async-friendly.
I have a CherryPy server running on a BeagleBone Black. Server generates a simple webpage and does local SPI reads / writes (hardware interface). The application is going to be used on a local network with 1-2 clients at a time.
I need to prevent a CherryPy class function being called twice, two or more instances before it completes.
Thoughts?
As saaj commented, a simple threading.Lock() will prevent the handler from being run at the same time by another client. I might also add, using cherrypy.session.acquire_lock() will prevent the same client from the running two handlers simultaneously.
Refreshing article on Python locks and stuff: http://effbot.org/zone/thread-synchronization.htm
Although I would make saaj's solution much simpler by using a "with" statement in Python, to hide all those fancy lock acquisitions/releases and try/except block.
lock = threading.Lock()
#cherrypy.expose
def index(self):
with lock:
# do stuff in the handler.
# this code will only be run by one client at a time
return '<html></html>'
It is general synchronization question, though CherryPy side has a subtlety. CherryPy is a threaded-server so it is sufficient to have an application level lock, e.g. threading.Lock.
The subtlety is that you can't see the run-or-fail behaviour from within a single browser because of pipelining, Keep-Alive or caching. Which one it is is hard to guess as the behaviour varies in Chromium and Firefox. As far as I can see CherryPy will try to serialize processing of request coming from single TCP connection, which effectively results in subsequent requests waiting for active request in a queue. With some trial-and-error I've found that adding cache-prevention token leads to the desired behaviour (even though Chromium still sends Connection: keep-alive for XHR where Firefox does not).
If run-or-fail in single browser isn't important to you you can safely ignore the previous paragraph and JavaScript code in the following example.
Update
The cause of request serialisation coming from one browser to the same URL doesn't lie in server-side. It's an implementation detail of a browser cache (details). Though, the solution of adding random query string parameter, nc, is correct.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import threading
import time
import cherrypy
config = {
'global' : {
'server.socket_host' : '127.0.0.1',
'server.socket_port' : 8080,
'server.thread_pool' : 8
}
}
class App:
lock = threading.Lock()
#cherrypy.expose
def index(self):
return '''<!DOCTYPE html>
<html>
<head>
<title>Lock demo</title>
<script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/qooxdoo/3.5.1/q.min.js'></script>
<script type='text/javascript'>
function runTask(wait)
{
var url = (wait ? '/runOrWait' : '/runOrFail') + '?nc=' + Date.now();
var xhr = q.io.xhr(url);
xhr.on('loadend', function(xhr)
{
if(xhr.status == 200)
{
console.log('success', xhr.responseText)
}
else if(xhr.status == 503)
{
console.log('busy');
}
});
xhr.send();
}
q.ready(function()
{
q('p a').on('click', function(event)
{
event.preventDefault();
var wait = parseInt(q(event.getTarget()).getData('wait'));
runTask(wait);
});
});
</script>
</head>
<body>
<p><a href='#' data-wait='0'>Run or fail</a></p>
<p><a href='#' data-wait='1'>Run or wait</a></p>
</body>
</html>
'''
def calculate(self):
time.sleep(8)
return 'Long task result'
#cherrypy.expose
def runOrWait(self, **kwargs):
self.lock.acquire()
try:
return self.calculate()
finally:
self.lock.release()
#cherrypy.expose
def runOrFail(self, **kwargs):
locked = self.lock.acquire(False)
if not locked:
raise cherrypy.HTTPError(503, 'Task is already running')
else:
try:
return self.calculate()
finally:
self.lock.release()
if __name__ == '__main__':
cherrypy.quickstart(App(), '/', config)
Looking for some code samples to solve this problem :-
Would like to write some code (Python or Javascript) that would act as a subscriber to a RabbitMQ queue so that on receiving a message it would broadcast the message via websockets to any connected client.
I've looked at Autobahn and node.js (using "amqp" and "ws" ) but cannot get things to work as needed. Here's the server code in javascript using node.js:-
var amqp = require('amqp');
var WebSocketServer = require('ws').Server
var connection = amqp.createConnection({host: 'localhost'});
var wss = new WebSocketServer({port:8000});
wss.on('connection',function(ws){
ws.on('open', function() {
console.log('connected');
ws.send(Date.now().toString());
});
ws.on('message',function(message){
console.log('Received: %s',message);
ws.send(Date.now().toString());
});
});
connection.on('ready', function(){
connection.queue('MYQUEUE', {durable:true,autoDelete:false},function(queue){
console.log(' [*] Waiting for messages. To exit press CTRL+C')
queue.subscribe(function(msg){
console.log(" [x] Received from MYQUEUE %s",msg.data.toString('utf-8'));
payload = msg.data.toString('utf-8');
// HOW DOES THIS NOW GET SENT VIA WEBSOCKETS ??
});
});
});
Using this code, I can successfully subscribe to a queue in Rabbit and receive any messages that are sent to the queue. Similarly, I can connect a websocket client (e.g. a browser) to the server and send/receive messages. BUT ... how can I send the payload of the Rabbit queue message as a websocket message at the point indicated ("HOW DOES THIS NOW GET SENT VIA WEBSOCKETS") ? I think it's something to do with being stuck in the wrong callback or they need to be nested somehow ...?
Alternatively, if this can be done easier in Python (via Autobahn and pika) that would be great.
Thanks !
One way to implement your system is use python with tornado.
Here the server:
import tornado.ioloop
import tornado.web
import tornado.websocket
import os
import pika
from threading import Thread
clients = []
def threaded_rmq():
connection = pika.BlockingConnection(pika.ConnectionParameters("localhost"));
print 'Connected:localhost'
channel = connection.channel()
channel.queue_declare(queue="my_queue")
print 'Consumer ready, on my_queue'
channel.basic_consume(consumer_callback, queue="my_queue", no_ack=True)
channel.start_consuming()
def consumer_callback(ch, method, properties, body):
print " [x] Received %r" % (body,)
for itm in clients:
itm.write_message(body)
class SocketHandler(tornado.websocket.WebSocketHandler):
def open(self):
print "WebSocket opened"
clients.append(self)
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print "WebSocket closed"
clients.remove(self)
class MainHandler(tornado.web.RequestHandler):
def get(self):
print "get page"
self.render("websocket.html")
application = tornado.web.Application([
(r'/ws', SocketHandler),
(r"/", MainHandler),
])
if __name__ == "__main__":
thread = Thread(target = threaded_rmq)
thread.start()
application.listen(8889)
tornado.ioloop.IOLoop.instance().start()
and here the html page:
<html>
<head>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script>
$(document).ready(function() {
var ws;
if ('WebSocket' in window) {
ws = new WebSocket('ws://localhost:8889/ws');
}
else if ('MozWebSocket' in window) {
ws = new MozWebSocket('ws://localhost:8889/ws');
}
else {
alert("<tr><td> your browser doesn't support web socket </td></tr>");
return;
}
ws.onopen = function(evt) { alert("Connection open ...")};
ws.onmessage = function(evt){
alert(evt.data);
};
function closeConnect(){
ws.close();
}
});
</script>
</head>
<html>
So when you publish a message to "my_queue" the message is redirects to all web page connected.
I hope it can be useful
EDIT**
Here https://github.com/Gsantomaggio/rabbitmqexample you can find the complete example