Server sent events with Flask and Tornado - python

I have been playing around with sending server sent events with Flask and Tornado. I took a look at this blog article:
https://s-n.me/blog/2012/10/16/realtime-websites-with-flask/
I decided to try writing my own Flask app to send server sent events as an exercise. Here is the code for my Flask app called sse_server.py:
#! /usr/bin/python
from flask import Flask, request, Response, render_template
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
app = Flask(__name__)
def event_stream():
count = 0
while True:
print 'data: {0}\n\n'.format(count)
yield 'data: {0}\n\n'.format(count)
count += 1
#app.route('/my_event_source')
def sse_request():
return Response(
event_stream(),
mimetype='text/event-stream')
#app.route('/')
def page():
return render_template('index.html')
if __name__ == '__main__':
print "Please open a web browser to http://127.0.0.1:5000."
# Spin up the app
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()
In my templates folder, I have a simple index.html page:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test</title>
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<script type="text/javascript" src="../static/sse_client.js"></script>
</head>
<body>
<h3>Test</h3>
<ul id="output">
</ul>
</body>
</html>
In my static folder, I have a file called sse_client.js:
var queue = [];
var interval = setInterval(function(){addItem()}, 1000);
function addItem(){
if(queue.length > 0){
var item = queue[0];
queue.shift();
$('#output').append(item);
}
}
$(document).ready(
function() {
var sse = new EventSource('/my_event_source');
console.log('blah');
sse.onmessage = function(event) {
console.log('A message has arrived!');
var list_item = '<li>' + event.data + '</li>';
console.log(list_item);
queue.push(list_item);
};
})
Basically, my app's structure is
sse/
sse_server.py
static/
sse_client.js
templates/
index.html
The app displays the index page, but the data is not getting streamed to it. I have no idea what I am doing wrong. I think I need another set of eyes on this. I'm sure it's something really minor and stupid.

Tornado's WSGIContainer does not support streaming responses from wsgi apps. You can either use Flask with a multi-threaded or greenlet-based wsgi server, or use Tornado's native RequestHandler interface, but not when you're combining Flask and Tornado with WSGIContainer.
Combining Flask and Tornado is usually not a good idea; see https://github.com/mitsuhiko/flask/issues/986

To use the url "../static/sse_client.js" you need your webserver or your Flask app to serve the static JavaScript file. From the Flask docs:
To generate URLs for static files, use the special 'static' endpoint
name:
url_for('static', filename='style.css')
The file has to be stored on the filesystem as static/style.css.
Read more

Related

Flask-SocketIO not receiving events on server

I'm currently learning Flask and have recently found out about Flask-SocketIO. I've learned, that the module is based on events so that the client side can communicate with the server side, so I tried doing that. But for some reason, the events I send from the client side, don't make it to the server. I've tried fixing it for a few hours but I don't understand what is wrong with my code. Thanks for helping me!
main.py
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
#app.route("/", methods=["GET", "POST"])
def home():
return render_template("index.html")
#socketio.on('my event')
def handle_my_custom_event(json):
print('received json: ' + str(json))
if __name__ == "__main__":
socketio.run(app, debug=True)
/templates/index.html
<!DOCTYPE html>
<html lang="en">
<h1> Chat room</h1>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'}); #Creating event when connected
});
</script>
</html>
Any time things don't go the way you think they should go, you have to look for clues left in the logs. In this case that means looking at the output of the Flask process, and the browser console.
The Flask process does not show any errors, but it does show that there was no Socket.IO connection attempted. So really this isn't a problem about events not being received, but connections don't being made.
The browser console shows this:
Uncaught SyntaxError: Private field '#Creating' must be declared in an enclosing class
You see the problem? You are using # to start a comment. That should have been a // in JavaScript.

How to refresh front page after back-end task completed

I'm design a web application with python flask, the processing flow is like:
User choose a specified a URL, and ask server site to do some time-consuming task;
The time-consuming task is then running in a new thread;
After #2 is done, server will update some info regarding the URL, I need to refresh the page in #1
I don't know how to notify the "completion" status to front-end so as to refresh the page.
Anyone can help me out?
Thanks
Using flask-socketio would probably work for what you're asking. It establishes a connection between the front-end and back-end that lets the backend emit a message and the front-end registers a callback that fires when it receives the event.
$ pip install flask-socketio
app.py
import json
import time
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
#app.route('/')
def hello(name=None):
return render_template('index.html')
#socketio.on('long-running-event')
def handle_my_custom_event(input_json):
time.sleep(5)
emit('processing-finished', json.dumps({'data': 'finished processing!'}))
if __name__ == '__main__':
socketio.run(app)
templates/index.html
<html>
<body>
<button id="long-running-operation">Process data</button>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
var socket = io.connect('http://' + document.domain + ':' + location.port);
let button = document.getElementById('long-running-operation');
button.addEventListener('click', () => {
socket.emit('long-running-event', {data: 'process data'});
});
socket.on('processing-finished', function (data) {
alert(JSON.parse(data).data)
});
</script>
</body>
</html>
Then run the app with python app.py. Visit the app in the browser at http://127.0.0.1:5000/ and click the button. The button will send the 'long-running-event' request to flask and flask processes it by sleeping for 5 seconds to simulate a long running process. Then flask emits the 'processing-finished message and javascript fires the callback registered to listen for that event by alerting the message that flask sent.

Unable to pass button event to Flask SocketIO to external python program

