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’s request.

  • on_message() is a powerful decorator to filter messages or catch all. The return value of functions decorated with on_message() is sent as a reply, similar to View functions. Note that it has limitations, notably flash() 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) decorators

  • Flask-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):

  1. No: Maintainer (I probably don’t have the resources nor need)

  2. Yes: Name is Flask-Websockets

  3. Yes: MIT license

  4. Yes: API characteristics

  5. Yes: I install it using pip install -e .

  6. No: No test suite

  7. Yes: Documentation.

  8. Yes: Supports >= 3.6

See also Approved extensions.