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

  • #251 Miguel Grinberg said 2015-08-06T00:28:23Z

    @James: gevent-socketio (a dependency of my extension) does not support horizontal scaling. So at this time you can only have a single socketio server. You can scale your web app, but you need to route all your socket traffic through a single host. The problem is that broadcasting requires knowing all the users, so each host will have a partial list of users and will not be able to broadcast properly.

    I'm working on a new version of Flask-SocketIO that will not depend on gevent-socket (which isn't maintained anymore). I plan to add support for a Redis backend that can be used to support broadcasting with multiple servers.

  • #252 Epic Jefferson said 2015-08-07T06:54:28Z

    Hey Miguel! I want to combine Flask-SocketIO with https://github.com/rochacbruno/Flask-GoogleMaps/

    the setups are very similar

    socketIO is: from flask import Flask, render_template from flask.ext.socketio import SocketIO

    app = Flask(name) app.config['SECRET_KEY'] = 'secret!' socketio = SocketIO(app)

    if name == 'main': socketio.run(app)

    --and flask-googlemaps is: from flask import Flask from flask_googlemaps import GoogleMaps

    app = Flask(name) GoogleMaps(app)

    if name == "main": app.run(debug=True)

    This causes a conflict and I can't figure out a way to combine them gracefully, would you have any suggestions?

  • #253 Miguel Grinberg said 2015-08-07T16:14:48Z

    @Epic: what conflict do you get?

  • #254 Cesco said 2015-08-09T03:37:45Z

    Do you have a working example of a Python Websocket client that works with the extension? I cannot seem to find a solution that contemplates namespaces and events. thanks @Miguel

  • #255 Miguel Grinberg said 2015-08-09T18:15:28Z

    @Cesco: Why do you need a Python client? Typically it is a Javascript application running in a browser that is the client. I don't think there are any compatible Socket.IO clients written in Python. It's certainly possible for one to exist, but I'm not sure there is a use case. If you have a Python client and a Python server, it's less complicated to use regular sockets to make them talk.

  • #256 Cesco said 2015-08-18T18:06:43Z

    @Miguel as usual usual, you are right. I took the regular socket and it is working perfectly on the arduino side. Although I haven't implemented any routes to send/receive data from the sockets. I have been doing all on the CLI. I think that because of the blocking nature of socket communication, I must to do it with celery, right?

  • #257 Miguel Grinberg said 2015-08-20T06:13:29Z

    @Cesco: Celery is one option, but not the only one. You can use one of the async frameworks, gevent or eventlet are good choices.

  • #258 Vipul said 2015-08-20T11:30:48Z

    Hello Miguel,

    I went through your tutorial for WebSockets. And it is working as explained. On Below things i need clarification: 1. I executed python app.py then html page gets render correctly. 2. To stop the server-side websocket, i did Ctrl+Z which took me to the console prompt (which is an abrupt termination of server). 3. To restart server i again execute python app.py but this time Binding Address already in Use message appears and template didn't render on refresh.

    I want to know how to overcome this issue.

    Thanks, Vipul

  • #259 Miguel Grinberg said 2015-08-21T04:24:34Z

    @Vipul: Ctrl-Z does not stop the server, it just suspends it. To stop the server use Ctrl-C.

  • #260 Vipul said 2015-08-21T15:11:27Z

    @Mguel: Thanks for the quick update. Your suggestion helped me. Below additional question i do have: 1. I ran the websocket server with ip as 0.0.0.0. I want the websocket server to serve two different ip (like. 10.216.xx.xx and 192.168.xx.xx). Does websocket configuration serve this purpose (i.e. two different networks)? 2. Any way to configure the websocket server timings and handling only limited client connections? 3. Apart from websockets, any example/tutorial (of yours) to configure the lighttpd proxy and user-authentication?

    Thanks, Vipul

  • #261 Miguel Grinberg said 2015-08-21T21:50:41Z

    @Vipul: 1. When you say 0.0.0.0 you are making the server listen on all interfaces. If you have two networks, then both will be listening. 2. See the documentation for the websocket server options, which include timeouts (note these options are going to change in Flask-SocketIO version 1.0). To only accept specific clients you have to validate the clients yourself and disconnect those that you don't want. 3. I have basic setup for lighttpd in the mega tutorial, on the article dedicated to deployment.

  • #262 Alex said 2015-09-12T03:16:28Z

    Hi, thanks for putting together the flask tutorial and for sharing flask-socketio. While I am at beginner level in python/Web programming I have a keen interest in adding a real time feature to my project. I am using python 3.4 though and I believe gevent has a beta using python 3.x. How to update flask-socketio to use with Python 3.x?

    I am afraid my project, using python 3,will clash with socket.io as it is currently supporting only pyhton 2.7.

    Thanks!

  • #263 Miguel Grinberg said 2015-09-12T07:16:52Z

    @Alex: Please give version 1.0a of flask-socketio a try. This is a pre-release, but this version runs on Python 3.4, and you have the choice to use gevent or eventlet. See http://blog.miguelgrinberg.com/post/flask-socketio-needs-your-help for additional details.

  • #264 crowdstar said 2015-09-24T21:46:29Z

    After banging my head against the wall for a few days I realized my antivirus was causing the websocket connection to fail. I would see the 101 changing protocol message, the websocket would say pending but never communicated. It would eventually fail to polling. I couldn't find any error messages. So FYI if you are stuck try disabling your antivirus.

  • #265 Miguel Grinberg said 2015-09-25T04:20:10Z

    @crowdstar: Interesting. First time I heard of this type of problem. What antivirus are you using, out of curiosity?

  • #266 mushfau said 2015-09-25T13:35:22Z

    im getting this error, please help

    Traceback (most recent call last): File "server.py", line 44, in socketio.run(app, port=3000) File "/usr/local/lib/python2.7/dist-packages/flask_socketio/init.py", line 303, in run self.server = socketio.Server(**self.server_options) AttributeError: 'module' object has no attribute 'Server'

  • #267 Miguel Grinberg said 2015-09-25T23:17:57Z

    @mushfau: The 1.0 pre-releases have different dependencies. Please create a separate virtualenv for 1.0.

  • #268 russ said 2015-09-27T23:32:05Z

    Two tips for anyone having troubles getting the example to work:

    I initially couldn't get to example to work because my pip was at version 1, and was installing v1.0b1 of Flask-SocketIO which was breaking things. ("pip install --upgrade pip" to upgrade)

    I've created a Dockerfile here which installs everything need to run the sample, and patches the app.py to server on "0.0.0.0": https://gist.github.com/russau/45b379d12cf594d52da1

    Thanks for creating this!

  • #269 John Markham said 2015-10-01T14:41:44Z

    Hello Miguel, I would love to know what your opinion is on REST vs Websockets? Surely the Websockets client-server pattern supercedes the need to use REST, no?

    Instead of GET, PUT, POST, DELETE, you can establish a line from client to Server with Websocketers, then make a request to the Server, the server can now throttle the data output back to the client, or indeed the client can initiate smarter requests like, hey send me the first 50 records for this query, and most likely I'll need the next 50 in the next 2 minutes, so please cache the data.

    Thanks, John.

  • #270 Miguel Grinberg said 2015-10-02T04:46:04Z

    @John: WebSockets are expensive, you need a dedicated connection for each client. So you are forced to use an asynchronous server framework to be able to be able to support all your clients. On the client, it is way easier to send HTTP requests than to use WebSocket, more so if the client is not a web browser (iPhone, Android, etc.). Also, HTTP has very solid caching mechanisms that can save considerable bandwidth, whereas with WebSocket you just get the raw socket, any optimizations you have to implement yourself.

    These are just a few reasons why a REST API may make more sense than WebSocket for many applications. My opinion is that WebSocket and Socket.IO are good for applications that exchange lots of small messages with low latency between the server and the client. If the needs of the application aren't demanding, the simplicity and robustness of HTTP is more adequate.

  • #271 raoul ducorbo! said 2015-10-19T08:51:29Z

    Hi Miguel, thanks a lot for your flask tutorial and this useful socketio extension !

    I use socket IO to inform web user during the copy of several files to usb drive (progress form). Here is the scheme : Web client emit a copy message and open a progress window onMessage, Web server launch the treaded copy the thread is copying files one by one, emitting a message to inform the progress form: socketio.emit('progress', {'data': {'current': i, 'max': max, 'pict': pict}}, namespace = '/usb')

    The pb is that messages seems to be delayed at the end of the copy process, not sent progressively.

    Is this the normal way ? Is there a way to make something like a flush to force messages to be sent right now ? or may be i did something wrong...

    thanks ! RD.

  • #272 seb5s said 2015-10-20T15:47:39Z

    Thanks for a great article. I had som troubles installing 'gevent' I got the following error message when trying to install it with pip on osx: command 'cc' failed with exit status 1

    Turns out you can use gevent==1.1b3 in requirements.txt instead :)

  • #273 Miguel Grinberg said 2015-10-22T05:06:43Z

    @raoul: the server runs on coroutines when you work with Flask-SocketIO. If you need background processing to work you need to release the CPU every so often. You can do this by adding a time.sleep(0) call, for example.

  • #274 Kamal said 2015-10-29T15:18:59Z

    Hi, I don't know if this tutorial is old or still applicable, I just downloaded your code, followed your steps, but when I tried to run : python app.py

    I got this :

    python app.py async_mode is threading WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance. * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat async_mode is threading WebSocket transport not available. Install eventlet or gevent and gevent-websocket for improved performance.

    THanks, your help is appreciated.

  • #275 Kamal said 2015-10-29T16:22:12Z

    The index.html on the download has nothing to do with this tutorial, it looks like a chat application, can you please direct me to the source code of this tutorial. thanks

Leave a Comment