Quickstart¶
Flask-Websockets¶
Flask-Websockets is a Flask extension which enables Flask-style use of gevent-websockets. Take a look at the Documentation if you intend to use it.
from flask import Flask
from flask_websockets import WebSockets
app = Flask(__name__)
sockets = WebSockets(app)
@sockets.on_message
def echo(message):
return message
if __name__ == '__main__':
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
server.serve_forever()
Installation is currently only possible via source.
pip install git+https://github.com/shoeffner/Flask-Websockets@master#egg=Flask-Websockets
Features¶
Flask-Websockets resulted from the need of a raw implementation of websockets to communicate with a Unity-WebGL app.
There is a multitude of Flask extensions for websockets, but most of them require some sort of manual while True
-loops to handle requests.
Flask-Websockets tries to follow Flask-patterns as close as possible, thus it has the following features:
ws
allows access to the websocket connection of the current context. It works similar to Flask’srequest
.on_message()
is a powerful decorator to filter messages or catch all. The return value of functions decorated withon_message()
is sent as a reply, similar to View functions. Note that it has limitations, notablyflash()
and custom HTTP status codes do not work.
Caveats & Issues¶
Flask-Websockets is a tool I built for a specific purpose (communication between two specific components), thus it is very limited. For instance, inspired by Flask-Sockets, it does not work properly with the flask debug runner. Thus, the recommended way to run it is as done above:
if __name__ == '__main__':
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
server.serve_forever()
Flask-Websockets does not have the following typical Flask-capabilities other Frameworks provide:
There is also a list of “known” issues, though not all are Flask-Websockets’ fault:
Logging is not properly supported (gevent-websocket#16)
Reloading is currently not supported, this is essentially the same as for Flask-Sockets flask-sockets#48
The websockets do not seem to work using
wss://
, instead,ws://
needs to be used.While it is possible to have some cookie/session handling to identify clients accross connections (e.g. a website using HTTP and websocket), it only works with one open browser tab at the moment. If a second tab is opened, it takes over control over the websocket.
Examples¶
While the obligatory echo-example is given above, here are a few more examples. A fully integrated example can be found in the examples directory.
All examples below assume the following boilerplate:
from flask import Flask
from flask_websockets import WebSockets, ws
app = Flask(__name__)
sockets = WebSockets(app)
# EXAMPLE CODES HERE
if __name__ == '__main__':
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
server = pywsgi.WSGIServer(('127.0.0.1', 5000), app, handler_class=WebSocketHandler)
server.serve_forever()
The simple echo shown at the beginning is employing the most basic use case of the on_message decorator: handle each and every message.
If the method returns None
(which it implicitly does anyways), nothing is done.
If it returns a str
or a bytes
object, it is send via the websocket.
If it returns something of another type, an error is raised.
Note that this catch-all method is always called, even if another functions handles the same message.
To restrict this behavior, setup the app as follows: WebSockets(app, match_one=True)
.
@sockets.on_message
def on_message(message):
# do something
return 'some result'
@sockets.on_message
def reply(message):
# This raises an error
# return {'message': message, 'reply': 'Reply!'}
# Instead, use:
from Flask import jsonify
return jsonify({'message': message, 'reply': 'Reply!'})
For long running tasks, it is possible to send status updates using the global ws
.
@sockets.on_message
def do_some_work(message):
import time
ws.send('Starting work')
time.sleep(3)
ws.send('Hang in there')
time.sleep(2)
ws.send('Work done')
It is possible to use the on_message()
decorator to match (regex) patterns.
The patterns are compiled using the standard re module.
@sockets.on_message("^ECHO .*")
def echo(message):
_, msg = message.split(' ', 1)
return msg
Similar to the flask.has_app_context()
and flask.has_request_context()
, Flask-Websockets comes with has_socket_context()
to check whether a socket context is available.
from flask import render_template
from flask_websockets import has_socket_context
@sockets.one_message("^ECHO .*")
def echo(message):
print(has_socket_context()) # True
return message
@app.route('/')
def index():
print(has_socket_context()) # False
return render_template('index.html')
Using url_for()
in templates works with the special rule websocket
and supplying _external=True, _scheme='ws'
.
ws = new WebSocket("{{ url_for('websocket', _external=True, _scheme='ws') }}");
To handle cross-connection identification, you must set a session cookie. This requires a number of tweaks, mostly you MUST set a secret key, and you should set a cookie SameSite value.
from uuid import uuid4
from flask import Flask, session, render_template
from flask_websockets import WebSockets, ws, has_socket_context
app = Flask(__name__)
app.secret_key = b'secretkey'
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_SAMESITE='Strict'
)
sockets = WebSockets(app, patch_app_run=True)
@app.route('/reply_via_ws')
def send():
if has_socket_context():
ws.send('A reply via websocket')
# Note that this is still an HTTP request, so we need a response
return 'A reply via HTTP'
@app.route('/')
def index():
session['ws.identifier'] = str(uuid4())
return render_template('index.html')
Alternatives¶
As mentioned above, there is a number of Flask extensions to enable websocket capabilites. Here is a list of alternatives you should check out before using Flask-Websockets:
Flask-Websocket (same name as this package without s) handles JSON messages to filter messages for
.on(event)
decoratorsFlask-SocketIO uses socket.io instead of gevent-websockets, and thus comes with rooms, filters, etc.
Flask-Sockets heavily inspired the initial work for Flask-Websockets and offers cookie handling, routing, and Blueprint support; however it is less Flask-like and requires to pass a
ws
argument and implementing a custom loop.Flask-uWSGI-WebSocket uses a custom loop, but the repository seems to have moved (and I spent less than a minute to search for it).
Flask-Socket-Tornado has Tornado-style sockets (I never used Tornado, so I have no clue what that means).
Important: not “approved”¶
This is no approved extension (and thus, I didn’t put it up on PyPI):
No: Maintainer (I probably don’t have the resources nor need)
Yes: Name is Flask-Websockets
Yes: MIT license
Yes: API characteristics
Yes: I install it using
pip install -e .
No: No test suite
Yes: Documentation.
Yes: Supports >= 3.6
See also Approved extensions.