Easy WebSockets with Flask and Gevent

Posted by
on under

This weekend I decided to take a short vacation from my book writing effort and spend time on a project I wanted to work on for a long time. The result of this effort is a brand new Flask extension that I think is pretty cool.

I'm happy to introduce Flask-SocketIO, a very easy to use extension that enables WebSocket communications in Flask applications.

What is WebSocket?

WebSocket is a new communication protocol introduced with HTML5, mainly to be implemented by web clients and servers, though it can also be implemented outside of the web.

Unlike HTTP connections, a WebSocket connection is a permanent, bi-directional communication channel between a client and the server, where either one can initiate an exchange. Once established, the connection remains available until one of the parties disconnects from it.

WebSocket connections are useful for games or web sites that need to display live information with very low latency. Before this protocol existed there were other much less efficient approaches to achieve the same result such as Comet.

The following web browsers support the WebSocket protocol:

  • Chrome 14
  • Safari 6
  • Firefox 6
  • Internet Explorer 10

What is SocketIO?

SocketIO is a cross-browser Javascript library that abstracts the client application from the actual transport protocol. For modern browsers the WebSocket protocol is used, but for older browsers that don't have WebSocket SocketIO emulates the connection using one of the older solutions, the best one available for each given client.

The important fact is that in all cases the application uses the same interface, the different transport mechanisms are abstracted behind a common API, so using SocketIO you can be pretty much sure that any browser out there will be able to connect to your application, and that for every browser the most efficient method available will be used.

What about Flask-Sockets?

A while ago Kenneth Reitz published Flask-Sockets, another extension for Flask that makes the use of WebSocket accessible to Flask applications.

The main difference between Flask-Sockets and Flask-SocketIO is that the former wraps the native WebSocket protocol (through the use of the gevent-websocket project), so it can only be used by the most modern browsers that have native support. Flask-SocketIO transparently downgrades itself for older browsers.

Another difference is that Flask-SocketIO implements the message passing protocol exposed by the SocketIO Javascript library. Flask-Sockets just implements the communication channel, what is sent on it is entirely up to the application.

Flask-SocketIO also creates an environment for event handlers that is close to that of regular view functions, including the creation of application and request contexts. There are some important exceptions to this explained in the documentation, however.

A Flask-SocketIO Server

Installation of Flask-SocketIO is very simple:

$ pip install flask-socketio

Below is an example Flask application that implements Flask-SocketIO:

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 index():
    return render_template('index.html')

@socketio.on('my event')
def test_message(message):
    emit('my response', {'data': message['data']})

@socketio.on('my broadcast event')
def test_message(message):
    emit('my response', {'data': message['data']}, broadcast=True)

@socketio.on('connect')
def test_connect():
    emit('my response', {'data': 'Connected'})

@socketio.on('disconnect')
def test_disconnect():
    print('Client disconnected')

if __name__ == '__main__':
    socketio.run(app)

The extension is initialized in the usual way, but to simplify the start up of the server a custom run() method is used instead of flask run or app.run(). This method starts the eventlet or gevent servers if they are installed. Using gunicorn with the eventlet or gevent workers should also work. The run() method takes optional host and port arguments, but by default it will listen on localhost:5000 like Flask's development web server.

The only traditional route in this application is /, which serves index.html, a web document that contains the client implementation of this example.

To receive WebSocket messages from the client the application defines event handlers using the socketio.on decorator.

The first argument to the decorator is the event name. Event names 'connect', 'disconnect', 'message' and 'json' are special events generated by SocketIO. Any other event names are considered custom events.

The 'connect' and 'disconnect' events are self-explanatory. The 'message' event delivers a payload of type string, and the 'json' and custom events deliver a JSON payload, in the form of a Python dictionary.

To send events a Flask server can use the send() and emit() functions provided by Flask-SocketIO. The send() function sends a standard message of string or JSON type to the client. The emit() function sends a message under a custom application-defined event name.

Messages are sent to the connected client by default, but when including the broadcast=True optional argument all clients connected to the namespace receive the message.

A SocketIO Client

Ready to try your hand at some Javascript? The index.html page used by the example server contains a little client application that uses jQuery and SocketIO. The relevant code is shown below:

$(document).ready(function(){
    var socket = io();
    socket.on('my response', function(msg) {
        $('#log').append('<p>Received: ' + msg.data + '</p>');
    });
    $('form#emit').submit(function(event) {
        socket.emit('my event', {data: $('#emit_data').val()});
        return false;
    });
    $('form#broadcast').submit(function(event) {
        socket.emit('my broadcast event', {data: $('#broadcast_data').val()});
        return false;
    });
});

The socket variable is initialized with a SocketIO connection to the server. If the Socket.IO server is hosted at a different URL than the HTTP server, then you can pass a connection URL as an argument to io().

