[PYTHON] WebSocket application with Flask-Socket IO

Introduction

Because it became necessary to implement a chat application by accident I investigated how I could implement a WebSocket application using Flask.

I will write up to the point where I try to move the sample This alone is enough to use the main functions of chat.

Extension selection

I'm going to implement it on Flask's shoulders The following two extensions were listed as options.

It's a little old as February 2014, but in Explanatory article by miguelgrinberg There is a section that explains the comparison between the two.

++++++++++++++++++++++++++++++++++++++++++++++++++

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.

Flask-Sockets uses the native WebSocket protocol Wrapping through gevent-websocket. Used only in modern browsers that natively support WebSockets. On the other hand, Flask-Socket IO can be used with 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 is a JavaScript SocketIO library Implements a protocol for exchanging messages. Flask-Sockets only implement channels of communication What you send is 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.

Flask-SocketIO creates an environment for event handlers that are close to the view function. This includes creating application contexts and request contexts. With some exceptions, as explained in the documentation.

++++++++++++++++++++++++++++++++++++++++++++++++++

Although the third translation is not done well I think the point is that it can be seamlessly implemented in existing Flask apps.

I was worried about the reliability of the author and the star of GitHub at the same level.

That's why I chose Flask-SocketIO. If you want to get in touch with the WebSocket protocol, or on the client (JavaScript) side Should I use Flask-Sockets if I want more degrees of freedom?

However, it seems that you can achieve what you want to do either way.

Preparation

