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

  • #401 yuhan cheng said 2018-07-30T06:16:04Z

    Hi Miguel: Thank you very much! The websocket server is work now. But I got a handshake problem when I use client.py instead of JS client to connect.

    scenario0: No security < client.py > "async with websockets.connect( 'ws://localhost:5000/') as websocket:"

    < server > nothing happened

    < client error > File "/Users/sarahcheng/Documents/Master/GRA/demo/Flask_microblog/venv/lib/python3.6/site-packages/websockets/py35/client.py", line 20, in await_impl extra_headers=protocol.extra_headers, File "/Users/sarahcheng/Documents/Master/GRA/demo/Flask_microblog/venv/lib/python3.6/site-packages/websockets/client.py", line 263, in handshake raise InvalidStatusCode(status_code) websockets.exceptions.InvalidStatusCode: Status code not 101: 302

    And I change to use wss scheme:

    scenario1: < client.py > async with websockets.connect( 'wss://localhost:5000/') as websocket:

    < server > 127.0.0.1 - - [30/Jul/2018 11:02:46] code 400, message Bad request syntax ('\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03|\x06Á¶\x10¢\x9ay\x1b\x1e<Õ«ú\x8b{wÇ\x00|CÌÿ,þ¨pË\t5B\x86\x00\x00\x82À0À,À2À.À/À+À1À-\x00¥\x00£\x00¡\x00\x9f\x00¤\x00¢\x00\xa0\x00\x9eÀ(À$À\x14À') 127.0.0.1 - - [30/Jul/2018 11:02:46] "ü|Á¶¢šy<Õ«ú‹{wÇ|CÌÿ,þ¨pË 5B†‚À0À,À2À.À/À+À1À-¥£¡Ÿ¤¢ žÀ(À$ÀÀ" 400 -

    < client error > Traceback (most recent call last): File "test/WSclient.py", line 19, in asyncio.get_event_loop().run_until_complete(hello()) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete return future.result() File "test/WSclient.py", line 10, in hello 'wss://localhost:5000/') as websocket: File "....../venv/lib/python3.6/site-packages/websockets/py35/client.py", line 2, in aenter return await self File "....../venv/lib/python3.6/site-packages/websockets/py35/client.py", line 12, in await_impl transport, protocol = await self._creating_connection File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 804, in create_connection sock, protocol_factory, ssl, server_hostname) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 830, in _create_connection_transport yield from waiter File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/sslproto.py", line 505, in data_received ssldata, appdata = self._sslpipe.feed_ssldata(data) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/sslproto.py", line 201, in feed_ssldata self._sslobj.do_handshake() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 689, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: UNKNOWN_PROTOCOL] unknown protocol (_ssl.c:833)

    scenario2: < client.py > async with websockets.connect( 'wss://localhost:5000', ssl=ssl_context) as websocket:

    < server > 127.0.0.1 - - [30/Jul/2018 11:02:46] code 400, message Bad request syntax ('\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03|\x06Á¶\x10¢\x9ay\x1b\x1e<Õ«ú\x8b{wÇ\x00|CÌÿ,þ¨pË\t5B\x86\x00\x00\x82À0À,À2À.À/À+À1À-\x00¥\x00£\x00¡\x00\x9f\x00¤\x00¢\x00\xa0\x00\x9eÀ(À$À\x14À') 127.0.0.1 - - [30/Jul/2018 11:02:46] "ü|Á¶¢šy<Õ«ú‹{wÇ|CÌÿ,þ¨pË 5B†‚À0À,À2À.À/À+À1À-¥£¡Ÿ¤¢ žÀ(À$ÀÀ" 400 - 127.0.0.1 - - [30/Jul/2018 09:38:08] code 400, message Bad HTTP/0.9 request type ('\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03\x90,\x87;\x10âêó\x9d¤uIuÖD\x18¬ñÏûÂ=~õ\x8e3¼\x87\x11â³G\x00\x00\x82À0À,À2À.À/À+À1À-\x00¥\x00£\x00¡\x00\x9f\x00¤\x00¢\x00') 127.0.0.1 - - [30/Jul/2018 09:38:08] "ü,;âêó¤uIuÖD¬ñÏûÂ=~õ3¼â³GÀ0À,À2À.À/À+À1À-¥£¡¤¢ À(À$ÀÀ" 400 -

    < client error > Traceback (most recent call last): File "test/WSclient.py", line 19, in asyncio.get_event_loop().run_until_complete(hello()) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 468, in run_until_complete return future.result() File "test/WSclient.py", line 10, in hello 'wss://localhost:5000/') as websocket: File "...../venv/lib/python3.6/site-packages/websockets/py35/client.py", line 2, in aenter return await self File "...../venv/lib/python3.6/site-packages/websockets/py35/client.py", line 12, in await_impl transport, protocol = await self._creating_connection File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 804, in create_connection sock, protocol_factory, ssl, server_hostname) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/base_events.py", line 830, in _create_connection_transport yield from waiter File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/sslproto.py", line 505, in data_received ssldata, appdata = self._sslpipe.feed_ssldata(data) File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/asyncio/sslproto.py", line 201, in feed_ssldata self._sslobj.do_handshake() File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ssl.py", line 689, in do_handshake self._sslobj.do_handshake() ssl.SSLError: [SSL: UNKNOWN_PROTOCOL] unknown protocol (_ssl.c:833)

    And I want to ask: 1. why the "ws" scheme is not work? 2. is that because of lack SSL wrapper in server, the handshake failed?

    Thank you for your attention! Thank you for your kind.

  • #402 Miguel Grinberg said 2018-07-30T21:01:18Z

    @yuhan: You can't use a websocket client to connect to Socket.IO, please use a Socket.IO client.

  • #403 Sutherlander said 2018-08-30T02:04:21Z

    Hi Miguel,

    I have a Flask application paired with the gevent server, and the start up section is: # Check on specified host for free port for port in ok_ports: sock_check = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock_check.settimeout(1) sock_check.connect((flask_options['host'],port)) sock_check.close() except socket.error: #host:port not in use flask_options['port'] = port try: #app.run(**flask_options) if comms_method == 'http': app_server = pywsgi.WSGIServer((host, port), app) srv_start_msg = "Server started: http://%s:%s" else: app_server = pywsgi.WSGIServer((host, port), app, certfile=context[0], keyfile=context[1])#, srv_start_msg = "Server started: https://%s:%s"

    app_server.start() print srv_start_msg % (host,port) return app_server except socket.error: print "Unable to start api on port %s" % port if port == ok_ports[-1]: raise Exception("No available ports")

    So the start() command on the pyWSGI app returns (the script continues, with server started in the BG(?)): http://www.gevent.org/api/gevent.baseserver.html#gevent.baseserver.BaseServer

    Is there an equivalent way to start the gevent socketio.run(app), using the start() instead of serve_forever(stop_timeout=None)?

    (BTW thanks for your previous help on session cookies, it helped a lot)

  • #404 Miguel Grinberg said 2018-08-30T21:12:54Z

    @Sutherlander: you can start your web server in any way you want. The only thing to keep in mind is that if you use websocket through the gevent-websocket package, then you need to use a different server class, provided by this package. Other than that, use your normal method to start the server.

  • #405 Arttu mahlakaarto said 2018-10-02T20:30:41Z

    Hi, Amazing tutorial! I need some help with flask chat system! The site I'm working on needs to have system where you can send messages one-to-one between users and each user can have multiple different one-to-one chats that they can send and receive from different users, and it also needs some sort of message history, but I'm having trouble both coming up with a good and efficient database schema for the chat history and the system for sending to specific users even if they are offline

  • #406 Miguel Grinberg said 2018-10-02T22:14:03Z

    @Arttu: Impossible for me to describe such a complex system in a blog comment, but your tables are likely going to be "chats" and "messages", or something similar. You can send to a specific user with the "sid" value that is assigned to each user when they connect. Pass this value as the "room" argument in the emit call.

  • #407 yuhan cheng said 2018-10-07T10:09:58Z

    Hi Miguel, Excuse me, is there a function could emit to a specific room in socketio-client python library? Thank you very much! ><

  • #408 Miguel Grinberg said 2018-10-07T19:23:40Z

    @yuhan: clients cannot contact other clients directly. The client always emits to the server, then the server can emit to a room.

  • #409 Hossain said 2018-10-16T22:38:26Z

    Hi Miguel, Thanks for the great package. I tried your package and it's working fine.

    My Project: I have a Raspberry Pi logging real-time temperature and humidity. Now, I am writing a flask app to push these data to clients who (subject to rights) will be able to observe continuously without refreshing the dashboard/page. After searching a lot, the options I find:

    Ajax WebSocket Framework e.g. bokeh or dash MQTT

    What will be the best option to make an efficient system, keeping in mind that there will be multiple sensors in the future? I would be really grateful if you can give me your opinions. Regards Hossain

  • #410 Miguel Grinberg said 2018-10-17T09:10:13Z

    @Hossain: there is no general answer to your question. The framework options that you list aren't really such, these frameworks are going to use one of the base mechanisms to refresh data, so really it comes down to Ajax (polling from the client), or a WebSocket solution (server push). Under WebSocket you have plain WebSocket, Socket.IO, MQTT and maybe others. It really comes down to selecting polling vs. server push and if you go with the latter, then use the mechanism you are most comfortable with.

  • #411 yuhan cheng said 2018-11-16T15:48:20Z

    Sorry, I still have a question..

    When I redirect the url, could I pass a socketio object to another page as parameter? The use case is... When clients come in "wait_room" url, they'll connect to flask_socketio server in JavaScript, and then wait for other clients in. After all in the "wait_room" url, the server would notify them to redirect to "game_start" url by socketio. And could I reuse the socketio connection in the new url? or.. What's the suggested method to implement the socketio connection between different urls ?

    Thank you, thank you very much.

  • #412 Miguel Grinberg said 2018-11-16T16:50:04Z

    @yuhan: No, nothing can be passed from one page to the next. Make a single-page app and then you can maintain your state through the life of your application.

  • #413 steven said 2018-12-05T11:35:41Z

    I have an AWS Ubuntu server with Flask socketIO on it. There are several displays that should stay connected all week. But after a few days i get this error.

    I tried changing the timeout and retry milliseconds but that didn't change anything.

    Any suggestions how i could resolve this error below? Or should i recode it to FlaskSockets?

    15:29:00,490 engineio ERROR service task exception Traceback (most recent call last): File "/usr/local/lib/python2.7/dist-packages/engineio/server.py", line 528, in _service_task s.check_ping_timeout() File "/usr/local/lib/python2.7/dist-packages/engineio/socket.py", line 72, in check_ping_timeout raise exceptions.SocketIsClosedError() SocketIsClosedError

  • #414 Miguel Grinberg said 2018-12-05T13:50:44Z

    @steven: this is just indicating that a connection unexpectedly ended. This can happen for a variety of reasons, none of which can be controlled by the server. The important facts is that 1) this error does not stop the server from running, and 2) any clients that disconnect will attempt to reconnect automatically. So as long as you find a problem in your application, you can ignore this error.

  • #415 Premananda Patro said 2018-12-26T09:13:28Z

    Hi, I'm using flask which support app.run(HOST,PORT,thread=Ture) but in socketio, how can i do as socketio.run(app,HOST,PORT,threaded=True)

  • #416 Miguel Grinberg said 2018-12-26T10:07:10Z

    @Premananda: you do not need to specify the threaded option, it is always enabled when you use Flask-SocketIO.

  • #417 X.Martinez said 2019-01-27T15:54:36Z

    Hi Miguel! First of all, thanks a lot for your tutorial, these are pure gold, honestly! I have run your example using websocketio, and I have tried to repeat it using no internet connection, as I'm making some test for a future local app that has outside connection forbidden. As far as I can understand, in the index.html file, there are these 2 js that require internet connection and I have been trying jump over it saving the content to a local file and referrint to them in the index.html without success. Any help please?? Thanks in advace guru!!

  • #418 Miguel Grinberg said 2019-01-27T16:14:30Z

    @X: yes, there is actually nothing tricky here, you just need to save the file somewhere in your Flask static file directory and then serve the file from there using ur_for, for example: url_for('static', filename='jquery.js').

  • #419 Ale said 2019-03-03T09:31:13Z

    Hi Miguel! I'm running a flask application along with a set of other services in Docker containers. I feel I'm not fully understanding this, is the socketio.init_app method in the create_app factory enough for having the socketio server listening? or should I have another container with the same image but with command socketio.run(...) running the server (like using celery worker)? What's the best practice here? Gracias!

  • #420 Ale said 2019-03-03T10:04:28Z

    I'm actually having an issue, the js client cannot connect to the server and it's always getting this error:

    VM15520:1 GET http://local.outflink.com:5000/socket.io/?EIO=3&transport=polling&t=Mb2_LpO 400 (Bad Request) Access to XMLHttpRequest at 'http://local.outflink.com:5000/socket.io/?EIO=3&transport=polling&t=Mb2_LpO' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

    I'm actually starting my app with gunicorn as follows: gunicorn --worker-class eventlet --certfile=keys/key.crt --keyfile=keys/key.key --bind 0.0.0.0:5000 myapp.run:app

    and this is my run.py: import eventlet from myapp import create_app eventlet.monkey_patch() app, celery = create_app('config_prod.py')

    I'm also using CORS(app) in my app factory.

    What's missing?

  • #421 Miguel Grinberg said 2019-03-03T10:54:22Z

    @Ale: The socketio.run(app) call starts a single server that listens for both HTTP and Socket.IO connections. So you need a single container for everything. I believe the CORS extension that you are using might be interfering with Flask-SocketIO's own CORS support. I'll have to investigate that more in detail to know for sure.

  • #422 Steve said 2019-04-07T13:48:56Z

    Hi Miguel. Thanks for this great work!

    I know this isn't really your problem, but wondering if you might want to add a note about it anyway, to potentially save others some frustration. In the post you say,

    "Event names 'connect', 'disconnect', 'message' and 'json' are special events generated by SocketIO. Any other event names are considered custom events."

    and I guess here you are referring to the server side, where your extension is running. (Which makes sense!) Still, I just wasted a few hours figuring out that over on the client side, where socket.io.js is running, ping is a reserved event too!

    Apparently others have experienced similar headaches, e.g. https://stackoverflow.com/questions/35542778/custom-ping-pong-socket-io https://github.com/socketio/socket.io-client/issues/1022 again, all relating to socket.io.js, which is not your responsibility.

    But I also figured out that the confusion is somewhat compounded by the fact that in your example app

    https://github.com/miguelgrinberg/Flask-SocketIO/tree/master/example

    (which is great, by the way -- thanks for it) you are still loading version 1.3.5 of socket.io.js -- where ping is apparently not a reserved event -- whereas new experimenters (like myself) are apt to use the latest socket.io.js, where it is.

    Your example does use my_ping instead of ping, which a good student might take as a hint -- but I did not! When I found ping was broken in my project, I tried changing my_ping to ping in yours, and yours still worked! (Which finally led me to the solution.)

  • #423 ed said 2019-10-19T22:19:49Z

    Hi Miguel Your work has been very helpful to me. I have a question around Flask-SocketIO and running on Azure App Services Python 3.7 Linux I have a basic application that runs fin locally but w=hen I push it to App Services using git "400 Bad Request" errors are returned for the socketio requests. Do you by chance have a tutorial on getting a basic Flask-SocketIO app running on Azure App Services, Python 3.7 Linux?

    Thanks Ed

  • #424 Miguel Grinberg said 2019-10-20T07:40:43Z

    @ed: enable logging on your Flask-SocketIO server to get information on the 400 error. Just add engineio_logger=True to the SocketIO() object.

  • #425 Yasmin Amran said 2019-10-23T07:50:23Z

    I am a big fan of yours for years. Your tutorials are always the best on the internet. Could you give an implemitation example such as multyplayer match memory card game using theses tools? Thanks a head Yasmin

Leave a Comment