The socket.on() syntax is used in the client side to define an event handler. In this example a custom event with name 'my response' is handled by adding the data attribute of the message payload to the contents of a page element with id log. This element is defined in the HTML portion of the page.

The next two blocks override the behavior of two form submit buttons so that instead of submitting a form over HTTP they trigger the execution of a callback function.

For the form with id emit the submit handler emits a message to the server with name 'my event' that includes a JSON payload with a data attribute set to the value of the text field in that form.

The second form, with id broadcast does the same thing, but sends the data under a different event name called 'my broadcast event'.

If you now go back to the server code you can review the handlers for these two custom events. For 'my event' the server just echoes the payload back to the client in a message sent under event name 'my response', which is handled by showing the payload in the page. The event named 'my broadcast event' does something similar, but instead of echoing back to the client alone it broadcasts the message to all connected clients, also under the 'my response' event.

You can view the complete HTML document in the GitHub repository.

Running the Example

To run this example you first need to download the code from GitHub. For this you have two options:

The example application is in the example directory, so cd to it to begin.

To keep your global Python interpreter clean it is a good idea to make a virtual environment:

$ virtualenv venv
$ . venv/bin/activate

Then you need to install the dependencies:

(venv) $ pip install -r requirements.txt

Finally you can run the application:

(venv) $ python app.py

Now open your web browser and navigate to http://localhost:5000 and you will get a page with two forms as shown in the following screenshot:

Any text you submit from any of the two text fields will be sent to the server over the SocketIO connection, and the server will echo it back to the client, which will append the message to the "Receive" part of the page, where you can already see the message sent by the 'connect' event handler from the server.

Things get much more interesting if you connect a second browser to the application. In my case I'm testing this with Firefox and Chrome, but any two browsers that you run on your machine will do. If you prefer to access the server from multiple machines you can do that too, but you first need to change the start up command to socketio.run(app, host='0.0.0.0') so that the server listens on the public network interface.

With two or more clients when you submit a text from the form on the left only the client that submitted the message gets the echoed response. If you submit from the form on the right the server broadcasts the message to all connected clients, so all get the reply.

If a client disconnects (for example if you close the browser window) the server will detect it a few seconds later and send a disconnect event to the application. The console will print a message to that effect.

Final Words

For a more complete description of this extension please read the documentation. If you want to make improvements to it feel free to fork it and then submit a pull request.

I hope you make cool applications with this extension. I can tell you that I had a lot of fun implementing this extension.

If you make something with it feel free to post links in the comments below.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

