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

    @Aron: gevent's port to Python 3 is incomplete, it does not currently work. Also note that gevent-socketio, another dependency, also needs to be ported, though it is much smaller than gevent, so once gevent is ported the rest is easy.

  • #177 Aron Kunze said

    Ah ok thank you, then I will have to port to Python2.

  • #178 Andrey Nechypurenko said

    Thank you very much Miguel for useful post!

    To me it seams like gevent is not going to be ported to Python3 soon. With this regard I am wondering what do you think about the idea to to (re)implement flask-socketio using asyncio instead of gevent?

    Thanks,
    Andrey.

  • #179 Miguel Grinberg said

    @Andrey: not a bad idea, but Flask does not run on asyncio, so you would need to port Flask first.

  • #180 James Harrison said

    Hi, Just regarding the previous post from Andrey, this may be interesting to you https://github.com/mrdon/flask

    I have a question regarding emiting data to certain users. I used to have something like this and it worked OK but everything was in the main server.py file.

    def emit_user(uid, room, handler, data):
    for sessid, socket in request.namespace.socket.server.sockets.items():
    if int(socket['/game'].session['id']) == int(uid):
    socket['/game'].base_emit(handler, {'data': data}, room=room)

    I decided to organise my project like your flask-socketio-chat repo and all works fine except i can't seem to emit to certain users any more. I can emit to everyone of just to a room, but not to a user.

    from .. import socketio

    <h1>this works</h1>

    def send_to_room(handler, data, room):
    socketio.emit(handler, data, room=room, namespace='/game')

    I found that i can't use the request.namespace.socket.server to loop through each user so i add these to a dict (because im out of context in a separate function) on connect, now i can get my matches but i can't emit to a single user.

    <h1>emit fails on</h1> <h1>socket_namespace is created on connect</h1>

    def send_to_user(handler, data, table, user_id):
    for sessid, socket in socket_namespace.items():
    if int(socket['/game'].session['id']) == int(user_id):
    print 'we have a match'
    socket['/poker'].socketio.emit(handler, data, room=table, namespace='/game')

    with the error
    AttributeError: 'SocketIO' object has no attribute 'base_emit'

    Any ideas? Is it because the way i'm creating socket_namespace

  • #181 James Harrison said

    HI,

    Or guess an easier way of asking this question. With the Flask-Socketio-Chat repo, how would i add a method to emit to just one user.
    I tried adding request.socket on connect but then I can't call socket['/test'].base_emit(handler, {'data': data}, room=room) like I would normally.

  • #182 James Harrison said

    Hi, Not to worry too much about my questions. I saw your comment on stackoverflow this morning about putting each user in a room so i've managed this like that.
    Thanks

  • #183 Miguel Grinberg said

    @James: my opinion is that it isn't a good practice to mess with the internal data structures, as those are not guaranteed to stay the same. My solution to address individual users is to put each user in a room, then you can target the user's room. That way you let the extensions handle the data structures for you.

  • #184 Arnon said

    Hi Miguel, awesome work with this one... :)
    I can't believe how little code I have to write for synchronizing browsers now - it's amazing!

    That said, there are a few issues I came across when trying to re-implement the chat-example using flask-socket-io:

    1. I see that you are not using the 'Mixin' classes like BroadcastMixin and RoomsMixing (https://gevent-socketio.readthedocs.org/en/latest/mixins.html#mixins-module).
      Is there a reason for that?
      The BroadcastMixin provides a 'broadcast_event_not_me' method, that is very useful for many use-cases (like a chat), that send to all of the sockets 'except' the originator of the broadcast - that ability is missing in your implementation.

    2. I found the whole story around 'request' and 'session' objects semantics very confusing, as there are objects with equivalent names in the gevent-socketio implementation underneath, which have a different semantics. The documentation you added for this is helpful, but still not clear enough, and while implementing the chat, I was getting weird and unexpected behavior, as well as occasional crashes of the server.
      The way I currently understand it is as follows:
      On the gevent-socketio level, they say that the 'Socket' class is instantiated once per socket-connection with a client (we'll refer to that object-instance as the 'connection-socket'), and then a 'session' object is a 'singleton' for this entire connection-socket, and bound to it's lifetime. The Names classes are then instantiated once per each namespace that is used by the client over that connection-socket, in object-instances they call 'virtual sockets', and that this 'session' singleton-object of that connection-socket is then made available in each of these 'virtual-sockets' (Namespace instances). To get to that 'session' object, I guess I could do either 'request.namespace.session' or 'request.namespace.socket.session'. This is the place to hold state for a given connection-socket, that would be shared across all of it's virtual-sockets.
      Conversely, they suggest a 'request' object be used on the 'virtual-sockets (*Namespace-instances), as representing the request object that came from the external framework (like flask), and that would be bound to whatever lifetime is relevant for that framework - is this the 'request' global you are 'injecting' to 'from flask import request'? What are the recommended use-cases for this object?
      How is the life-time of this object differ from the socket-io one?
      And what about the 'session' global you're also injecting - what are the use-cases for it? And how do THEY differ from the use-cases of the socket-io 'session' object? How do THEIR life-time/accessibility differ?
      And what about 'g' and 'current_app' ? How do you use those and for what use-cases? (You can see I'm not a flask user...) Should I use one of THOSE for app-context-related-state? When I tried to do this for storing the 'nicknames' list of the 'chat' example, it crashed the server because they didn't exist for it in the lifetime-context of their use in the handlers - perhaps I used/imported them incorrectly? I ended up just using a 'module' level variable for this kind of app-level (cross-socket) state, but it feels like I shouldn't be doing that...

    Here is the 'chat' example I was using as a reference:
    https://github.com/abourget/gevent-socketio/blob/master/examples/simple_chat/chat.py

    And here is my implementation using flask-socketio:
    from flask import Flask, request
    from flask.ext.socketio import SocketIO, emit, send, join_room, leave_room

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

    @socketio.on('nickname', namespace='/test/chat')
    def handle_chat_nickname(nickname):
    request.namespace.session['nickname'] = nickname
    nicknames.append(nickname)
    join_room('main_room')
    emit('announcement', ('%s has connected' % nickname), broadcast=True)
    emit('nicknames', nicknames, broadcast=True)

    @socketio.on('disconnect', namespace='/test/chat')
    def handle_chat_disconnect():
    nickname = request.namespace.session.get('nickname')
    if nickname:
    nicknames.remove(nickname)
    leave_room('main_room')
    emit('announcement', ('%s has disconnected' % nickname), broadcast=True)
    emit('nicknames', nicknames, broadcast=True)

    @socketio.on('user message', namespace='/test/chat')
    def handle_chat_message(message):
    emit('msg_to_room', message, room='main_room', broadcast=False)

    It works, but there are 2 things that bother me:
    1. The chat messages appear on the client twice, as they are 'broadcasted' back to the originator, in addition to him showing it's own message (this is due to the lack of 'not_by_me' broadcast method).
    2. I use a module-level global for cross-socket state - it feels like I should be using something else, but couldn't get any other option (with flask-globals) working correctly...

  • #185 Miguel Grinberg said

    @Arnon: answers to your questions:

    1. You are correct, I'm not using the mixins from gevent-socketio. My goal when I wrote this extension was not to expose gevent-socketio to Flask apps transparently, instead I'm using gevent-socketio as an auxiliary library. The interface that I provide is not based on gevent-socketio. But of course you can always use gevent-socketio directly if that works better for you. In particular, I have not implemented the "broadcast to everyone but myself". You can easily filter messages by including the id or name of the originator in the payload. I do intend to implement this feature soon, it's been requested several times.
    2. Along the same lines, gevent-socketio and Flask-SocketIO do not share the same API. My choices in Flask-SocketIO are to make the use of sockets easier to Flask applications, I did not try to preserve the features of gevent-socketio, instead I tried to follow the Flask design as much as possible.

    Regarding your chat code:

    1. If you not only send the message but also the nickname, then the client can only show messages that do not match the user's nickname.
    2. A module global variable is perfectly fine for gevent, since you will be using a single worker process.

    Also, see https://github.com/miguelgrinberg/Flask-SocketIO-Chat, my own attempt at a simple chat app.

  • #186 Arnon said

    Ok, 10x

    BTW: Your chat-implementation lacks a feature: If you join to a chat-room that already has other users already logged-in, you won't see their names because the server doesn't hold the state of which users are currently joined. You'll only get notified on new users that connect/disconnect after that... :)

  • #187 Frank Bolton said

    Hi Miguel,
    Great work with this flask-socketio and your superb tutorial set / book.
    I'm trying to deploy my project to a new Ubuntu server and I am getting errors on the installation of flask-socketio

    $ pip install flask-socketio
    response is shown in http://pastebin.com/Qv2hW92R

    $pip --version
    pip 6.0.6 from /home/ubuntu/flask_ktz/venv/local/lib/python2.7/site-packages (python 2.7)

    Are there any changes on the pip listing or somewhere else that would indicate why this is failing?

    Thank you
    Frank

  • #188 Miguel Grinberg said

    @Frank: you are missing the python-dev package, this is necessary to compile Python extensions written in C/C++.

  • #189 Luis Otero said

    Hi Miguel,
    Many thanks for this tutorial!
    Can I use the Flask-SocketIO extension inthe Google App Engine?

    Thanks!,
    -luis

  • #190 Miguel Grinberg said

    @Luis: I would think so, as long as they support WebSocket in their instances.

  • #191 Luis Otero said

    Hi Miguel,
    Many thanks for your answer, and sorry for the multiple emails.
    It seems like GAE does not support WebSocket (see http://code.google.com/p/googleappengine/issues/detail?id=2535).
    Do you know of an alternate framework that you would recommend to achieve bi-directional communication?

    Thanks!,
    -luis

  • #192 Miguel Grinberg said

    @Luis: SocketIO can fall back to long-polling, flash or other mechanisms to achieve two-way communication, so in theory you should be able to use Flask-SocketIO if your are okay using some of these alternative mechanisms.

  • #193 Manuel Godoy said

    As usual, nice tutorial.
    I am trying to use Flask-SocketIO in my Google App Engine application, and am hitting exactly this same issue (http://stackoverflow.com/questions/22673368/cannot-find-module-greenlet).

    According to their answer, there doesn't seem to be a way out since the greenlet library cannot be loaded into GAE.
    Can you share some insights into how can I overcome these difficulties?

  • #194 Miguel Grinberg said

    @Manuel: if they don't let you run greenlets then you are out of luck, as that is a requirement for gevent. You will need to find another platform. It's really surprising that this is not possible.

  • #195 ramin said

    this is a great tutorial. thanks for that.
    however when I try to write my own little ws server and install Flask.socketIO gevent is of version 1.0.1
    and I get the error msg

    ...
    File "core.pyx", line 474, in gevent.core.loop.timer (gevent/gevent.core.c:8590)
    TypeError: a float is required

    when starting the server. I can't install version 1.0
    ...
    Could not find any downloads that satisfy the requirement 1.0

    Am I doing something wrong or is there a problem with gevent 1.0.1.
    Can someone give me a hint.

    cheers

  • #196 Miguel Grinberg said

    @ramin: what was the problem with installing version 1.0?

  • #197 John said

    Hello Miguel, before all => Nice Job !
    I've an answer for you :)
    I use emit() in a common function and got this error:
    socketio.emit('apps.files', file_hash, namespace='/events')
    File "/Library/Python/2.7/site-packages/flask_socketio/init.py", line 441, in emit
    return request.namespace.emit(event, args, *kwargs)
    File "/Library/Python/2.7/site-packages/Werkzeug-0.9.6-py2.7.egg/werkzeug/local.py", line 338, in getattr
    return getattr(self._get_current_object(), name)
    AttributeError: 'Request' object has no attribute 'namespace'

    What's my error ? :(

  • #198 Miguel Grinberg said

    @John: The "emit()" function can only be used in a socket handler. Outside of a socket handler you have to use "socketio.emit()", where "socketio" is the instance of class "SocketIO".

  • #199 John said

    Thank's, i'll try this during the day.
    I've another question, i use angularjs in my Flask project with ngSocket (https://github.com/angular/ngSocket), an implementation among other socket.io.
    I tested many of these implementations, and i observed during session; flask-socketio received disconnect regularly.
    There is a timeout session ? It's to keep alive the session (between client/server) ?
    My last question, i use socketios between flask and angular to communicate and to send data; is this a good idea?
    Do I compartmentalize each user in a room, for more security?

  • #200 John said

    @Miguel : Can you provide an example about instantiation ?
    Because this doesn't work (and in flask route too) :

    from flask.ext.socketio import SocketIO
    socketio = SocketIO()
    socketio.emit('apps.files', {'data': 42}, namespace='/events')

Leave a Comment