Production Flask-SocketIO + ZeroRPC - python

I've deployed a flask-socketio web server, but after installing zerorpc which installs gevent i'm facing a lot of troubles..
at first my code looked like this:
socketio.start_background_task(poll_events)
socketio.run(app, host="0.0.0.0", keyfile='key.pem', certfile='cert.pem')
I'm starting a background task which will constantly read from a queue and send messages through socketio. now that gevent is installed it flask-socketio will try to use it (which i'm fine with actually making my server a production server and not a development one) but then socketio.start_background_task blocks. So I read that
from gevent import monkey; monkey.patch_all()
is required.
So now my code looks like that:
socketio.start_background_task(poll_events)
WSGIServer(('0.0.0.0', 5000), app, keyfile='key.pem', certfile='cert.pem').serve_forever()
For some reason when debugging with pycharm I received a lot of weird greenlet exceptions and also I think that sometimes socketio messages are dropped so I decided to use eventlet. Then again, patching is required. So my code looks like this:
socketio.start_background_task(poll_events)
eventlet.wsgi.server(eventlet.wrap_ssl(eventlet.listen(("0.0.0.0", 5000)), keyfile='key.pem', certfile='cert.pem'), app)
Because of monkey patching zerorpc throws an exception
"gevent.exceptions.LoopExit: This operation would block forever"
What is the correct way to deploy a production server with flask + socketio + zerorpc?

I've resolved the issue, when debugging I choose "threading" as async_mode
socketio = SocketIO(app, async_mode="threading")
and when deploying with gunicorn I use gevent
CMD ["gunicorn", "-w", "1", "-k", "gevent","--reload", "web_app:app"]
For some reason gevent doesn't work without gunicorn and eventlet wouldn't work with zerorpc..

Related

Flask talisman not working and redirects to https://localhost:8000

I have been running flask-talisman on my development server and everything checks out fine. Yet, with the same code and requirements installed on my dedicated server for production (Almalinux), just adding Talisman(app) after app = Flask(__name__) results in the webpage not loading with a redirection to https://localhost:8000. The error message that I precisely get on my browser after typing in the domain is:
This site can't be reached - localhost refused to connect
I am running Nginx 1.14.1 with gunicorn 20.1.0 and supervisor. The server is connected to the internet and without using Talisman it has run smoothly so far.
List of things that I tried without any effect
temporarily stopped firewall
restarted nginx
both tried to access the website through its domain and IP address - the redirection to localhost:8000 remains
tried to run the app on other ports, e.g. 8000 for testing
stripped down the code to a mere mini tutorial that runs well on my development server but not on my production server. So I figured it can't be the app itself.
checked error logs and there is literally nothing, not in the nginx error log or python app error log. Access log shows nothing usual, the same as if everything checks out.
searched the Internet and found nothing that would point in the right direction and explain the failed redirect to localhost:8000
Here is a stripped down tutorial code that should run but doesn't run on my server:
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
Talisman(app)
app.secret_key = 'kungfoo'
#app.route('/', methods=['GET', 'POST'])
def index():
return "Hello stackoverflow!"
if __name__ == "__main__":
app.run(debug=True)
Well,
proxy_set_header X-Forwarded-Proto $scheme;
does the trick in the nginx.conf within the server's location / {} block. This is stated in the gunicorn docs and can be easily missed...
It is recommended to pass protocol information to Gunicorn. Many web
frameworks use this information to generate URLs. Without this
information, the application may mistakenly generate ‘http’ URLs in
‘https’ responses, leading to mixed content warnings or broken
applications.

Can't get multiple uwsgi workers to work with flask-socketio

In development, flask-socketio (4.1.0) with uwsgi is working nicely with just 1 worker and standard initialization.
Now I'm preparing for production and want to make it work with multiple workers.
I've done the following:
Added redis message_queue in init_app:
socketio = SocketIO()
socketio.init_app(app,async_mode='gevent_uwsgi', message_queue=app.config['SOCKETIO_MESSAGE_QUEUE'])
(Sidenote: we are using redis in the app itself as well)
gevent monkey patching at top of the file that we run with uwsgi
from gevent import monkey
monkey.patch_all()
run uwsgi with:
uwsgi --http 0.0.0.0:63000 --gevent 1000 --http-websockets --master --wsgi-file rest.py --callable application --py-autoreload 1 --gevent-monkey-patch --workers 4 --threads 1
This doesn't seem to work. The connection starts rapidly alternating between a connection and 400 Bad request responses. I suspect these correspond to the ' Invalid session ....' errors I see when I enable SocketIO logging.
Initially it was not using redis at all,
redis-cli > PUBSUB CHANNELS *
resulted in an empty result even with workers=1.
it seemed the following (taken from another SO answer) fixed that:
# https://stackoverflow.com/a/19117266/492148
import gevent
import redis.connection
redis.connection.socket = gevent.socket
after doing so I got a "flask-socketio" pubsub channel with updating data.
but after returning to multiple workers, the issue returned. Given that changing the redis socket did seem to bring things in the right direction I feel like the monkeypatching isn't working properly yet, but the code I used seems to match all examples I can find and is at the very top of the file that is loaded by uwsgi.
You can run as many workers as you like, but only if you run each worker as a standalone single-worker uwsgi process. Once you have all those workers running each on its own port, you can put nginx in front to load balance using sticky sessions. And of course you also need the message queue for the workers to use when coordinating broadcasts.
Eventually found https://github.com/miguelgrinberg/Flask-SocketIO/issues/535
so it seems you can't have multiple workers with uwsgi either as it needs sticky sessions. Documentation mentions that for gunicorn, but I did not interpret that to extend to uwsgi.

