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

  • #151 Ivaylo Bozov said 2014-09-27T13:55:26Z

    Hi again,

    I run app by gunicorn but not work. Error in firebug console is: NetworkError: 500 Internal Server Error - http://localhost:5000/socket.io/1/?t=1411825756431

  • #152 Miguel Grinberg said 2014-09-28T06:10:49Z

    @Ivaylo: please run the application in debug mode and get a stacktrace on the server side, as that will give you a clue of what the problem is.

  • #153 Ivaylo Bozov said 2014-09-28T07:54:19Z

    Solve problems with this:

    gunicorn --worker-class socketio.sgunicorn.GeventSocketIOWorker module:app

    https://github.com/miguelgrinberg/Flask-SocketIO/issues/16

  • #154 Vayel said 2014-09-28T09:33:09Z

    Excellent !

    I also met the problem : No handlers could be found for logger "socketio.virtsocket".

    It was solved when I used no namespace.

  • #155 Joshua said 2014-10-12T14:59:18Z

    Hi there, Great job integrating gevent-socketio with flask. I do have a question. Is it possible to run more than one gevent-socketio server and load balance across multiple cores/servers? e.g. haproxy Thanks

  • #156 Miguel Grinberg said 2014-10-13T05:57:53Z

    @Joshua: Good question. Unfortunately gevent-socketio keeps state in the Python process, so it cannot scale horizontally. An possible strategy could be to scale horizontally below Flask-SocketIO.

  • #157 Patricio said 2014-10-13T14:53:58Z

    Hi Miguel, I am starting to rewrite some code and would be happy to achieve it with Flask and FlaskIO. I need some advice. The app is basically a live-capture (webcam and few other acquisition devices) system running on a PC, and the UI would be a webapp. In the UI one should be able to control the acquisition devices and have it's live preview thing (like live webcam etc...). Do you know if it is possible through a websocket to stream video (I saw mjpeg or simply reloading jpegs could do the work but maybe html5 and/or websockets allow much better latency/image quality) ? Do you know if that app could be hosted somewhere and still control a localhost FlaskIO rendering and controlling the devices (I think crossdomain scripting should avoid that, but found in the past workarounds using flash and crossdomain.xml hacks) ? Thank you very much !

  • #158 Miguel Grinberg said 2014-10-15T06:18:21Z

    @Patricio: WebSocket enables you to send anything you want, but you need to write the client-side code to handle the data. In particular for Motion JPEG there is another alternative, which is to have a route that serves a multipart stream. All major browsers can handle this and play the sequence of jpeg images. This is used by a lot of security cameras. As far as cross-domain issues, look at CORS. You need to add some headers in responses to enable the cross-domain support.

  • #159 Dave said 2014-10-15T16:31:48Z

    Hi Miguel, I've got a Flask-SocketIO server running but I'm not sure I'm handling authorization correctly. Could you clarify a couple things (perhaps fundamental) I'm having a hard time understanding? Hopefully it will also help someone else.

    I'm using HTTP Basic authentication to validate a browser connection/page load before loading socketio.js (0.9.16 due to current gevent restrictions as you point out in the docs). However, I'm wondering about possible non-browser websocket connections (my server may have this business requirement)? Do ws:// connections always start out as http:// connections and then get "upgraded"? If so, then would/could HTTP (Basic, etc.) authentication still be used? Or, can a client directly connect to the Flask-SocketIO websocket server using ws:// without HTTP? If so, what is the recommended authentication method in that case? I've seen examples of Node-based socketio servers (0.*) an ability to call io.set('authorization', callback(...) {}) - can something similar be done with Flask-SocketIO?

    I did see a question in the Flask-SocketIO area on GitHub where you mentioned to a questioner: "The easiest way is to do authentication outside of the socket connection and record the authenticated state in the user session. Socket handlers can see the session, so all they need to do is get the user from there." That sounds like HTTP Basic authentication would work, but would it work for non-browser websocket connections?

    Sorry if these are naive or stupid questions, but it's been frustrating to figure out even the core questions I'm trying to ask to get guidance. Thank you so much for a great tool (Flask-SocketIO) though. I needed a Python solution instead of Node, and this fits the bill terrifically...!

  • #160 Miguel Grinberg said 2014-10-16T06:16:44Z

    @Dave: not a stupid question at all. There are a couple of points to clarify.

    To enhance the answer you referred to, let me say that I think authentication needs to happen outside of the socket channel. You can use a regular HTTP route to authenticate and obtain a token, which can then be used in the socket channel.

    The other important point to note is that SocketIO is not a WebSocket server. So you will not be able to connect at ws:// with a regular WebSocket client. You will need to use a SocketIO client, which in turn may internally go from http:// to ws:// if it decides that is the best approach to take.

  • #161 Patricio said 2014-10-16T20:55:23Z

    @Miguel: I thought about using something like flxhr ( https://code.google.com/p/crossxhr/wiki/CrossXhr ) for crossdomain stuff but for the live video is there something vastly used in html5 that I could stick websockets with ?

  • #162 Miguel Grinberg said 2014-10-17T06:00:08Z

    @Patricio: this project you pointed at uses Flash, not something I would recommend since not everybody has a Flash plugin installed. As I said before, learn about CORS which is supported by all major browsers.

    I don't think there video over WebSocket protocol.

  • #163 frank bolton said 2014-10-19T21:03:21Z

    Hi Miguel I'm busy porting some node.js code to Flask, and it's got a socket.io component. I've worked through your example code with joining nad leaving rooms, but now I need to get a unique identifier for the socket. In node.js the syntax reads as follows: if(rooms[data.uid].indexOf(socket.id) > -1){

    I have a problem porting the socket.id to something python-esque. There is a similar example in this article. http://chrissilich.com/blog/socket-io-0-7-sending-messages-to-individual-clients/ Hopefully it's a syntax thing that I'm missing and not a gap in the library :)

    PS... your book and blog are awesome and I've been learning a lot from them

  • #164 Miguel Grinberg said 2014-10-19T23:21:48Z

    @frank: for low-level access to the SocketIO structures on the server I recommend that you read the gevent-socketio documentation. You can access the "namespace" object as request.namespace, from there you can get the socket.

  • #165 Gordon said 2014-10-22T09:34:06Z

    Tihs is great.

    i want to connect to the websocket server from a standalone python script. initally I tried websocket-client 0.21, but I'm not sure which ws:// address to use to create a connection hanlde, then simply send a message.

  • #166 Miguel Grinberg said 2014-10-22T21:15:52Z

    @Gordon: Flask-SocketIO does more than plain websocket, you have to use a SocketIO client to connect to it. If you want just websocket then have a look at Flask-Sockets.

  • #167 Amit said 2014-11-04T12:07:48Z

    Thank you Miguel.

    Itis working fine but a small issue why it is printing

    "Received #0: Connected Received #1: I'm connected! Received #2: Server generated event Received #3: Server generated event Received #3: YO Received #4: Server generated event Received #4: hey brother how are you Received #5: Server generated event"

    Why server messages insted of just messages.

  • #168 Miguel Grinberg said 2014-11-05T02:27:31Z

    @Amit: you need to look at the example applications. The server generated events demonstrate how the server can be the originator (i.e server push).

  • #169 Evan said 2014-11-11T18:45:01Z

    Miguel, Thanks again for another great piece of code. I was able to use your mega tutorial to get started in flask, and be pretty successful for a web programming newbie.

    I'm attempting to write a "multi-headed" digital photo frame system using a flask as a server and your socket.io extension.

    Eventually I intend to run the server on one machine, and push images from my photo library out to as many clients as are connected (9 x raspberry pi's to begin with, each booting to a full screen browser).

    The beauty of this approach is that any browser on any hardware can become a client, and will not need pre-acquired access to the photo library.

    To accomplish the image "push" from the server, I morphed this example code and I'm able to encode the image in base64 and push it to the client. I'm still trying to get a handle on exactly handle on the best way to do all this...

    My question is, I'm dealing with quite a bit of sluggishness pushing the images since they are around 5M each (1920x1200). Is there a better (supported) way to transfer the images to the client so they stay in true binary and never get converted to a base64 string for transmission? Would it be better to simply provide the static URL of the next image so the client could load it?

    I'm also getting "client disconnected" messages to the console moments after a client connects, which I've yet to track down. Though everything continues to work normally after that.

    Any help is appreciated, the code on github should run (port 4000) if you have a chance to give it a look. I'm only 4 hours in, and I've overcome many technical hurtles thanks to your code.

    (https://github.com/evanmj/framex0r)

  • #170 Miguel Grinberg said 2014-11-12T07:36:14Z

    @Evan: I'm not sure your application is a good use case for websocket. You are transmitting large blobs of binary data, while sockets are good for short messages with low latency. Have you considered using regular HTTP requests over Ajax? Pictures will then download as binary files so you will save some bandwidth.

    The disconnect messages that you get are (probably) the automatic disconnect messages from your previous session. This happens when you have a browser connected and then navigate away from the page or hit refresh.

  • #171 Mattias said 2014-11-21T15:38:30Z

    Is it possible to, from server side, access the list of rooms that are currently open/active? I want a background script to run (and transmit datA) while a room is filled with at least one client.

  • #172 Miguel Grinberg said 2014-11-23T00:31:35Z

    @Mattias: Yes, you have two ways to do this. (A) You can read Flask-SocketIO's source code to figure out how I store the rooms in the SocketIO object; or (B) create your own data structure with the list of rooms and update it as people enter/leave rooms. My recommendation is to go with option (B).

  • #173 Amit said 2014-11-23T15:27:29Z

    Hi miguel, seems like gevent is not getting python3 support, do you have any plans to extend it for python3?

    Till the time it get python3 support, maybe you can add a note to this post pointing to a good python 3 websocket.

  • #174 Miguel Grinberg said 2014-11-23T23:15:16Z

    @Amit: as far as I know, there is no readily available solution to run a SocketIO application on Python 3. There's been some efforts to get WebSocket servers (which have less functionality than SocketIO), but to my knowledge there has been no effort to integrate them with Flask.

  • #175 Aron Kunze said 2014-12-04T00:47:41Z

    Hey Miguel, I'm a bit confused as far as I know there is gevent for Python 3, I can install it via pip. Is there some other reason Flask-Socketio does not work for python3? Would be great if you could get it working for Python3 as I need it for a project I don't want to port back to Python2.

Leave a Comment