I have need to separate my python Flask web app and running code communicating via socketio. I'm able to get messages up from the external python program to the web, but I'm unable to get events from the web detected by the python program. In effect, I'd like when a user presses a button on a web page, for the external python code to print to terminal a hello world message. In the console for this html, I'm definitely seeing "Button pressed".
<html>
<head>
<title>Listener</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function() {
var socket = io.connect();
$('#mybutton').on('click', function() {
socket.emit('my event', 'yodle');
console.log('Button pressed');
});
});
</script>
<button id="mybutton">Push Me!</button>
</body>
</html>
This is my basic Flask webserver code, which is working:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'froggy'
app.debug = True
socketio = SocketIO(app, message_queue='redis://')
#app.route("/")
def index():
return render_template("index.html")
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0')
This is my separate running program, which is not printing anything to terminal when the button is pressed:
from flask_socketio import SocketIO
socketio = SocketIO(message_queue='redis://', host='0.0.0.0')
def my_function_handler(data):
print("Hello World")
if __name__ == '__main__':
while True:
socketio.on_event('my event', my_function_handler)
Can anyone point me to where I'm going wrong? Many thanks in advance!
You are trying to do something that is not supported. External processes can only emit, they are not recipients. If you need to emit and receive in the external process, then I recommend that you move the Socket.IO server entirely to this process.
Apparently this behavior is not supported in socketIO according to Miguel's answer, so I'm posting my particular solution to the problem if anyone is interested. I spent a lot of time Googling, Stack Overflowing, and generally falling down rabbit holes. What I ended up doing was that as I was using redis already, I just used it to pass messages between processes. There are a ton of general downsides to this approach, but it fits my needs nicely, so here's what I did. For index.html:
<html>
<head>
<title>Listener</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.8/socket.io.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function() {
var socket = io.connect();
$('#mybutton').on('click', function() {
socket.emit('button event');
});
});
</script>
<button id="mybutton">Push Me!</button>
</body>
</html>
Server code:
from flask import Flask, render_template
from flask_socketio import SocketIO
import redis
app = Flask(__name__)
app.config['SECRET_KEY'] = 'froggy'
app.debug = True
socketio = SocketIO(app, message_queue='redis://')
r = redis.Redis("localhost")
r.set('button', 'not pressed')
#app.route("/")
def index():
return render_template("index.html")
#socketio.on('button event')
def handleMessage():
r.set('button', 'pressed')
if __name__ == '__main__':
socketio.run(app, host='0.0.0.0')
Separate running process:
import redis
r = redis.Redis("localhost")
if __name__ == '__main__':
while True:
if r.get('button') == 'pressed':
print("Button pressed!")
r.set('button', 'not pressed')

In Flask, can I show one template while a function runs and redirect to another once the function is complete?

Basically I want to show a loading page while a time-consuming process takes place and then redirect to my complicated other page.
While not impossible to achieve, I recommend using javascript for this task.
Here is a small example. First lets write a very simple flask server, with one very slow endpoint.
from flask import Flask, render_template, jsonify
app = Flask(__name__)
#app.route("/")
def hello():
return render_template('redirect.html')
#app.route("/done")
def done():
return "Done!"
#app.route("/slow")
def slow():
import time
time.sleep(5)
return jsonify("oh so slow")
if __name__ == "__main__":
app.run()
Now, we can make a beautiful user experience by invoking the endpoint from javascript instead. Save it as templates/redirect.html as per usual.
<html>
<head>
<script>
function navigate() {
window.location.href = 'done'; // redirect when done!
}
fetch('slow').then(navigate); // load the slow url then navigate
</script>
</head>
<body>
Loading... <!-- Display a fancy loading screen -->
</body>
</html>

Why does my generator block Flask from sending SSE responses?

I am trying to use Flask to serve an SSE request, but my client only receives the events after my generator function has stopped / the connection is closed.
Here is the simplest reproduction I have been able to produce to demonstrate this:
#!/usr/bin/env python
from flask import Flask, Response
from time import sleep
def stream():
n = 10
while n > 0:
yield "data: hi\n\n"
sleep(0.5)
n = n - 1
app = Flask(__name__)
#app.route("/events")
def streamSessionEvents():
return Response(
stream(),
mimetype="text/event-stream"
)
app.run(debug=True, threaded=True)
Here is my test client:
<!doctype html>
<html>
<head>
<script>
var source = new EventSource(
"/events"
);
source.onmessage = function(event)
{
console.log(event);
};
</script>
</head>
<body>
</body>
</html>
The stream() generator will produce ten events and then return (I've deliberately done this to demonstrate the problem, ideally the generator would keep going forever), at which point the connection is dropped. The client page logs nothing until this point, then it spits out all ten events (if I dont have the counter variable in stream() then the page never gets any events).
I havent used Python or Flask a great deal and this has me very stuck, I cant see what I'm doing differently to other examples around the net. Any help very much appreciated.
Two things might interfere:
You have debug set to True, which installs middleware (specifically the Werkzeug debugger) that may break streaming.
From the Flask streaming patterns documentation:
Note though that some WSGI middlewares might break streaming, so be careful there in debug environments with profilers and other things you might have enabled.
However, using either curl or Chrome on your test code with Flask 0.10.1 and Werkzeug 0.9.4 I see the data: hi responses come streaming through properly, regardless of the debug flag setting. In other words, your code is working correctly with the most recent versions of the Flask stack.
EventSource streams are subject to same-origin policy limits. If you are not loading the HTML page from the same host and port, the request to your Flask server will be denied.
Adding the test page source in the Flask server at a separate route works for me:
#app.route('/')
def index():
return '''\
<!doctype html>
<html>
<head>
<script>
var source = new EventSource(
"/events"
);
source.onmessage = function(event)
{
console.log(event);
};
</script>
</head>
<body>
</body>
</html>
'''

Categories

Resources