How to refresh front page after back-end task completed - python

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.

Related

Flask-SocketIO running app but not firing events

So I'm building a simple Flask social media application that uses Flask's session ids. I want to remove a user's session id whenever they leave the page so that they have to re-login. I did a little research and found out that Flask-SocketIO allows you to do that through the 'connect' and 'disconnect' events. However, when I run the app, and I open up the site/log on to the page, nothing happens. I don't get any output from the connect event nor the disconnect event when I close the tab. Here's the full code.
# Starting/Initialisation of App
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = SECRET_KEY
socket_io = SocketIO(app=app, logger=True, engineio_logger=True)
db = SQLAlchemy(app)
# Socket IO Events
#socket_io.on('connect')
def on_connect():
print("Connected!")
#socket_io.on('disconnect')
def on_disconnect():
# TODO Fix this socket io disconnect event. It's not working for some reason. Just figure things out with socketio
print("Client Disconnected")
print("YOOOOOOOOOO")
session.pop('id')
#socket_io.on('message')
def on_message():
print("Exit message")
# App Routes
# A page where a user can login to an account
#app.route('/', methods=['GET', 'POST'])
def index():
# If user submits form
if request.method == "POST":
# Get response and user's Account object from db
# if query does not return None aka user is in database
# Check if response's password is the same as the user's password
# If it is, redirect to homepage
# Else if not, return an "invalid password" error
form = request.form # User Response
user = Account.query.filter_by(email=str(request.form['email'])).first() # Query for user
if user is not None: # Validating query
if str(form['password']) == user.password: # Checking password
session['id'] = user.id
return redirect('/home')
else:
return "Invalid password"
else:
return "Invalid email or password"
else:
return render_template('index.html')
# Run App
if __name__ == '__main__':
socket_io.run(app)
I'm don't receive any errors or whatsoever so I'm really confused.
Below is my pip freeze
bidict==0.21.2
click==8.0.1
colorama==0.4.4
Flask==2.0.1
Flask-SocketIO==5.1.0
Flask-SQLAlchemy==2.5.1
greenlet==1.1.0
h11==0.12.0
itsdangerous==2.0.1
Jinja2==3.0.1
MarkupSafe==2.0.1
python-engineio==4.2.0
python-socketio==5.3.0
simple-websocket==0.2.0
SQLAlchemy==1.4.22
Werkzeug==2.0.1
wsproto==1.0.0
Would really appreciate it if anyone has any solutions to this. Thanks in advance.
Few things to consider here:
print statements to stdout won't necessarily show in your output with Flask, I believe stderr does, so you might want to try either that or the logger, e.g.:
print('Connected!', file=sys.stderr)
app.logger.info("Connected!")
Are you sure you have the right version of the client? I don't believe the client is included with Flask-SocketIO, and Flask-SocketIO isn't compatible with the latest client version (4). You should be using client version 3.1.3 (https://socket.io/docs/v3/). This is the version of the SocketIO library you include (this example truncated from the aforementioned SocketIO docs and updated to show an alert box once connected so you can be sure it's worked):
<html>
<body>
...
<script src="https://cdn.socket.io/3.1.3/socket.io.min.js" integrity="sha384-cPwlPLvBTa3sKAg ddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh" crossorigin="anonymous"></script>
<script type='text/javascript'>
const socket = io("ws://localhost:5000");
socket.on("connect", () => {
// either with send()
alert("You're connected!");
socket.send("Hello!");
// or with emit() and custom event names
socket.emit("salutations", "Hello!", { "mr": "john" }, Uint8Array.from([1, 2, 3, 4]));
});
</script>
</body>
</html>
does your web console in your browser show any errors? Are you sure the client side code is correct and you're connected?
Viewing the Javascript console varies from browser to browser, but will show you any errors encountered in your Javascript. In Chrome for example, you click the 3 dots to the far right of your address bar, then More Tools -> Developer Tools. This is cruicial in debugging Javascript issues.
Are you sure you have included socket.io in client side means in html. you can include this script in your head in html to include it.
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js" integrity="sha512-q/dWJ3kcmjBLU4Qc47E4A9kTB4m3wuTY7vkFJDTZKjTs8jhyGQnaUrxa0Ytd0ssMZhbNua9hE+E7Qv1j+DyZwA==" crossorigin="anonymous" defer></script>
Then you need to define socket in your javascript file or script tag.
<script type="text/javascript">
socket = io.connect(document.href );
socket.on('connect', function() {
// do something when user connects
});
socket.emit("message",data) // data is sent to flask
</script>
for more information, refer to the documentation.
https://flask-socketio.readthedocs.io/en/latest/

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.

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>

Server sent events with Flask and Tornado

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

Categories

Resources