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

  • #276 Miguel Grinberg said 2015-11-01T05:59:02Z

    @Kamal: the message is actually correct. It's telling you that the software is running without WebSocket support, and suggests you install eventlet or gevent (which are optional) to add WebSocket. Nothing is broken, by the way, the extension still works without WebSocket.

  • #277 Miguel Grinberg said 2015-11-01T06:01:52Z

    @Kamal: and regarding the code, the example has evolved to support new features added after this article was published. That's why the screenshot above looks different.

  • #278 min said 2015-11-13T23:24:48Z

    @Miguel

    After I deploy Flask-SocketIO-Chat to heroku(http://flasksocketiochat.herokuapp.com/) https://github.com/miguelgrinberg/Flask-SocketIO-Chat Procfile:web: gunicorn --worker-class eventlet chat:app

    It takes some time to join the room, also the following errors happen every time. It seems that after several times retrying, It can join the room. Should I ignore the error?

    The connection to ws://flasksocketiochat.herokuapp.com/socket.io/?EIO=3&transport=websocket&sid=ac2dd7db4e844fa8ba063f7eee407908 was interrupted while the page was loading.

    Firefox can't establish a connection to the server at ws://flasksocketiochat.herokuapp.com/socket.io/?EIO=3&transport=websocket&sid=b99aaae379f24438acef4b4e5833c71d.

    I have depolyed example in flask-socketio too, the same errors happened I can connect to server but i failed and tried several times. http://flasksocketio2.herokuapp.com/ output look like this. Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #0: Connected Received #1: I'm connected! Received #5: Server generated event Received #6: Server generated event Received #7: Server generated event Received #8: Server generated event

  • #279 Miguel Grinberg said 2015-11-14T06:49:29Z

    @min: looking into it, I'll update the bug you wrote on github as soon as I understand the problem.

  • #280 Vipul said 2015-11-26T14:41:15Z

    Hello Miguel,

    I am using Flask-SocketIO 0.6.0 and Python2.7. Its working as expected.

    I am using client-side application as HTML page came along with your tutorial. Now, I am using HAPROXY which routes all client connections from port.80 to Flask-Socket (configured on port say.5000). This is working perfectly.

    But my end-user application is in AngularJs which can also communicate with Flask-SocketIO (provided HAProxy is disabled). But post enabling HAProxy, Web-Socket connection could not established.

    Any ideas??

    Thanks & Regards, Vipul Jain

  • #281 Miguel Grinberg said 2015-11-28T18:43:41Z

    @Vipul: have you configured haproxy to properly handle the websocket protocol? Websocket is different than HTTP, I'm not familiar with the haproxy set up for it, but my guess is that it has to be configured separately from HTTP.

  • #282 Francesco said 2015-11-30T11:32:59Z

    Hi Miguel, thank you for this tutorial. SocketIO looks amazing! However I have one problem. I am trying to initialize a SocketIO object in one of my blueprints.

    let's say the blueprint is called blueapp, whenever the command --> socketio = SocketIO(blueapp) is ran, I get the following error:

    AttributeError: 'Blueprint' object has no attribute 'wsgi_app'

    I can't figure out how to sort this problem, any reccomendation ?

  • #283 Miguel Grinberg said 2015-12-02T18:54:18Z

    @Francesco: I have written an example project that shows how to structure a Flask application that uses Flask-SocketIO: https://github.com/miguelgrinberg/Flask-SocketIO-Chat

  • #284 Andrias said 2015-12-16T02:44:11Z

    sir, can I develop some kind of server-client push notification based on this post?

  • #285 Miguel Grinberg said 2015-12-22T19:52:23Z

    @Andrias: yes, this is frequently used for server-push. The server can initiate an exchange in the same way the client does. See the example app on github for an example of that.

  • #286 Waseem Jan said 2016-01-01T14:08:34Z

    Hi Miguel, In my application I am following factory pattern. I have basically created two different applications and then combined them through DispatcherMiddleware like this app = DispatcherMiddleware(frontend.create_app(), { '/api': api.create_app() }) I have inititialized my extensions.py from flask_socketio import SocketIO socketio = SocketIO() and then called its init_app to pass it configurations socketio.init_app(app) and finally I am running my server in server.py like this app = DispatcherMiddleware(frontend.create_app(), { '/api': api.create_app() }) if name == "main": socketio.run(app) So basically I am passing socketio.run an instance of DispatcherMiddleware. When I start server like this I get error on console 'DispatcherMiddleware' object has no attribute 'config' Can you please help me how to use socketio with DispatcherMiddleware. They way I have written my code is mostly inspired by Overholt Application. http://mattupstate.com/python/2013/06/26/how-i-structure-my-flask-applications.html

  • #287 Waseem Jan said 2016-01-03T19:26:06Z

    Hi, Can Flask-socketIO be deployed on apache server with mod-wsgi ? One other question about the security. You have mentioned that we should do the authentication through http and after that initialize a websocket connection. In my application I will create a separate room(based on userid ) for each user so that the backend can emit new notifications to him. Can you explain a little how initializing websocket after login make it more secure. If I do authentication through http and login but after that I join the room with someone else id, the system may push someone else notifications to me.

  • #288 Miguel Grinberg said 2016-01-03T22:26:08Z

    @Waseem: the argument that you pass to socketio.run should be a Flask application, not a generic WSGI middleware. Assuming frontend is a Flask app, you can initialize your dispatching as follows:

    app = frontend.create_app() app.wsgi_app = DispatcherMiddleware(app, { '/api': api.create_app() })

    If you do it as above, the Flask app instance isn't buried in a chain of middlewares and instead remains accessible.

  • #289 Miguel Grinberg said 2016-01-03T22:33:10Z

    @Waseem: regarding the second question, I have not had much luck with SocketIO and mod-wsgi. The Socket.IO server needs multiple threads and a single process, so the typical apache configuration is not going to work. For this reason production deployments are done usually with greenlets (gevent or eventlet), which mod_wsgi does not support either. The best configuration I have found is to deploy the server on gunicorn with the gevent or eventlet worker, and then put nginx as a reverse proxy. Configuration examples are in the documentation.

  • #290 Hamid said 2016-01-05T01:43:00Z

    Thanks a lot for your blog. Could you please briefly explain how I can pass Values from my server to client? I posted my detailed question here: http://stackoverflow.com/questions/34594666/run-portion-of-code-every-n-second-inside-certain-decorator?noredirect=1#comment56948411_34594666

    I appreciate your help

  • #291 Miguel Grinberg said 2016-01-05T16:33:57Z

    @Hamid: See the example app included in the Flask-SocketIO repository on Github. That app has a background thread that sends server-initiated events to the client periodically.

  • #292 Vipul Jain said 2016-01-08T14:00:32Z

    Hello Miguel, I am using your sample source.

    I have two queries: 1. When i executed, it ran well correctly. I inserted print statement after "if name == 'main':" and observed that print statement appeared twice.

    Does it mean that app.py file executes twice?

    2.Incase i close the browser, i saw the message 'Client disconnected' appears. But disconnect message print time was variable.

    What is the specific time from Browser close event till 'Client disconnect' print message? And incase it is configurable then share the details?

  • #293 Miguel Grinberg said 2016-01-09T04:04:51Z

    @Vipul: 1. you probably were running in debug mode with the reloader, which starts a master process and a child process. Only one process will run when you turn debug mode off. 2. When you are using WebSocket a client disconnect is detected immediately, but when using long-polling the server waits a while and declares the client disconnected after some time passes without receiving any requests.

  • #294 shashank khare said 2016-01-13T12:03:57Z

    I am trying to build a customer support chat solution using websockets in the backend using Flask in the backend. How well will it cope if on an extreme case we have 180 customers talking to 4 agents? I am planning to have queueing strategy server side so agents can select the chats so I dont have 100 tabs open suddenly. Any idea on the best frameworks to help me in this? i.e queuing websocket new connections serverside and opening only when its accepted?

  • #295 Miguel Grinberg said 2016-01-14T15:40:23Z

    @shashank: using a coroutine framework (eventlet or gevent) will allow you to support hundreds of clients without using a lot of server resources. For the queue, I would have customers connect to the socket and from then on they wait until they get the event that starts the support call, at which point they can open a window/tab, whatever is necessary.

  • #296 Vipul Jain said 2016-01-14T17:00:55Z

    Hello Miguel,

    I am running your sample source from target board having debian linux. I did a sample test of sending approx 383KB of json-data from websocket server (using example source of flask-socketio) to Flask-socketio template at 100ms periodically. In example source, delay of 1 is replaced with 0.1 (i.e.100ms). I am using "htop" utility to monitor system resource usages (basically RAM and CPU).

    Observations: 1. After connection of websocket server and UI client, websocket process (app.py) MEM% utilization increases at rate of 0.1% per 3 sec for data emit rate of 383KB per 100ms. 2. Even though i have closed the browser, i saw the disconnect event on server script. And surprising thing is that server still emits data (don't know why) and MEM% of app.py was also increasing. It never stopped. 3. This increasing MEM% of app.py is eating up the system RAM. I suppose it is Memory leakage in python files. 4. Memory usage of flask-socketio screen (running on desktop Chrome browser increases drastically. In my case, it reached to 1,220,906 KB after approx 10 mins. But after 5-mins, desktop test-screen becomes blank on scrolling.

    I have captured video and image. Want to share with you. If possible drop your mail-id to my inbox (mentioned in Email field).

    I am looking for a resolution on above queries (especially 1, 2, 3). I will get back to you sometime later on my earlier query #292. Thanks for your continued support.

    Regards, Vipul Jain

  • #297 Miguel Grinberg said 2016-01-16T22:55:25Z

    @Vipul: I need to see your code (make it a gist). If you want to share the video and images you captured, upload to youtube, imgur, etc. and send me the links.

  • #298 Vipul Jain said 2016-01-17T06:47:52Z

    Hello Miguel, Inline to Post#296,297, regarding Memory consumption. As requested, please find the video at https://youtu.be/rSiFRY396no

    Regarding source, i was using yours sample source code, with only few below changes 1. data emit delay of 10 changed to 0.1 2. json data 'Server generated....' is changed to huge json data of size approx.383-400KB 3. host set to my boardip and port to 5000

    Somewhere, i read - websockets were meant for sending small data to UI. Well, i am not sure about definition of small data. But even though, memory consumption should not increase. Meantime, i am investigating into the same.

  • #299 Miguel Grinberg said 2016-01-27T15:35:58Z

    @Vipul: I'm not seeing the same here, odd. For me, the page stays at about 88MB. I've been watching the task manager for about 5 min. and it has not increased.

  • #300 Vipul Jain said 2016-02-01T11:49:04Z

    Hello Miguel, 1. I was using Flask-SocketIO v0.6.0 with SocketIO js v0.9.16. Memory leak issue was with this versions. I upgraded to Flask-SocketIO v2.0 and SocketIO js v1.3.5. Using this versions, memory leak issue has resolved. Thanks for investigating the same.

    After upgradation, i found that - send and reading message appears. For example: reading ('send', u'3probe') reading ('send', u'3') reading ('send', u'3') ...

    Query1: Any way to disable this messages?

    Aligned to query (#292 & #293), after upgrading to new versions, 3.1. Client disconnect message appears immediately on Browser close event. Thanks.

    While upgrading to new versions, Flask-SocketIO uses python-engineio and python-socketio. Query-2: Difference between python-engineio and python-socketio? Query-3: If python-engineio is dependent on python-socketio?

Leave a Comment