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

  • #326 Jain said 2016-05-04T14:45:26Z

    Hello Miguel, Thanks for continued support. But this time, i again had observations of memory leak.

    I have below Flask SocketIO-2.0 setup. 1.Flask-SocketIO-2.0.tar.gz 2.python-socketio-1.0.tar.gz 3.python-engineio-0.8.7.tar.gz

    And running yours sample example of Flask-socketIO-2.0 Flask-SocketIO-master.zip To run sample of Flask-SocketIO-2.0, please install this - gevent-socketio-0.3.6.tar.gz

    I had below observations using Google Chrome. I have uploaded the video for your reference. Step-1: At Initial, (Refer video ‘01 InitWebsocket.mp4’ at url https://www.dropbox.com/l/s/UM2SlTS2Ep7V6cvtLK3udu

    UI html page is able to initiate web-socket connection with server. Web-Socket connection is having status code: 101 with Time status as Pending. Also, data is visible in Frame section of browser’s debug window Memory consumption started with 0.6% of system memory Date size is 21 Kbyte Data emit rate is time.sleep(0.05). Data-emit rate is kept high just to reproduce issue fast enough rather waiting for issue to reproduce post couple of days. At initial - No memory leak observed

    Step-2: After few minutes i.e. Approx. 3 minutes, (Refer video ‘02 WebSocketMemoryLeak.mp4’ at url https://www.dropbox.com/l/s/MQRACKo8qdv8elUPjNckQo

    Data display on browser is slowed down. It can be visible in Frame section of browsers debug window Gradually, memory consumption starts increasing and reaches to approx. 2.2%, followed by web-socket connection breaks/completes at 8.3 min. On web-socket connection failure, Client fall backs to polling mechanism. Some Assertion errors noticed on terminal. Client re-attempts for web-socket connection. On web-socket re-connection, some messages as 2probe, 3probe, 5 appear. This messages may be internal to web-socket library. Memory consumption is still 2.2% One thing noticed that “while web-socket connection broke, some data packets were missed” to be display on screen and even do not appeared in polling response to UI From now onwards, whenever web-socket connection breaks, Opcode -1 displays in Frame section. ‘Client disconnection’ message appears on terminal. Memory consumption increases by approx. 1%. Web-Socket connection persists only either for seconds or for <2min. Point. 5 reproduces continuously. Memory consumption increases by approx. 1%.

    Step-3: Actually, in step-2, web-socket connection was able to establish. But now in step-3, I would make changes in browser settings to block web-socket connection permanently and instead let polling mechanism only occur frequently. This change is made to notice that Memory Leak also happens heavily during polling and while closing the browser and re-opening the browser page. Refer video 03 PollingMemLeak.mp4 at url https://www.dropbox.com/l/s/urnBdMkJqjt0aEcUHJCahr

    Memory consumption is already at 21% While polling was in progress, memory leak increased from 21% to 22%. Later, I refreshed the screen, memory increased from 22% to 23.6%. Suddenly, 400 BAD Request and 500 Internal Server error appeared on browser’s debug window, some key error appeared on terminal and memory reduced from 23.6% to 23%. Again memory consumption started increasing from 23% to 24.2%, Before, internal server error appears and drops to 23%, I quickly again refreshed the screen, memory consumption started increasing from 24.2% to 25.2%. On again refresh, memory from 25.2% to 25.6%.

    Observations/Summary for Step2 and Step3: Step-2: 1. Whenever approx. 7000 to 8000 data packets pushed to browser, later data appearing on browser’s frame window get slower followed by memory leak and ending up with Web-Socket connection failure. This caused to re-attempt web-socket connection. 2. Frequent web-socket connection breakages occur.

    Step-3 1. In polling mechanism, memory consumption keeps increasing by 1%. The moment 404 and 500 error appears, memory drops by 1%. 2. Before 404 and 500 error appears, if user refreshes screen, then memory never drops and instead increases by another 1%

    Please share your mail-id/reference.

    Thanks & Regards, Jain

  • #327 Miguel Grinberg said 2016-05-04T20:22:05Z

    @Jain: If there is a memory leak, it is on the client side, so it should have no effect on the Flask-SocketIO server.

  • #328 Jain said 2016-05-05T08:50:44Z

    Aligned to discussion thread #326, #327, if you have seen video <01 InitWebsocket.mp4> then memory consumption of our linux process kept on increasing.

    Do you mean Flask-SocketIO client library have the issue? If yes then why does memory consumption of our sample application is increasing (i mean an app based on Flask-SocketIO server side package)?

    I am facing severe memory leak issue. Not able to judge what thing made our sample application to consume more memory.

    Great thanks if you could replicate above setup and execute this at your end...

  • #329 Miguel Grinberg said 2016-05-05T14:45:20Z

    @Jain: sorry, I could not watch the video, I assumed you were talking about memory on the client. I will investigate. It would be better if you write an issue on the github repository, that makes it easier for me to keep track of current bugs.

  • #330 Mattias R. said 2016-05-17T10:52:45Z

    Hi! I've been working on a quite large web application where we use flask, flasksocketio etc. And I've seen that some problems occur when we monkey patch the application. Both gevent and eventlet brake the application. The problem is that when we monkey patch the application, python-hmac occasionally returns the wrong value. When I changed so I do not use eventlet or gevent everything seems to work fine. (except for the websockets which instead is long-poll).

    I also found that the following edit works fine: from gevent import monkey monkey.patch_all(ssl=False)

    Is it possible to run the application without eventlet & gevent? Why does gevent monkey patch ssl -stuff?

    The web application is running on ARMv71. And we have a couple of background threads running.

    Thanks!

  • #331 Miguel Grinberg said 2016-05-18T04:58:11Z

    @Mattias: Flask-SocketIO does not require that you monkey patch the standard library, this could be a requirement of your application, or of some of your other dependencies. In your application you can avoid monkey patching if you call the gevent or eventlet functions for threading, I/O, etc. Also you can selectively patch portions of the standard library, so you may be able to find a subset of things to monkey patch that does not interfere with your code.

  • #332 Bo said 2016-05-26T18:14:49Z

    I am a total newbie in terms of web/socket/javascript. I am seeking a solution to a problem that I have a long running program with various types of outputs (log message, execution transcript etc). Instead of piping all these to standard output, or to different files (logging), I would like to be able to use a browser to point to http://localhost:port/log and get a LIVE view of the log message (and other URLs for transcript etc). I think flask, websocket etc fit the bill almost perfectly but I cannot find a way to update the log messages without refreshing the page. Could you point me to appropriate technology for this (e.g. example of javascript + websocket)?

  • #333 Miguel Grinberg said 2016-06-02T01:52:43Z

    @Bo: There are two main solutions for your problem: Ajax or WebSocket. With ajax, the client routinely sends a request to the server to obtain new log entries. If the client polls frequently enough, the illusion that the system is dynamic will be achieved. With WebSocket, each client makes a permanent connection to the server, and then the server can send data over that connection at any time, outside of the HTTP protocol. Ajax is easier to set up, WebSocket is more efficient.

  • #334 Jordan.J said 2016-06-14T16:26:14Z

    Hello,

    First off, awesome tutorial and appreciate the library :D I wanted to know if it's mandatory to use a javascript based client or if it's possible to use a pure python client and how would go about setting this up? Can you point me in the right direction?

    Thanks -Jordan

  • #335 Miguel Grinberg said 2016-06-15T14:42:13Z

    @Jordan: you can use any Socket.IO client, written in any language. There are official clients for Javascript (for browser and/or node.js), Java, C++ and Swift. I have also come across unofficial clients written in Python and C#. If you search pypi you'll find the Python one.

  • #336 Dimitri said 2016-06-24T15:55:46Z

    Hello Miguel,

    I wanted to make a small comment on the answer you gave to Waseem regarding the mounting of multiple Flask app with werkzeug's DispatcherMiddleware. I tried your solution but it failed on a recursion error. Maybe I didn't implemented properly though.

    I found a workaround by creating a dummy Flask app and use it as the entry point. It gives something like that:

    app = Flask(name) app.wsgi_app = DispatcherMiddleware( backend.create_app(debug=args.debug), { '/api': api.create_app(debug=args.debug) } )

    socketio.init_app(app) socketio.run(app, host=args.host, port=args.port, debug=args.debug)

  • #337 erm3nda said 2016-08-05T11:41:32Z

    Thank you for your great explanations.

  • #338 Billy said 2016-08-31T18:37:54Z

    Miguel, I have had no luck getting the the simplest of servers to run. The basic example in the latest docs seems to exit immediately (on both windows and linux). No message or errors, it just doesn't serve....

    from flask import Flask, render_template from flask_socketio import SocketIO

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

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

  • #339 Miguel Grinberg said 2016-09-10T17:44:31Z

    @Billy: that is really weird. I recommend that you write a question on stack overflow with additional details, like the contents of your virtual environment (pip freeze), python 2 or 3, etc.

  • #340 Priyend said 2016-10-24T23:31:10Z

    Hi Miguel

    Many thanks for the excellent information and extensions for Flask.

    I am having trouble trying to get Flask-SocketIO working with RedHat's Openshift (www.openshift.com) server.

    Your above example works perfectly on my local machine. However when I upload it to the Openshift server it does not work. This is due to Openshift using port 80 for the normal HTTP traffic but a different port (8000) for websocket traffic.

    I have tried all sorts of methods I could find from the net but cannot get it to work. Can you help with the appropriate setup to get your simple example above to work on Openshift?

    Many thanks Priyend

  • #341 Miguel Grinberg said 2016-10-25T17:18:09Z

    @Priyend: I'm not familiar with OpenShift, but based on the port restrictions you mention it seems to me you have to pick a single transport to use. If you connect your socket.io client to port 80 you will have only long-polling available. If you connect to port 8000, then you have to tell the client to connect directly using WebSocket, as the default is to connect on long-polling first, then upgrade. See https://coderwall.com/p/pgk00a/socket-io-and-openshift-websockets for an example of how to use the WebSocket transport with Socket.IO.

  • #342 ffsdmad said 2017-01-02T20:08:40Z

    how to deployment it using Uwsgi and Nginx

  • #343 Miguel Grinberg said 2017-01-03T05:26:51Z

    @ffsdmad: See the deployment section in the documentation: https://flask-socketio.readthedocs.io/en/latest/#deployment

  • #344 Mattffc said 2017-01-16T15:57:55Z

    Hi Miguel,

    Great extension. I've been using it successfully for some time to develop an app on my local machine. When it came to deployment onto my digital ocean server I hit a roadblock. The websocket handshake would always return with a status of 200 rather than 101 "changing protocols". I was originally using nginx with gunicorn with eventlet as a worker and I followed all the advice about specifying to nginx to allow upgrading and use of http 1.1 etc but to no avail. To make sure it wasn't an error with my code I followed this guide and downloaded the example code, installed the requirements onto the remote server but found that I still get 200 status rather than 101. The communications default to XHR. The only change I made to the code was to run the server with host '0.0.0.0' and port 80.

    I'm wondering if it's some kind of firewall that digital ocean has in place to block websockets. Anyway I'm completely stuck on this and wondered if you have any advice.

    Thanks in advance!

    Matt

  • #345 Miguel Grinberg said 2017-01-16T21:58:06Z

    @Mattffc: not sure what the problem might be. I have used digital ocean droplets with Flask-SocketIO and never had any problem. My setup was with nginx listening on port 80, and the gunicorn with the eventlet worker service the Flask app on a high numbered port.

  • #346 GrinningHermit said 2017-02-03T22:07:06Z

    Hello mr. Grinsberg,

    The ease with which I got this to work is great. Thanks for all the explanations. The github resource is very instructive. I originally wanted to ask you about exchanging dynamic variables between a background thread and the main one. But I solved that by using a queue variable. StackOverflow/Google combo is priceless in that regard. So 'just' a big thank you instead! I was hesitant at first, but your work made me understand websockets and I think I get the basics now :)

  • #347 msbc said 2017-02-09T10:03:55Z

    Hi Miguel

    flask-socketio can be deployed with apache + uwsgi? If yes, can you please provide reference link if any?

    Thanks & regards,

  • #348 Miguel Grinberg said 2017-02-09T21:44:25Z

    @msbc: I haven't had much luck with Apache and Socket.IO, to be honest. You can use uwsgi combined with gevent, so I'm not sure what role could Apache have once you have everything running with uwsgi.

  • #349 Omar El Kareh said 2017-02-13T21:51:58Z

    Hello Miguel, excellent library there btw. One question, how can i load the SSL certificates if i'm running the app directly like app.run()... not using apache or nginx. Flask allows me to setup the SSL context parameter when calling run, but socketio doesn't have that parameter, can i set it in some other way? thanks.

  • #350 Miguel Grinberg said 2017-02-14T16:59:42Z

    @Omar: the socketio.run() method accepts kwargs. Any extra arguments you pass will be given to the web server that you are using (eventlet or gevent). So there you can pass the ssl options for your server.

Leave a Comment