2014-02-10T03:46:26Z

Easy WebSockets with Flask and Gevent

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

495 comments

  • #126 Ben said 2014-06-26T02:01:46Z

    If I call emit() followed by a time consuming computation then the emit is only sent after that time consuming computation respectively when the socket handler returns. At least this is how I observed it. Is there any way to make the emit call happen right away?

  • #127 Miguel Grinberg said 2014-06-26T05:17:36Z

    @Ben: it is a really bad idea to long computations in this type of web server. The problem is that threads (actually greenlets in this case) need to cooperate and "yield" frequently. The simplest way to improve your problem is to call gevent.sleep(0) at different times during the long computation, that way the other greenlets get a chance to run.

  • #128 Greg said 2014-07-03T13:27:15Z

    Hi. Thanks for a terrific tool. I have a little question. How do I emit messages from a function that should run permamenty, aka while True? I tried to call function:

    @socketio.on('post article') def post_the_article(message): emit('my response', {'data': message['data']})

    via post_the_article(text), but I have error:

    emit('my response', {'data': message['data']}) TypeError: string indices must be integers

    The idea is to make a bot for a chat that is constantly parsing the rss feed and posts it in the chat.

  • #129 Miguel Grinberg said 2014-07-03T17:48:41Z

    @Greg: see the example app in the Github repository. This app has a background thread that sends periodic events to all listeners.

  • #130 Tennyson said 2014-07-07T23:05:05Z

    Hi. Thanks for the great tutorial, i finally got the server running on heroku but i would like to how i can connect an android client (autobahn websocket) to the websocket server i.e what port do i use?

  • #131 Miguel Grinberg said 2014-07-08T03:05:27Z

    @Tennyson: use the default HTTP ports, 80 for plain text, 443 for SSL.

  • #132 Kevin said 2014-07-10T12:38:41Z

    Is it possible to list all users currently connected to a room? Is it possible to assign usernames to the users' sessions?

    Thank you for any advice!

  • #133 Kevin said 2014-07-10T12:44:36Z

    Also, is it possible to send messages to a specific user?

  • #134 Miguel Grinberg said 2014-07-11T02:44:40Z

    @Kevin: the extension does not have a concept of "users", it only knows about "connections". Your application can keep track of users and their associated connections easily, you just need to handle the connect and disconnect events and update your data structure there. Once you have that you can do all the things that you asked.

  • #135 Kevin said 2014-07-21T13:50:46Z

    @Miguel Grinberg: Thanks, it's exactly what I did. I'm tracking the connections in my app now, and am also able to send "private" messages to individual users. Another questions though: does your Flask extension work with IE9 and other old browsers? You mentioned that it downgrades itself, but I am unable to connect with socket on IE9 (everything works perfectly under Chrome and Firefox). Do I have to add some options somewhere to make this work?

    Thank you very much for your help and this addon, it's really amazing!

  • #136 Miguel Grinberg said 2014-07-21T18:21:43Z

    @Kevin: I haven never tested IE9, it is supposed to work though. Maybe take a look at the debugging console to see if there are any clues?

  • #137 David S said 2014-07-24T02:21:02Z

    Thank you for your work on the flask plugin!

  • #138 Carl Johan Rehn said 2014-07-31T15:35:18Z

    Thanks for a very interesting tutorial. Maybe a stupid question: how do you create a websocket client in Python that works with your example?

    Yours, Carl.

  • #139 Miguel Grinberg said 2014-08-02T04:52:07Z

    @Carl: the client is normally Javascript code that uses the SocketIO client libraries. But there is a client implementation in Python also: https://pypi.python.org/pypi/socketIO-client.

  • #140 Gleb said 2014-08-11T15:00:52Z

    Hello, Miguel, thank you for you chat. I've learned many things and i've made a fork of it. Could you help me please with this kind of an error "You need to use a gevent-socketio server"

    if path is not None and path.startswith('socket.io'): if 'socketio' not in environ: raise RuntimeError('You need to use a gevent-socketio server.')

    This exception occurs only when i run application on heroku by this Procfile.

    web: gunicorn app:app --log-file=-

  • #141 Miguel Grinberg said 2014-08-12T07:49:34Z

    @Gleb: see the documentation to learn how to run the app with gunicorn.

  • #142 Mauro De Giorgi said 2014-08-13T00:04:02Z

    Hi Miguel, I'm trying to send messages directly from the server, like you say in the documentation:

    Sometimes the server needs to be the originator of a message. This can be useful to send a notification to clients of an event that originated in the server. The socketio.send() and socketio.emit() methods can be used to broadcast to all connected clients:

    def some_function(): socketio.emit('some event', {'data': 42})

    I do something like this inside my own function:

    socketio.send('my response', {'data': 42})

    in this case socketio is the one that I define like socketio = SocketIO(app). Unfortunately the message are not sent to the client.

    The normal communication client/server, like this works perfectly fine:

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

    Any suggestion? Thanks a lot

    Mauro

  • #143 Miguel Grinberg said 2014-08-13T06:28:54Z

    @Mauro: are you using socketio.emit() or socketio.send()? These two functions are different, emit() is used with custom event names, send() just sends messages to the client, all under a generic event name.

  • #144 Francisco Pena said 2014-09-03T14:05:14Z

    Hi Miguel, In this post, I noticed you provided a secret key to the apps config variable in the example code.

    Is that key used when establishing a connection to the server and client, or while messages are being sent across between server and client?

  • #145 Miguel Grinberg said 2014-09-03T14:33:03Z

    @Francisco: the only reason I included a secret key is that the extension works with the user session (it exposes the user session to socket handlers), and to use a user session you need to have a secret key in place, since Flask uses it to encrypt it and send it to the client as a cookie. There is no direct use of encryption in this extension, if you need that you can use https:// or implement your own encryption mechanism.

  • #146 Francisco Pena said 2014-09-03T15:12:39Z

    Ah, Alrighty.

    I'm concerned about cross site vulnerabilities. Does flask-socketio do anything to help prevent that?

    Thank you for the help! :D

  • #147 Miguel Grinberg said 2014-09-03T19:10:43Z

    @Francisco: the extension does not do this for you. You can generate a token for the user when the login through regular routes happens, then if you put that in the user session it will be accessible for verification in the socket handlers.

  • #148 Ivaylo Bozov said 2014-09-23T07:01:39Z

    How to run this on fcgi by lighttpd? It is possible?

  • #149 Miguel Grinberg said 2014-09-23T18:11:47Z

    @Ivaylo: my understanding is that lighttpd does not support websocket, so I don't think it is possible to use it.

  • #150 Ivaylo Bozov said 2014-09-24T07:12:26Z

    Thanks Grinberg,

    I'll try with Gunicorn + Nginx.

    Cheers!

Leave a Comment