I added the Flask-SocketIO library to my Flask application to handle live connections and testing it with cookies being sent to the backend after the user completes a specific action. This is in case the user closes their browser or the webpage before issuing another HTTP request to the URL. I found that the browser won't save the cookie being set with document.cookie in javascript until it receives the cookie back from the server in response. Since adding the WebSocket and testing it in the development environment, the socket does emit the cookie correctly. However when I attempt to use ctrl-C in the terminal, it won't fire the signal, and even after I close the terminal and the browser/webpage, localhost is still listening on the port. I know how to manually kill the process with taskkill but my concern is that the socket connection with the client is left open after they had already closed the webpage/browser. I'm assuming this can be bad for performance on the backend and possibly cause some security risks, though I'm not sure. This might be because I'm not handling the socket connection and emitted message on the backend correctly but that's because the example code in the documentation was difficult to follow. Here is the documentation: https://flask-socketio.readthedocs.io/en/latest/.
Can someone please answer this question?
__init__.py:
from flask import Flask, url_for, current_app
from flask_socketio import SocketIO
csp = {
"default-src": [
"'self'",
'https://www.youtube.com'
],
'script-src': [ "'self'",
'https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js',
'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js'],
'style-src': ["'self'",
'https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css'],
'img-src': ["'self'",
'data:',
'https://i.ytimg.com' ],
'font-src': ['data:']
}
config = {
"CACHE_TYPE": "simple",
"DEBUG": True,
"SQLALCHEMY_DATABASE_URI": "sqlite:///site.db",
"SQLALCHEMY_TRACK_MODIFICATIONS": False,
"SQLALCHEMY_ECHO": False
}
socketio = SocketIO()
def create_app():
app = Flask(__name__)
app.config.from_mapping(config)
socketio.init_app(app)
from flask_app.main.routes import main
app.register_blueprint(main)
with app.app_context():
db.create_all()
socketio.run(current_app)
return app
main/routes.py:
from flask import render_template, session, request, current_app, Blueprint, redirect, url_for, json, make_response
from flask_app import cache
import os
main = Blueprint("main", __name__)
#main.route("/")
def index():
return render_template("index.html", title="Home")
#main.context_processor
def inject_template_scope():
injections = dict()
# Use this method to check if the client has consented to the use of cookies.
def cookies_check():
value = request.cookies.get('consent')
return value != None
injections.update(cookies_check=cookies_check)
return injections
run.py:
from flask_app import create_app
base.html:
<html>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"
integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I="
crossorigin="anonymous"></script>
</body>
</html>
base.js:
$(window).on('load', function() {
function cookieConsent() {
var socket = io();
const now = new Date();
document.cookie = "consent=consented:true,dateTime:" + now + ",lang:" + navigator.language;
socket.on('connect', function() {
socket.emit('consent cookie', {consent_cookie: document.cookie});
});
}
}
Project Hierarchy:
project/
|_ flask_app/
|_ main/
|_ routes.py
|_ templates/
|_ base.html
|_ static/
|_ js/
|_ base.js
|_ __init__.py
|_ run.py
Environment Variables:
FLASK_APP : run.py
FLASK_ENV : development
socketio.run(app) start a development server at the port 5000.
So when you execute flask run flask calls function create_app() and expects an app. Your function however doesn't actually return app because socketio.run(app) is blocking. When you press CTRL+C it stops this line.
Related
[Flask SSE API doesn't work on production]
Hi, i'm facing an issue in my flask application where I have multiple regular APIs and 1 HTTP API that sends SSE to my React app. In my local development env, the app works just fine as expected. However, when I deployed it to CPanel shared hosting, I noticed that the the React app makes proper request of Content-Type text/event-stream, but received text/html response header from the API after 2 minute timeout. Is there anything wrong with how I implemented the server?
main.py
from myapplication import create_app
from flask import stream_with_context
from gevent import monkey; monkey.patch_all()
from gevent.pywsgi import WSGIServer
app = create_app()
if __name__ == '__main__':
http_server = WSGIServer(("localhost", 5000), app)
http_server.serve_forever()
myapplication/init.py
from flask import Flask
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from os import path, environ
from dotenv import load_dotenv
db = SQLAlchemy()
DATABASE_NAME = 'database.db'
load_dotenv()
def get_database_uri():
host = environ['DB_HOST']
port = environ['DB_PORT']
name = environ['DB_NAME']
username = environ['DB_USER']
password = environ['DB_PASS']
return f'postgresql+psycopg2://{username}:{password}#{host}:{port}/{name}'
def create_database(app):
if not path.exists('myapplication/' + DATABASE_NAME):
db.create_all(app=app)
print('Database created.')
def create_app():
app = Flask(__name__)
CORS(app)
cors = CORS(app, resource = {
r"/*": {
"origins": "*"
}
})
app.config['SECRET_KEY'] = environ['SECRET_KEY']
app.config['SQLALCHEMY_DATABASE_URI'] = get_database_uri()
db.init_app(app)
from .gallery import gallery
from .document import document
from .timer import timer
from .member import member
app.register_blueprint(gallery, url_prefix='/gallery')
app.register_blueprint(document, url_prefix='/document')
app.register_blueprint(timer, url_prefix='/timer')
app.register_blueprint(member, url_prefix='/member')
from .models import Member, Gallery, Document, Timer
create_database(app)
return app
timer.py (SSE api)
global_count = '60'
global_refresh_count = '0'
#timer.route('/stream')
def get_current_stream():
def send_event():
while True:
event_payload = '{"count": "%s", "refreshCount": "%s"}'%(global_count, global_refresh_count)
data_message = f'data: {str(event_payload)}\n\n'
yield data_message
time.sleep(1)
return Response(send_event(), mimetype='text/event-stream')
Local development response:
dev response
Local development eventstream response:
dev event stream response
Production response:
prod response
CPanel python app setup:
Application startup file: passenger_wsgi.py
Application Entry point: application
passenger_wsgi.py
import imp
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
wsgi = imp.load_source('wsgi', 'main.py')
application = wsgi.app
Please kindly help guide me folks, much appreciated!
you have to set certain headers like below
resp = Response(
send_event(),
mimetype='text/event-stream'
)
resp.headers['X-Accel-Buffering'] = 'no'
resp.headers['Cache-Control'] = 'no-cache'
return resp
and configure setting throughout your service
for example in case of nginx:
location '/' {
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 12h;
...
proxy_pass {your_route};
}
in case of gunicorn:
set timeout param to 0 when you execute it.
/{path_to}/gunicorn --workers {num_workers} --timeout 0 --bind {path} -m 007 {module}:{app_name}
Suppose ther is a chat group.
Is there a way to append new messages sent by any member of a particular group to the html page of all the online users in that group using FLASK.
WITHOUT: creating a thread for each users and monitoring for latest updates in the chats.
This is an example, with a very minimal code. If you are interested then you can explore more and do the experiments.
Firstly, you need the Flask-SocketIO module, run the below command to install it.
pip install flask-socketio
With this example, the project directory will be something like below:
ProjectDirectory
|
|-> templates
| |
| |-> index.html
|
|-> app.py
app.py
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'randomSecretKEy#123'
socket = SocketIO(app)
#app.route('/')
def index():
return render_template('index.html')
#socket.on('message')
def message(data):
print(data)
emit('receive_message', data, broadcast=True)
if __name__ == '__main__':
socket.run(app)
index.html
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
<script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
console.log('Connected to server');
});
socket.on('receive_message', function(msg) {
console.log('Received message: ', msg)
})
function sendMessage() {
msg = $('#message').val()
socket.emit('message', msg)
}
</script>
<input type="text" id="message"><button onclick="sendMessage()">Send</button>
Run the Flask app, open two browser tabs, with web developer tools opened side by side. AS soon as you open, you will see that Connected to server message has been logged. Now, type some message and hit the send button, in one tab, and you will see that, the message has been received in another tab's console.
I hope you get a direction with this and it is helpful for you.
Note: Again, I am telling this is a very basic example. Off course, you can add more logic and functionality, and make it more like a chatting app, but it will be out of the scope of this question. So, I will leave it to you and your imagination, to which extent you can learn and take this ahead. You can read about the module's documentation here.
I've used perfect Flask-SocketIO library with Python 3 for couple of months. Everything worked as expected until the last couple of days.
All works fine, if namespace for connection to websocket server stay default /socket.io. But I'm geting an error now if I trying to change namespace for connection to python flask-socketio backend.
My app.py:
from flask import Flask, session, request, render_template, copy_current_request_context
from flask_cors import CORS, cross_origin
import flask_socketio as socketio
import ujson
async_mode = 'threading'
namespace = '/mynamespace'
app = Flask(__name__)
CORS(app)
app.config['SECRET_KEY'] = 'secret!'
sio = socketio.SocketIO(app, async_mode=async_mode)
#sio.on('connect', namespace=namespace)
def connect():
logging.info('Connected')
#sio.on('disconnect', namespace=namespace)
def disconnect():
logging.info('Disconnected')
#app.route("/home/index")
def home():
return render_template('index.html',
async_mode=sio.async_mode)
I'm using ./main.py to run the server, main.py contains:
from app import app, sio
if __name__ == "__main__":
sio.run(app, debug=False)
My template/index.html contains ton of code js, but I think most valuable I loading the socketio from cdn in a head:
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
... and I using connect with custom namespace path, as in docs:
namespace = '/mynamespace/socket.io';
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port,
{path: namespace});
socket.on('connect', function() {
socket.emit('Connected to server');
})
As I understand, By default socketio library trying to connect to backend with emitting connect message to namespace. During loading 'index.html' template on '/home/index' route, logging the errors to console:
Flask server also gives and 404 error:
My best guess: at this moment it looks like something changed in client-side JS library or in chrome browser itself(few days ago I updated Chrome).
Maybe I just understood wrong one small detail. I really appreciate some help with this problem.
Stack versions:
Python 3.7.2,
Flask 1.0.2,
Flask-SocketIO 3.3.1,
socketio.min.js
1.3.5,
Google Chrome 77.0.3865.90 (64 bit)
You are confusing namespace with path, which are completely different things. The path is the endpoint URL where the Socket.IO server is listening. The namespace is a protocol feature of Socket.IO that allows multiplexing of multiple logical connections into a single physical connection.
-->project
--->run.py
--->config.py
--->readme.md
--->app
--->__init__.py
--->controllers
--->__init__.py
--->test_controller.py
--->model
--->__init__.py
--->test_model1.py
--->test_model2.py
run.py
from app import app
app.run(host = '0.0.0.0', port = 8080, debug = True)
config.py - All configuration variable
app/__init__.py
from flask import Flask
app = Flask(__name__)
controllers/__init__.py - Empty
controllers/test_controller.py
#app.route('/test', methods=['POST'])
def test():
return "Hello world"
When I start my server form run.py the server gets started.
But when I try the URL http://locahost:8080/test, it returns 404.
But if the route is configured in app/___init__.py it is working.
Can anyone please guide me what is incorrect here in configuration.
I want to keep the above structure, please let me know of any issues.
Unless you import the file containing the #app.route decorator, it won't be registered. Flask won't import and register all .py files automagically for you.
At the end of your __init__.py file in app/, import projectname.controllers, and import test_controller in the __init__.py file in the controllers module.
I'm having a hard time integrating create-react-app single page application to my flask backend. I want to be able to make a fetch/axios call from my front end like so: axios.get('/getResults') and fetch('/getResults'). Some things I have tried but not limited to is specifying the Flask port as 3000 which is the same used by create-react-app. Also, used the proxy configuration feature on the "package.json" file of create-react-app but to no avail. I suspect my folder structure and Flask code implementation may likely be causing this. Below is my folder structure and "app.py" code. Any help I could get will be appreciated. I can provide additional information if necessary. Thanks
Project -build(contains static folder, index.html...Other meta files)-node_modules-public-srcapp.pypackage.jsonrequirements.txt
app.py:
from flask import Flask, Response, request, jsonify, make_response, send_from_directory,render_template
app = Flask(__name__, static_path='/build/static/')
app.debug=True
#app.route('/')
def root():
print('Inside root function')
return app.send_static_file('index.html')
#app.route('/getResults', methods=["GET"])
def results():
print('Inside getResults path')
return app.send_static_file('index.html')
#app.route('/postData', methods=["POST"])
def data_results():
print('Inside postData path')
data = request.get_json
return jsonify(data)
#app.route('/<path:path>')
def send_js(path):
print("inside send_js fxn")
return send_from_directory('./build/static',path)
if __name__ == "__main__":
print("inside main host call")
app.run(host='0.0.0.0', port=3000)
Errors I get when I run "python app.py" are:
On the terminal: Inside root function
127.0.0.1 - - [12/Jun/2017 09:42:24] "GET / HTTP/1.1" 404 -
On the browser:Not Found - The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
I was having the exact same issue and I was able to solve it by appeasing Flask with symlinks.
Keep the templates and static directory paths at their defaults and in the directory with your Flask main file, add these symlinks
ln -s build/ templates
ln -s build/static static
In case you were curious, this was my specific problem, which just involved a few more nested directories but was in essence the same:
Running NPM Build from Flask
You can then use Nurzhan's root configuration:
#app.route('/')
def root():
print('Inside root function')
return render_template('index.html')
But you only require your app declaration to be: app = Flask(__name__)
The only thing that doesn't work for me is the favicon, and I will update this answer once I figure that out.
In development mode, you need to configure your create-react-app package.json to forward "ajax" request to the flask server.
Here is what my package.json looks like:
{
"name": "socialite",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",
"devDependencies": {
"react-scripts": "1.0.10"
},
"dependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}
See the proxy field? That's where the magic happens, replace its value with the flask server address. That way, you can take advantage of CRA hot reloading feature. This is documented at in create-react-app as "Proxying API Requests in Development"
Then do run your application, you go at localhost:3000 or whatever port yarn opens for you. And when you do an API call in javascript over the rainbow to the server for instance: fetch('/api/model/') or something nodejs' server will forward to the flask app. I think the nodejs server does look at the content-type field of the ajax request to know whether it should forward the request to the backend server or not.
I recommend you prefix all your backend routes with something like /api/v1/ or something so the nginx configuration is neat and easy to write.
I think you have a number of misunderstandings.
The create-react-app runs on its own server on port 3000 and if you try to run your flask app on the same port on the same machine it will complain that port 3000 is already in use. So from this we move to another question - the structure of your application.
Will it be a separate reactjs based client on the frontend and api based on flask in the backend which will be 2 separate applications communicating with each other over HTTP? In this case the frontend and backend will usually run on separate servers.
Or it will one flask application which will use reactjs in its template pages?
You can fix your current problem with not finding URL by changing to this in your code:
#app.route('/')
def root():
print('Inside root function')
return render_template('index.html')
And this:
template_dir = os.path.abspath('build/templates')
app = Flask(__name__, static_path='/build/static/',
template_folder=template_dir)
Since your templates folder is in the build directory.