513 comments
  • #1 JaniG said

    Hi Miguel. Thank you for your great post and work. Is it possible to deploy application with Flask-SocketIO on Heroku?

  • #2 Miguel Grinberg said

    @JaniG: I haven't tried it, but it should run, gunicorn can be used as the server.

  • #3 jh said

    First of all, thanks for the post and extension.

    What's the procedure for using Flask-SocketIO within a blueprint?

  • #4 Miguel Grinberg said

    @jh: As I explain in the article, the socket connection works independently of the regular HTTP based routes, so having blueprints in your application does not change how you work with sockets at all, you can have both coexisting in the same application. You can think of the SocketIO object as a special kind of blueprint that applies to socket communication. Like you do for regular blueprints you can put all the socket based handlers in a separate module or package, that will help keep your project neatly organized.

  • #5 Pedro Baumann said

    Hello Miguel,

    I have been tinckkering with python websockets for the past week and I've tried Tornado implementation and this one, I have to say I really like the way your implementation works. The customized messages/broadcasts and callbacks allow building a great and more clear code.

    I just started to try to deploy a small app to heroku using gunicorn and it doesn't let me, it seems it won't let me set socket.io (node.js module) as a prerequicite. Hace you got any clue about anywhere I could try this deployment? I'm rather new to python webApps and feel kind of lost here.

  • #6 Miguel Grinberg said

    @Pedro: socket.io is not a server pre-requisite, your server only needs gevent, gevent-socketio and gevent-websocket. The socket.io Javascript library is used in the client only, so Heroku does not need to be told, just put it in your HTML.

  • #7 George P. said

    Hi! Thanks for all of your tutorials!

    Maybe I'm doing something wrong, but when I make two tabs in Google Chrome with the app running on them, only one message goes through, and then it stops. On my initial look, I'm not seeing anything in the code that would make it this case. Thoughts?

    Thanks,
    George

  • #8 Miguel Grinberg said

    @George: Not sure. What do you mean by "it stops"? You have to trigger the sending of messages with the buttons. Works fine for me here, I'm using Chrome for Linux right now to test it.

  • #9 George Anthony said

    So far, it's only allowed me to send two messages in each browser window: one from the one on the left, one from the one on right.

    In the traceback, I'm getting an attributerror:

    AttributeError: 'SocketIO' has no value 'namespace'

  • #10 Miguel Grinberg said

    @George: you probably have a typo in your script. Compare it with my copy on GitHub.

  • #11 George P. said

    I had cloned your script from GitHub, gotten dependencies, and ran it.

    I downloaded it again and tried it, same result. Just some weird glitch if no one else had an issue like this.

  • #12 Miguel Grinberg said

    Does a single tab work? And what about two different browsers?

  • #13 George P. said

    Last comment on here...just wanted to let anyone who runs into this know what I found to fix it:

    If you get a traceback that ends in:

    File "/Library/Python/2.7/site-packages/flask_socketio.py", line 122, in dispatch_message
    self.namespace[k] = v
    AttributeError: 'SocketIO' object has no attribute 'namespace'

    Please "pip install Flask-SocketIO --upgrade". Now that I have 0.2.1 instead of 0.1.0, Miguel's excellent tutorial is running perfectly!

    Cheers,
    George

  • #14 Bill said

    I was wondering what would be the best way of using a database (sqlite3) combined with this tutorial code, to have lasting realtime data and updates?

  • #15 Miguel Grinberg said

    @Bill: For the database you can use your favorite package, be it the native sqlite access, Flask-SQLAlchemy or something else. Adding sockets does not really change things much, the environment in a socket handler is similar to that of a request, so you can use your database just fine.

  • #16 Gianluca Niccolini said

    I Have the same issue running Pyhton 2.7.5 on windows.
    I've downloaded code from github.

  • #17 Miguel Grinberg said

    @Gianluca: same issue as who? Can you be more specific?

  • #18 Marko Üeli said

    Hi Miguel, thank you for this great Flask extension! I got it nicely working through an nginx proxy with ssl, but I can't figure out how to push data to the client on server generated events, e.g. in a Redis pub/sub scenario. I would be grateful for a hint. Thanks, Marko

  • #19 Miguel Grinberg said

    @Marko: If you want the server to initiate an exchange you have to have a way to locate the socket namespace for a given user. In the connect event you can store "request.namespace" in a dictionary indexed by user id, for example. The namespace object has the send() and emit() methods to write to the client.

  • #20 Marko Üeli said

    Thanks Miguel, works fine the way you suggested but I just don't know where to put e.g. Redis pubsub.listen() (or a periodical request to a db) and not block the application main loop. What I want to achieve is to push data to connected users once it becomes available from an external source (redis/db etc). Any idea? Thanks

  • #21 W.Michael DePeel said

    Miguel, awesome work! I'm using it for powering the status page of a timeclock, showing the IN/OUT Vacation/Sick statuses of each employee.

    This might be similar to Marko's question, but I didn't quite follow your response. I have a route in flask that, when hit, inserts a time punch for that employee. It's at that time that I would want to re-push the status html out to the status page. So, I want to trigger a socket broadcast event from within a normal flask route. Is there a way to do that?

    Thanks! Your tutorial has really helped me get as far as I have with my timeclock, and has laid the foundation for the remainder of my internal projects for my employer.

  • #22 Miguel Grinberg said

    @Marko: You need to start a gevent task that listens for events from Redis independently of the application. This task needs to have access to the list of namespaces that the info will then need to be sent.

  • #23 Miguel Grinberg said

    @W.Michael: As long as your view function has access to the socket namespace (the request.namespace object that is made available in socket calls) then you can write stuff just fine. You can store this namespace somewhere during the socket connect event, for example.

  • #24 Roo said

    Hello,

    Thanks for writing this lib - I'm attempting to use it on my first Python project though I am struggling to get my head around using this in my app.

    I am building an app on a Raspberry Pi that aims to use HTML/JavaScript for the GUI which willbe navigated by an input device connected to the Pi's GPIO ports (I'm using a digital encoder with a push-button function as a means to browse and select a username from a set of

    • elements within the browser)

      Using parts from your Flask mega-tutorial I was able to get a dictionary of lists that gets assembled from a comma-separated file hosted on an external server rendered out into HTML by passing the dict on the render_template() function.

      Now I need to get events firing from the backend (as a result of the GPIO input) that tells the front end where the user has navigated to. I thought websockets would be perfect for this, but I am stuck at the first hurdle - getting the app that renders the usernames out into HTML and your example to run in tandem.

      I have a feeling I need to be using two threads - one for the socketIO stuff and another for generating and rendering the username dict. I will definitely need a third for tracking the GPIO input once I come to implement that (but one step at a time!)

      I am unsure how to implement the thread as my function to return the username dict gets called like this:

      @app.route('/')
      def index():
      return render_template("index.html", users = user.return_users())

      As you've probably guessed, I am brand new to Python and pretty new to programming in general. This question is probably out of scope of what you are offering here but I've been stuck on this for hours. I'd really appreciate any thoughts, even a helpful link I could read to help me progress.

      Thanks

      • Roo
  • #25 W.Michael DePeel said

    Thanks, I'll try that. Looking forward to your book!

Leave a Comment