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

  • #76 Miguel Grinberg said 2014-04-14T18:20:38Z

    @Lars: Does it work if you remove the namespace='/MyUrl' from your emit? A namespace is not the same as a route this is a socketio thing that enables you to multiplex several connections through a single channel.

  • #77 samir said 2014-04-14T22:45:05Z

    I ran the command but I still have the same problem. an error occurs on the server , Here the capture screen error: File "/usr/local/lib/python2.7/dist-packages/socketio/init.py", line 67, in socketio_manage socket = environ['socketio'] KeyError: 'socketio' {'GATEWAY_INTERFACE': 'CGI/1.1', 'HTTP_CONNECTION': 'Close', 'HTTP_HOST': '127.0.0.1:8000', 'PATH_INFO': '/socket.io/1', 'QUERY_STRING': '', 'RAW_URI': '/socket.io/1', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '45123', 'REQUEST_METHOD': 'POST', 'SCRIPT_NAME': '', 'SERVER_NAME': 'localhost', 'SERVER_PORT': '8000', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'gevent/1.0 Python/2.7', 'gunicorn.sock': , 'wsgi.errors': ', mode 'w' at 0xb73320d0>, 'wsgi.input': , 'wsgi.multiprocess': False, 'wsgi.multithread': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.version': (1, 0)} failed with KeyError

  • #78 Arne said 2014-04-15T07:07:27Z

    Do you have further sources how to identify logged-in users (via Flask) and send ws-messages “directly”? I'm thinking of notifications or in my case, sending large sets of "real-time" data from energy measurements to a specific user. I do not want to broadcast the data to all users. There must be a way to “dynamically” allow to spawn new “secured” websockets.

  • #79 Miguel Grinberg said 2014-04-16T05:42:51Z

    @samir: it still does not look like you are using the socketio worker.

  • #80 Miguel Grinberg said 2014-04-16T05:45:58Z

    @Arne: websocket connections are permanent, once the client connects the connection remains until one of the parties disconnects. One way to reference users is by putting them each in a room. The room name can be the username or some other unique property of each user. Then you can send something to a user by sending it to the user's room. Hope this helps.

  • #81 Arne said 2014-04-16T06:13:52Z

    Thanks for commenting my comment. A room seems to suitable, thanks!

  • #82 Ramy said 2014-04-21T10:02:31Z

    Hi Miguel, thanks for all of you great articles and contributions to the Python community!

    I just wanted to ask if you think it will be a good idea to use gevent and Flask-SocketIO in a big website? I really love Flask, and Python in particular, and I was always asking myself if it's a good idea to use them for large websites. With the concept of WebSockets and persistent connections, I'm just afraid the server won't scale pretty fast...

    I think, in particular, my question relates to the use of gevent as a server, rather than Flask as a framework...

    If you think it will indeed have scaling problems, which technology would you choose to develop big websites (that make use of WebSockets)?

    Appreciate your response!

  • #83 Miguel Grinberg said 2014-04-21T14:41:42Z

    @Ramy: I haven't used gevent for a large application myself, so I can't really comment from direct experience. My impression is that gevent is pretty robust. The page at http://gevent.org/success.html is not too long, but there are a couple of fairly known names, so I guess it is being used for big things by some.

  • #84 Ramy said 2014-04-21T20:43:38Z

    Thanks for the reference. I just watched a video of a talk at EuroPython 2012, by one of the developers of Diqus - Adam Hitchcock. I got to that video, following your refernce for the success stories of gevent. He talked about how they made Disqus realtime using gevent & Flask, serving tens of thousands requests per second. While I didn't understand EVERY concept & method he talk about, I understood enough to know it is possible to run big websites pretty well. It's pretty interesting talk - you can watch it (if you haven't already) at: https://www.youtube.com/watch?v=PeVB5DNptD4 . Thanks again.

  • #85 Jenny said 2014-04-24T23:25:39Z

    Awesome extension! I got the demo up and running without any trouble, but now I an trying to run the code behind uwsgi (2.0.4) and nginx (1.4.4) locally and am encountering issues. I suspect I'm missing something from my uwsgi config but an unsure what it is. The uwsgi command I'm running looks like

    uwsgi --callable app --gevent 100 --socket 127.0.0.1:4040 --http-websockets

    When I open the app in a browser at http://127.0.0.1:8084, I see the template being rendered correctly, but get the error

    GET http://127.0.0.1:8084/socket.io/1/?t=1398378596489 404 (NOT FOUND)

    in the console. So the websocket handshake is never made and I'm left with a nonfunctioning form.

    I understand this is a pretty specific case, but it must be that others would want to know how to configure uwsgi to run the extension correctly. Any thoughts would be hugely appreciated!

  • #86 Miguel Grinberg said 2014-04-25T07:13:38Z

    @Jenny: I haven't tried using uwsgi. Your problem is that for this extension you can't use the normal gevent based server, there is a specialized server included in the gevent-socketio plugin. The documentation does not indicate support for uwsgi, they only mention gevent alone, gunicorn (with gevent worker) and nginx. See http://gevent-socketio.readthedocs.org/en/latest/server_integration.html.

  • #87 Adrien said 2014-04-26T13:00:14Z

    Hi Miguel! Thanks to your greatful work!

    Everything works fine but I wonder if there is a simple way to print out on the console any event requests in the same way regular http ones are printed.

    Do I have to intercept and print everything myself in the app or is there something already existing for this purpose?

  • #88 Miguel Grinberg said 2014-04-26T19:49:22Z

    @Adrien: when you are using websocket there are no requests, the entire exchange between a client and a server happens in the context of a single request. The data that is sent between server and client is completely arbitrary and application dependent and it could go at a very high rate, so it is not practical to print every single message that goes through.

  • #89 Ben said 2014-05-01T01:18:41Z

    Hi,

    when running the example from github: https://github.com/miguelgrinberg/Flask-SocketIO I got some problem: The application runs fine but I seem unable to terminate the server properly. This is what I get: http://pastebin.com/XwWtp0H0

    I run the application by "python app.py" and terminate it via ctrl + C.

    I'm on ubuntu 13.10 with python 2.7.5. I used virtualenv with -r and the requirements.txt so I suppose everything should be okay with regards to modules.

    Any help is appreciated.

  • #90 Miguel Grinberg said 2014-05-01T06:39:16Z

    @Ben: please write a bug on github so that I don't forget to look into this. Thanks.

  • #91 David N. said 2014-05-03T05:29:53Z

    Miguel, thank you so much for what you do.

    I'm trying to merge the example Flask socketio app above with an existing Flask app based on your Mega Tutorial.

    Given that, what would be the proper way to do that with respect to app/init.py, views.py, config.py, run.py, etc.

    I'm a bit confused about how to structure the above files so flask and socketio would be initialized and .run() and the threads would be started. Thank you!

  • #92 Miguel Grinberg said 2014-05-03T06:09:29Z

    @David: there is probably many "proper" ways. I would probably create the socketio object in app/init.py, so make it easy for the run.py script to import this object and run it. The socket event handlers can go in a sockets.py module to not mix things up with the regular views. You will need to import sockets in init.py, like you do with views.py.

  • #93 TimD said 2014-05-05T23:12:56Z

    Hello Miguel! Thank you for your work on creating flask-SocketIO. I was wondering if/how you can invoke a socket message from an http request. Something like: @app.route('/send',methods=['GET']) def send_http(): socketio.emit('send', {'data':'mymessage'}, namespace='/socketmsg', broadcast=True)

    @socketio.on('get', namespace='/socketmsg') def get_socket(data=None): print 'data:',data

  • #94 Miguel Grinberg said 2014-05-06T06:17:55Z

    @TimD: you can call socketio.emit() from the HTTP route, that works (you don't need to include broadcast=True, in that context messages are always in broadcast mode).

    But the clients will be receiving the messages, not the server. The socket handler you wrote in your example is not necessary.

  • #95 TimD said 2014-05-06T18:40:33Z

    Thanks Miguel! Odd, I tried my exact code yesterday with no success, but it seems to be working great now. Yes, I just put the socket handler in the example to show the clients connected to the same namespace.

  • #96 Ben said 2014-05-09T23:07:10Z

    How long does it usually take until a BugFix for Flask-SocketIO is incorporated in the current version available through pip?

    Best regards

    Ben

  • #97 Miguel Grinberg said 2014-05-10T04:03:56Z

    @Ben: if I forget to do a release you just need to remind me, I'm all for releasing often. Just pushed 0.3.5 with all the fixes. :)

  • #98 Lee said 2014-05-10T16:10:08Z

    Hi, great extension! I haven't used it yet, however I have some basic questions so please excuse my lack of knowledge here.

    socketio.run(app) runs the gevent server to handle websockets, what about the flask app itself, how do you tell the regular http web server to listen as usual? Because 'app' is being sent to your wrapper? I would just set app config options to change ip:port?

    I guess I don't understand how running a regular flask web app and a flask socketio app works and communicates between each other.

    Thanks!

  • #99 Miguel Grinberg said 2014-05-11T19:34:56Z

    @Lee: the socketio/gevent web server handles both the socket stuff and the regular WSGI traffic. The two servers run sort of separately, my extension bridges them to some extent.

  • #100 ben said 2014-05-14T12:15:00Z

    Does the socket/IO extension experience any benefit from being run with multiple workers via gunicorn?

Leave a Comment