Use local Docker as in the previous article (http://qiita.com/nanakenashi/items/cbe8e8ef878121638514). My execution environment is Public Beta of Docker for Mac.

version


$ docker --version
Docker version 1.12.0, build 8eab29e, experimental

I'm just using Docker to create an environment quickly If you have an environment where new Python can run to some extent, skip the environment construction part.

Source

flask_socket/
    ├ Dockerfile
    └ requirements.txt

Dockerfile

#Specifying the base image
FROM python:3.5.2-alpine

#Store the directory where the source is placed as a variable
ARG project_dir=/web/socket/

#Install git after updating packages that can be installed with apk
RUN apk update
RUN apk add git

# requirements.Install the package listed in txt
WORKDIR $project_dir
ADD requirements.txt .
RUN pip install -r requirements.txt

#Flask from the GitHub repository-Get SocketIO source code
RUN git clone https://github.com/miguelgrinberg/Flask-SocketIO.git Flask-SocketIO
WORKDIR $project_dir/Flask-SocketIO/example

requirements.txt

After installing Flask and Flask-SocketIO with pip $ pip freeze> requirements.txt.

requirements.txt


click==6.6
Flask==0.11.1
Flask-SocketIO==2.6.2
itsdangerous==0.24
Jinja2==2.8
MarkupSafe==0.23
python-engineio==0.9.2
python-socketio==1.4.4
six==1.10.0
Werkzeug==0.11.10

Except for the packages that the body of Flask depends on Flask-SocketIO, python-engineio, python-socketio, six have been added.

procedure

Image creation

Image creation


$ cd /path/to/flask_socket/
$ docker build -t flask_socket . 

Container startup

Container startup


$ docker run -p 5000:5000 -it flask_socket /bin/sh

Add code

Add the host setting to the application execution code.

app.py


# socketio.run(app, debug=True)
socketio.run(app, host='0.0.0.0', debug=True)

Run sample application

Run Flask


$ python app.py

When you access localhost: 5000 from your browser, you will see a screen like the one below.

Flask-SocketIO.png

Explain a part of the'Send:'form

The difference between ʻEcho and Broadcast` can be easily understood by arranging the tabs. Other items are omitted because they are interpreted as chat room related operations. By the way, you can specify the name of the room, but you cannot enter your own name.

Interpretation of code

Processing at application runtime

app.py


#Loading the required modules
from flask import Flask, render_template, session, request         
from flask_socketio import SocketIO, emit, join_room, leave_room, \
      close_room, rooms, disconnect                                  

#Specifying the library to be used for asynchronous processing
# `threading`, `eventlet`, `gevent`Can be selected from
async_mode = None

#Create a Flask object and specify the session information encryption key
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'

#Flask object, async_Create SocketIO server object by specifying mode
socketio = SocketIO(app, async_mode=async_mode)

#Global variable for storing threads
thread = None

app.py


#Start SocketIO Server in debug mode
socketio.run(app, debug=True)

What to do when the page is opened

The SocketIO library (JavaScript) is loaded in the script in html.

index.html


<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>

After that, specify the namespace (WebSocket communication endpoint) and A connection is made with the SocketIO server.

Specifying handlers for events issued when connecting to the server The handler for the my response event is also specified here. The response seems to be added to the tag specified by the # log selector.

index.html


namespace = '/test'; 

var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);

socket.on('connect', function() {
    socket.emit('my event', {data: 'I\'m connected!'});
});

socket.on('my response', function(msg) {
    $('#log').append('<br>' + $('<div/>').text('Received #' + msg.count + ': ' + msg.data).html());
});                                                                                                                                                   

On the server side, receive a connection request from the client with the following code The response is returned by firing the my response event. The socketio.start_background_task (target = background_thread) part will be described later.

app.py


@socketio.on('connect', namespace='/test')
def test_connect():
    global thread
    if thread is None:
        thread = socketio.start_background_task(target=background_thread)
    emit('my response', {'data': 'Connected', 'count': 0})

With the above code, the initial operation part of the Response log is displayed.

Received #0: Connected Received #1: I'm connected!

By writing a handler for the event in both Python and JavaScript I found that I could define a two-way interaction.

Processing during Echo / Broadcast

Echo / Broadcast has the Form tag described as follows

index.html


<form id="emit" method="POST" action='#'>
    <input type="text" name="emit_data" id="emit_data" placeholder="Message">
    <input type="submit" value="Echo">
</form>
<form id="broadcast" method="POST" action='#'>
    <input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message">
    <input type="submit" value="Broadcast">
</form>

Submitting the form executes the handler defined in the following code. My event event and my broadcast event respectively I am firing with the data entered in the form.

index.html


$('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;
});

As a result, the following code on the Python side is executed It returns the result by firing the my response event. Almost the same code is lined up, but in the case of my broadcast event By putting broadcast = True as a keyword argument for ʻemit` Specifies that the message should be sent to all clients.

app.py


@socketio.on('my event', namespace='/test')
def test_message(message):
    session['receive_count'] = session.get('receive_count', 0) + 1
    emit('my response',                                               
         {'data': message['data'], 'count': session['receive_count']})
                                                                      
@socketio.on('my broadcast event', namespace='/test')
def test_broadcast_message(message):
    session['receive_count'] = session.get('receive_count', 0) + 1
    emit('my response',
         {'data': message['data'], 'count': session['receive_count']},
         broadcast=True)

ping / pong processing

In the part of'Average ping / pong latency' It shows how much latency there is in communicating with the server.

The code below records the communication start time every second I'm firing the my ping event. The ping_pong_times variable is an array for storing past communications.

index.html


var ping_pong_times = [];
var start_time;
window.setInterval(function() {
    start_time = (new Date).getTime();
    socket.emit('my ping');
}, 1000);

The Python side only fires my pong in response to the fire of the my ping event.

app.py


@socketio.on('my ping', namespace='/test')
def ping_pong():
    emit('my pong')

On the JavaScript side, take the difference between the time when my pong fired and the start time The average of past communication records is displayed in'Average ping / pong latency'.

app.py


socket.on('my pong', function() {
    var latency = (new Date).getTime() - start_time;
    ping_pong_times.push(latency);
    ping_pong_times = ping_pong_times.slice(-30); // keep last 30 samples
    var sum = 0;
    for (var i = 0; i < ping_pong_times.length; i++)
        sum += ping_pong_times[i];
    $('#ping-pong').text(Math.round(10 * sum / ping_pong_times.length) / 10);
});

Event generation processing on the server side

All the processing up to this point started from the client side (JavaScript). In your application, you may want to push information from the server side.

In the sample code, the handler for the connect event had the following description:

app.py


thread = socketio.start_background_task(target=background_thread)

The background_thread that is the target is defined as follows.

app.py


def background_thread():
    """Example of how to send server generated events to clients."""     
    count = 0
    while True:
        socketio.sleep(10)
        count += 1
        socketio.emit('my response',
                      {'data': 'Server generated event', 'count': count},
                      namespace='/test')

You can see that it fires the my response event every 10 seconds. It seems to be useful when automatically updating the timeline or implementing an application such as Bijin Clock.

Conclusion

I just dropped the sample code and read it, but it was helpful. It seems that you can make various things just by modifying this a little.

Although it will be necessary to read the entire source code from now on I think Flask's appeal is that it's small, including extensions, and easy to stick to.

Recommended Posts

WebSocket application with Flask-Socket IO
WebSocket with Python + uWSGI
Web application development with Flask
Web application creation with Django
Load test Websocket with Locust
Web application with Python + Flask ② ③
Rails application building with Docker
Web application with Python + Flask ④
Measure Django application coverage with Coverage.py
Deploy a Django application with Docker
Monitor Python application performance with Dynatrace ♪
Twitter posting application made with Django
Build a web application with Django
Application development with Docker + Python + Flask
Python application: Data cleansing # 2: Data cleansing with DataFrame
Application of graphs with plotly sliders
Automate Windows Application Testing with Windows Application Driver-Python