[PYTHON] Application WebSocket avec Flask-Socket IO

introduction

Parce qu'il est devenu nécessaire de mettre en place une application de chat par accident J'ai étudié comment je pourrais implémenter une application WebSocket à l'aide de Flask.

J'écrirai au point où j'essaye de déplacer l'échantillon Cela suffit à lui seul pour utiliser les principales fonctions du chat.

Sélection d'extension

Je vais l'implémenter sur l'épaule de Flask Les deux extensions suivantes ont été répertoriées comme options.

Il est un peu vieux comme février 2014, mais dans l 'Article explicatif de miguelgrinberg Il y a une section qui explique la comparaison entre les deux.

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

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 utilise le protocole natif WebSocket Emballage via gevent-websocket. Utilisé uniquement dans les navigateurs modernes qui prennent en charge WebSocket de manière native. D'autre part, Flask-Socket IO peut être utilisé avec des navigateurs plus anciens.

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 est une bibliothèque JavaScript SocketIO Il implémente un protocole d'échange de messages. Flask-Sockets n'implémente que des canaux de communication Ce que vous envoyez dépend de l'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 crée un environnement pour les gestionnaires d'événements proches des fonctions d'affichage. Cela inclut la génération de contextes d'application et de contextes de requête. À quelques exceptions près, comme expliqué dans la documentation.

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

Bien que le troisième point n'ait pas été bien traduit Je pense que le fait est qu'il peut être implémenté de manière transparente dans les applications Flask existantes.

J'étais inquiet pour la fiabilité de l'auteur et de la star de GitHub au même niveau.

Par conséquent, j'ai choisi Flask-SocketIO. Si vous souhaitez entrer en contact avec le protocole WebSocket, ou côté client (JavaScript) Dois-je utiliser Flask-Sockets pour la liberté?

Cependant, il semble que vous puissiez réaliser ce que vous voulez faire de toute façon.

Préparation

Utilisez le Docker local comme dans l'article précédent (http://qiita.com/nanakenashi/items/cbe8e8ef878121638514). Mon environnement d'exécution est la bêta publique de Docker pour Mac.

version


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

J'utilise juste Docker pour créer rapidement un environnement Si vous avez un environnement dans lequel le nouveau Python peut s'exécuter dans une certaine mesure, ignorez la partie construction de l'environnement.

La source

flask_socket/
    ├ Dockerfile
    └ requirements.txt

Dockerfile

#Spécification de l'image de base
FROM python:3.5.2-alpine

#Stocker le répertoire dans lequel la source est placée en tant que variable
ARG project_dir=/web/socket/

#Installez git après la mise à jour des packages qui peuvent être installés avec apk
RUN apk update
RUN apk add git

# requirements.Installez le package répertorié dans txt
WORKDIR $project_dir
ADD requirements.txt .
RUN pip install -r requirements.txt

#Flask du référentiel GitHub-Obtenir le code source de SocketIO
RUN git clone https://github.com/miguelgrinberg/Flask-SocketIO.git Flask-SocketIO
WORKDIR $project_dir/Flask-SocketIO/example

requirements.txt

Après avoir installé Flask et Flask-Socket IO avec 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

Sauf pour les paquets dont dépend le corps de Flask Flask-SocketIO, python-engineio, python-socketio, six ont été ajoutés.

procédure

Création d'image

Création d'image


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

Démarrage du conteneur

Démarrage du conteneur


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

Ajouter du code

Ajoutez le paramètre host au code d'exécution de l'application.

app.py


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

Exécuter un exemple d'application

Exécution du flacon


$ python app.py

Lorsque vous accédez à localhost: 5000 depuis votre navigateur, vous verrez un écran comme celui ci-dessous.

Flask-SocketIO.png

Expliquez une partie du formulaire '' Envoyer: ''

La différence entre «Echo» et «Broadcast» peut être facilement comprise en organisant les onglets. Les autres éléments sont omis car ils sont interprétés comme des opérations liées à la salle de conversation. En passant, vous pouvez spécifier le nom de la salle, mais vous ne pouvez pas entrer votre propre nom.

Interprétation du code

Traitement lors de l'exécution de l'application

app.py


#Chargement des modules requis
from flask import Flask, render_template, session, request         
from flask_socketio import SocketIO, emit, join_room, leave_room, \
      close_room, rooms, disconnect                                  

#Spécification de la bibliothèque à utiliser pour le traitement asynchrone
# `threading`, `eventlet`, `gevent`Peut être sélectionné parmi
async_mode = None

