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
  • #201 Miguel Grinberg said

    @John: Not sure about the disconnects. This ngSocket library you are using does not appear to be a Socket.IO client, it is plain WebSocket. How did you make it connect to the Socket.IO server?

    The idea of using socket connections is to send data from client to server or from server to client with low latency. If you don't need the server to push data, and if you don't have low latency requirements, then using sockets is probably not a good idea and you should use regular HTTP requests.

    The idea of rooms is not security, it's just a helper to build groups of users that you can address together as a group. To address individual users you put each in a room.

    As far as examples, take a look at the example in the github repository. I believe your code should work, as long as you initialize the SocketIO instance properly.

  • #202 John said

    Of course if I have low latency constraints.
    Regarding the problem, I have instantiated as needed, communication between AngularJS and Flask works.
    However this does not work when I use socket.emit ...
    What seems strange to me is that I have no error, except that the data is not sent.
    Here are the parts of my code:

    <h2>run.py</h2>

    import webapp from app, socketio
    socketio.run (app, host = '0.0.0.0', port = 5000)

    <h2>webapp / init.py</h2>

    app = Flask ( name)
    app.config.from_object ('config')
    Triangle (app)
    db = SQLAlchemy (app)
    socketio = SocketIO (app)
    app.debug = True

    This part works, data is sent and AngularJS received 'Connected'
    @ socketio.on ('connect', namespace = '/ events')
    def test_connect ():
    print ('Client connected')
    emit ('my response', {'data' 'Connected'})

    This part does not cause any error but sends nothing
    @ app.route ('/ data', methods = ['GET'])
    def send_data ():
    "" "Here I start a thread but for example I simplify" ""
    socketio.emit ('appsfiles', {'data': 42}, namespace = '/ events')
    make_response response = ()
    response.status_code = 200
    return response

    <h2>Angular Side</h2>

    var MyApp = angular.module ('sync_in' ['ui.router', 'ngSocket']);

    MyApp.config (function ($ socketProvider) {
    $ socketProvider.setUrl ("http://127.0.0.1:5000/events");
    });
    MyApp.controller ('IndexCtrl', function ($ scope, $ http $ interval, $ socket) {
    $ socket.on ('appsfiles', $ scope, function (data) {
    console.log ('youhou');
    console.log (data);
    });
    $ http.get ('/ data')
    .success (function (data, status, headers, config) {
    console.log ('getted');
    })
    .error (function (data, status, headers, config) {
    $ scope.test = 'failed';
    console.log ('not getted');
    })
    });

  • #203 John said

    @Miguel: About ngsocket library, it is a link error, the correct link is: https://github.com/chrisenytc/ng-socket

  • #204 John said

    Ok, i found the problem....
    In my thread, I have a for loop that sends each entry via emit if I do not put time.sleep (0.1) between each entry nothing is sent ...
    Is this a bug? or security?

  • #205 Miguel Grinberg said

    @John: This ng-socket lib apparently uses Socket.IO 1.2 on the client side. Note that Flask-Socket works with version 0.9 of the client library, this is a requirement of project gevent-socketio which is a dependency, so it would not be unexpected to find issues.
    The sleep is just letting the background tasks run. When you run under gevent tasks are cooperative. I your function never sleeps then the other tasks never get a chance to run.

  • #206 John said

    I have trouble with the links today: https://github.com/Cyruxx/ng-socket
    You're right, this is not the problem of function or timeout ...
    Finally it is angular, which takes too long to connect to flask, and the current function runs before the socket connection.
    I've bothered for nothing :(

  • #207 dexter aparicio said

    i tried to run your code from github, and i encounter the below error:

    python app.py
    * Running on http://0.0.0.0:5000/
    * Restarting with stat
    Traceback (most recent call last):
    File "app.py", line 106, in <module>
    socketio.run(app,host="0.0.0.0")
    File "/usr/lib/python2.6/site-packages/flask_socketio/init.py", line 408, in run
    run_with_reloader(run_server)
    File "/usr/lib/python2.6/site-packages/werkzeug/serving.py", line 631, in run_with_reloader
    return run_with_reloader(args, *kwargs)
    File "/usr/lib/python2.6/site-packages/werkzeug/_reloader.py", line 265, in run_with_reloader
    reloader.run()
    File "/usr/lib/python2.6/site-packages/werkzeug/_reloader.py", line 167, in run
    self._sleep(self.interval)
    File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 75, in sleep
    hub.wait(loop.timer(seconds, ref=ref))
    File "core.pyx", line 474, in gevent.core.loop.timer (gevent/gevent.core.c:8590)
    TypeError: a float is required

    Any idea? Hope to hear from you..Thanks Miguel.

  • #208 Miguel Grinberg said

    @dexter: I have never tested Python 2.6. Any chance you can try 2.7? Also check the version of gevent that you are using and see if it is the same one I have in the requirements file.

  • #209 daixtr said

    i encountered 'a float is required' error and the fix is simply to downgrade werkzeug to 0.9.6. Hope this helps other snake fans out there..

  • #210 John said

    @Miguel:
    I progress in my tests, and I would like to know if there is a way to prevent connection to a user?
    Because currently, everyone can connect to the socket.
    I saw examples which check authentication via Flask on receipt of emit, but is this really the best way (or only)?
    Do you have any advice to implement an authentication system REST before connecting to the socket? (I think it could be a bit more secure right?)

  • #211 John said

    Same error on python 2.7 (Centos), on MacOsx all is good (version component are same)

    • Running on http://127.0.0.1:5000/
    • Restarting with stat
      Traceback (most recent call last):
      File "run.py", line 11, in <module>
      socketio.run(app, host='127.0.0.1', port=5000)
      File "/usr/lib/python2.7/site-packages/flask_socketio/init.py", line 408, in run
      run_with_reloader(run_server)
      File "/usr/lib/python2.7/site-packages/werkzeug/serving.py", line 631, in run_with_reloader
      return run_with_reloader(args, *kwargs)
      File "/usr/lib/python2.7/site-packages/werkzeug/_reloader.py", line 265, in run_with_reloader
      reloader.run()
      File "/usr/lib/python2.7/site-packages/werkzeug/_reloader.py", line 167, in run
      self._sleep(self.interval)
      File "/usr/lib64/python2.7/site-packages/gevent/hub.py", line 75, in sleep
      hub.wait(loop.timer(seconds, ref=ref))
      File "core.pyx", line 474, in gevent.core.loop.timer (gevent/gevent.core.c:8590)
      TypeError: a float is required
  • #212 daixtr said

    hi again, i'm beginning to get serious into this flask-socketio thing. So I noticed, that I need to put 'from flask.ext.socketio import SocketIO,emit' at the very first line. Otherwise, weird things happen such as events-generated-but-not-propagated-to-clients. Perhaps, by briefly mentioning this quirk or explain it so others will be aware of it. Although, i think i saw this got mentioned in youtube by Calvin Ching https://www.youtube.com/watch?v=optUfH7oq8Q .Nevertheless, it took me an hour of head-scratching, and version checking all dependencies and comparing. Thank you.

  • #213 Miguel Grinberg said

    @daixtr: interesting. Were you using Werkzeug 0.10 before?

  • #214 Miguel Grinberg said

    @daixtr: try adding the gevent monkey patching at the top, not the import of SocketIO and emit. I believe that is what's giving you trouble. This is not required for every project, but depending on how your application is structure and what dependencies it has, you may need the monkey patching.

  • #215 Miguel Grinberg said

    @john: looks like the latest Werkzeug release causes this, according to @daixtr, see comment above. Downgrade to 0.9.6 to avoid the problem.

    Regarding authentication, I agree that this is best done outside of the socket connection. Once you authenticate the user you can give access to socket connection.

  • #216 John said

    @all: Werkzeug-0.10.1 solves problem with float

  • #217 John said

    I'll try to summarize my questions in one post to prevent spam: p
    @Miguel:
    1. Do you think Socket.IO in the version that works with Flask would pass binary data? (eg send a sound or picture?) My question is based primarily on the fact that the protocol is designed for it or not? (I read in previous posts that you do not counseled that the Protocol is dedicated to short messages but socket.IO 1.0 now provides a function that supports binary data).
    2. About authentication, the idea that you seem to suggest would be to retrieve the token via API, and pass this token via socket.io. Ok but this implies returning the token to each message sent no?

    I work for a while on a data synchronization with a desktop app client and my idea is to use socket.io as transmission channel between the clients as well as server.
    What would be your advice? Transfer files via API? socket.IO?

    1. In the event socket.IO would be appropriate in my case, do you think Flask-SocketIO will evolve to support binary data function?
  • #218 Miguel Grinberg said

    @John: 1. there is no direct support for binary data, but you can encode binary data to be able to send it as strings, for example using base64 encoding.
    2. socket.io is special in that once the connection is established there is no need to re-authenticate. If you authenticate the user and then include a token or user id in the user session, then you can check this data from the socket connect handler. You can re-verify the identity on all subsequent events if you like as well.
    3. As I said above, I would not depend on the framework, just use base64 encoding, which will work on the current and any future releases.

  • #219 John said

    @Miguel: I work with binary large (2 to 4 GB), the base64 encoding is suicidal (consumer ram / cpu + 33% of the real size).
    The best method would be to send chunk by chunk data but apparently there is no real solution.

    Regarding authentication, was the idea of connecting to socketio via a client application that uses this kind of tool : https://github.com/invisibleroads/socketIO-client and not between the browser and the server.

  • #220 Miguel Grinberg said

    @John: the idea of using websocket is to have low latency messages. With messages that are that big I really don't see the benefit. The overhead of sending a regular HTTP request is going to be in the noise, so I would not complicate things adding websocket.

    If you are not using a browser, then what you said earlier makes sense. Get a token through a regular HTTP based API, then send the token back right after connecting.

  • #221 Justin said

    Hi Miguel, thanks for releasing this library having good fun using it to implement live streaming results in my web game.

  • #222 Jerry said

    @Miguel. Wonderful how simple your solution is. For my application, I currently use Flask to control a sensor. I'd like to stream the sensor updates to multiple Andriod clients. Would you recommend Flask-SocketIO for this or would you recommend something else? Currently my Android client polls Flask for the sensor data (as a JSON), and with multiple clients this is not efficient. A websocket solution seems best with the server broadcasting the sensor data to the clients. Is Flask-SocketIO a good match for this?

  • #223 Miguel Grinberg said

    @Jerry: Yes, that is an ideal solution for you. I imagine your sensor data is a small payload, so by having clients poll you have a lot of overhead from the headers of the requests and responses. With WebSocket, once you establish a connection, you just transmit the payload, there is no extra wrappers. Good luck!

  • #224 Karthik said

    Hi Miguel,

    Thanks for the tutorial on flask-socketio. One major question I have is how do we handle socket connections distributed over multiple servers load balancing them and doing a broadcast. For example: I have two servers (S1, S2)handling socket connections and I need to broadcast a message to all the clients connected to a particular namespace on S1 and S2 on receipt of an event on S1.

    I found a similar question on SO in here - http://stackoverflow.com/questions/18267314/socket-io-connections-distribution-between-several-servers

    Thanks,
    Karthik

  • #225 Miguel Grinberg said

    @Karthik: the gevent-socketio package (in which my extension is based) does not currently support multiple servers, as it keeps state in memory. And my extension does the same thing, so this is a limitation, you will need to use one server. I believe there is a fork of gevent-socketio that writes the state to disk (redis if I remember correctly), and that enabled it to support multiple servers with a load balancer. If this ever gets officially released I will gladly add support for it.

Leave a Comment