Gevent/Gevent-websocket not being used by Flask-SocketIO

I am building a web interface/data API using Flask and Flask-SocketIO for websocket communication. I would like to start shifting to a more development-ready setup using Gevent/Gevent-websocket, Gunicorn, and eventually Nginx for load balancing. However, after installing Gevent and Gevent-websocket, I am still getting a warning message when starting the SocketIO server:
WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.
According to the Flask-SocketIO docs,
When the application is in debug mode the Werkzeug development server is still used and configured properly inside socketio.run(). In production mode the eventlet web server is used if available, else the gevent web server is used. If eventlet and gevent are not installed, the Werkzeug development web server is used.
This implies that the use of Gevent should be automated behind the scenes as part of Flask-SocketIO. I checked my Python installs with pip list and confirmed that I have Gevent 1.3.4 and Gevent-websocket 0.10.1 installed. Here is the initialization code for the SocketIO server:
app.py
flaskApp = Flask(__name__)
flaskApp.config['SESSION_TYPE'] = 'filesystem'
Session(flaskApp)
socketio = SocketIO(flaskApp, async_mode='threading', manage_session=False)
def createApp():
flaskApp.secret_key = "super secret"
socketio.run(flaskApp, host='0.0.0.0', port=80)
start.py
app.register_blueprint(monitor.blueprint)
...
createApp()
Why is Flask-SocketIO not detecting my Gevent install?
The portion of the docs that you quoted refers to the async_mode argument, and how it is set by default. You are setting async_mode='threading', so that disables the automatic selection of an async mode. Remove the argument, and then you'll get eventlet or gevent, depending on what you have installed.

Proper format of Procfile for Flask App on Heroku

I'm trying to deploy a flask app on heroku. I've gotten to the point where the app builds and deploys, but when I try to go to the URL, the app times out with the following error.
Error R10 (Boot timeout) -> Web process failed to bind to $PORT within 60 seconds of launch
I think the problem is with my procfile. It has one line.
web: python add_entry3.py
Other people have procfiles that look like this:
web: gunicorn app:app
This is just a toy app and I don't care about performance so I don't think I need to use gunicorn for the web server. Should I be putting a colon and command after my app's file name (add_entry3.py)?
Most likely your flask app isn't answering on the port and interface the Heroku expects. By default, Flask only listens on 127.0.0.1, and I think on port 5000. Heroku passes your app a PORT environment variable and you'd need to tell Flask to listen on all interfaces.
But there are reasons other than performance you want to avoid Flask's default debug server for production code. It's got memory leaks, there are security implications, and really ... just don't do it. Add gunicorn to your requirements.txt and use that.
But if you must use the Flask test/debug server, change your app.run() call to something like this:
app.run(host='0.0.0.0', port=int(os.environ.get("PORT", 5000)))

How to run python websocket on gunicorn

I found this 0 dependency python websocket server from SO: https://gist.github.com/jkp/3136208
I am using gunicorn for my flask app and I wanted to run this websocket server using gunicorn also. In the last few lines of the code it runs the server with:
if __name__ == "__main__":
server = SocketServer.TCPServer(
("localhost", 9999), WebSocketsHandler)
server.serve_forever()
I cannot figure out how to get this websocketserver.py running in gunicorn. This is because one would think you would want gunicorn to run server_forever() as well as the SocketServer.TCPServer(....
Is this possible?
GUnicorn expects a WSGI application (PEP 333) not just a function. Your app has to accept an environ variable and a start_response callback and return an iterator of data (roughly speaking). All the machinery encapsuled by SocketServer.StreamRequestHandler is on gunicorn side. I imagine this is a lot of work to modify this gist to become a WSGI application (But that'll be fun!).
OR, maybe this library will get the job done for you: https://github.com/CMGS/gunicorn-websocket
If you use Flask-Sockets extension, you have a websocket implementation for gunicorn directly in the extension which make it possible to start with the following command line :
gunicorn -k flask_sockets.worker app:app
Though I don't know if that's what you want to do.

Categories