#Créez un objet Flask et spécifiez la clé pour le chiffrement des informations de session
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'

#Objet Flask, asynchrone_Créer un objet serveur SocketIO en spécifiant le mode
socketio = SocketIO(app, async_mode=async_mode)

#Variable globale pour stocker les threads
thread = None

app.py


#Démarrez le serveur SocketIO en mode débogage
socketio.run(app, debug=True)

Que faire lorsque la page est ouverte

La bibliothèque SocketIO (JavaScript) est chargée dans le script en html.

index.html


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

Après cela, spécifiez l'espace de noms (point de terminaison de la communication WebSocket) et Une connexion est établie avec le serveur SocketIO.

Spécification des gestionnaires pour les événements émis lors de la connexion au serveur Le gestionnaire de l'événement my response est également spécifié ici. La réponse semble être ajoutée à la balise spécifiée par le sélecteur # log.

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

Côté serveur, recevez une demande de connexion du client avec le code suivant La réponse est renvoyée en déclenchant l'événement «ma réponse». La partie de socketio.start_background_task (target = background_thread) sera décrite plus tard.

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})

Avec le code ci-dessus, la partie de l'opération initiale du journal de réponse s'affiche.

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

En écrivant un gestionnaire pour l'événement à la fois en Python et en JavaScript J'ai trouvé que je pouvais définir une interaction bidirectionnelle.

Traitement pendant l'écho / la diffusion

Echo / Broadcast a la balise Form décrite comme suit

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>

La soumission du formulaire exécute le gestionnaire défini dans le code suivant. Événement «Mon événement» et «mon événement de diffusion» respectivement Je tire avec les données saisies dans le formulaire.

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;
});

En conséquence, le code suivant du côté Python est exécuté Renvoyer le résultat en déclenchant l'événement my response. Presque le même code est aligné, mais dans le cas de "mon événement de diffusion" En mettant broadcast = True comme argument de mot-clé pour ʻemit` Spécifie que le message doit être envoyé à tous les 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)

traitement ping / pong

Dans la partie de "Latence moyenne ping / pong" Il montre la latence de la communication avec le serveur.

Le code ci-dessous enregistre l'heure de début de la communication toutes les secondes Je déclenche l'événement my ping. La variable ping_pong_times est un tableau pour stocker les communications passées.

index.html


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

Le côté Python ne déclenche my pong qu'en réponse au déclenchement de l'événement my ping.

app.py


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

Du côté JavaScript, prenez la différence entre l'heure à laquelle mon pong a tiré et l'heure de début La moyenne des enregistrements de communication passés est affichée dans «Latence moyenne ping / pong».

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);
});

Traitement de la génération d'événements côté serveur

Tout le traitement jusqu'à ce point a commencé du côté client (JavaScript). Dans votre application, vous souhaiterez peut-être envoyer des informations du côté serveur.

Dans l'exemple de code, le gestionnaire de l'événement connect avait la description suivante:

app.py


thread = socketio.start_background_task(target=background_thread)

Le "background_thread" qui est "cible" est défini comme suit.

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')

Vous pouvez voir qu'il déclenche l'événement ma réponse toutes les 10 secondes. Cela semble utile lors de la mise à jour automatique de la chronologie ou de la mise en œuvre d'une application telle que Bijin Clock.

Conclusion

Je viens de déposer l'exemple de code et de le lire, mais cela a été utile. Il semble que vous puissiez faire diverses choses simplement en le modifiant un peu.

Bien qu'il soit désormais nécessaire de lire l'intégralité du code source Je pense que l'attrait de Flask est qu'il est petit même s'il comprend des extensions, et il est facile de s'y tenir.

Recommended Posts

Application WebSocket avec Flask-Socket IO
WebSocket avec Python + uWSGI
Développement d'applications Web avec Flask
Créer une application Web avec Django
Test de charge Websocket avec Locust
Application Web avec Python + Flask ② ③
Créer une application Rails avec Docker
Application Web avec Python + Flask ④
Mesurer la couverture des applications Django avec Coverage.py
Déployer l'application Django avec Docker
Surveillez les performances des applications Python avec Dynatrace ♪
Application de publication sur Twitter réalisée avec Django
Créer une application Web avec Django
Développement d'applications avec Docker + Python + Flask
Application de Python: Nettoyage des données Partie 2: Nettoyage des données à l'aide de DataFrame
Application de graphiques avec des curseurs
Automatisez les tests d'applications Windows avec Windows Application Driver-Python Edition