Les applications en temps réel comme WebSockets sont utilisées chaque jour, maintenant plus que jamais avec l’essor des sites Web de médias sociaux tels que Twitter, Google+ et Facebook, entre autres. Les serveurs transmettent les mises à jour aux clients au lieu d’interroger les modifications à intervalles réguliers – c’est ce qu’on appelle un « WebSocket ». Dans l’ensemble, cela a été une avancée fantastique, mais il a ses inconvénients qui se sont avérés problématiques pour les développeurs Python. Cet article vous aidera à créer un serveur websocket en utilisant Python.

Avec les derniers développements du langage Python et avec l’aide de frameworks tels que Tornado, la gestion des connexions Websocket ou HTTP/2 est devenue beaucoup plus facile. Tornado est une bibliothèque de mise en réseau Python qui permet aux développeurs de construire des serveurs HTTP en Python et qui leur offre la possibilité d’aborder plus facilement WebSocket et d’autres transmissions réseau. Tornado fournit des fonctionnalités de serveur Web en Python qui permettent aux développeurs expérimentés de mieux gérer ces connexions.

Cet article illustrera les concepts de la programmation asynchrone et de la gestion de plusieurs connexions. nous montrerons comment configurer un serveur de salle de discussion simple et permettre à plusieurs clients de s’y connecter.

Que sont les Websockets ?

WebSocket est un protocole de communication, fournissant une communication en duplex intégral sur une seule connexion TCP. WebSocket est une combinaison de technologies qui permet des communications bidirectionnelles complètes entre un navigateur et un serveur. Cela facilite les transferts de données en temps réel, car cela vous permet de contrôler simultanément les efforts du client et du serveur sans délai. Il a une surcharge inférieure à celle des alternatives semi-duplex telles que l’interrogation HTTP, facilitant le transfert de données en temps réel depuis et vers le serveur.

La faible latence et la multidirectionnalité de WebSockets sont particulièrement utiles pour les applications qui doivent envoyer ou recevoir des messages tout en recevant des réponses du serveur. Étant donné que les WebSockets commencent par une requête HTTP, ils sont 100 % compatibles avec l’infrastructure Web existante et peuvent même s’exécuter sur le même port que vos requêtes Web existantes, telles que celles effectuées sur les ports 80 ou 443 (par exemple). Cette compatibilité simplifie la sécurité cross-origin, ce qui signifie que quiconque sur l’infrastructure côté client ou côté serveur n’a pas à modifier ses réseaux afin de prendre en charge des communications sécurisées à l’aide de WebSockets.

Conditions préalables

Lors de l’ajout de la prise en charge de WebSocket à votre application, vous devez utiliser un serveur Web tel que Tornado qui intègre la prise en charge de WebSocket.

Tornado est livré avec sa propre implémentation de WebSocket qui permet de créer un serveur WebSocket à l’aide de Python. Pour les besoins de cet article, c’est exactement ce dont nous aurons besoin. Si vous n’avez aucune expérience et aucune idée de ce genre de choses, réinventer la roue n’est peut-être pas une bonne idée. Au lieu de cela, commencer par un exemple concret et le comprendre serait la meilleure approche pour vous, et c’est exactement ce que nous allons faire ici !

Le code utilise le concept de WebSockets et la bibliothèque de mise en réseau Tornado utilisant le langage Python, et c’est tout ce dont nous avons besoin. Nous pouvons installer WebSockets et Tornado en utilisant les commandes pip suivantes :

pip install tornado
pip install websockets

Configuration du fichier main.py

Le serveur de la salle de discussion et le client de la salle de discussion sont implémentés ensemble dans un seul fichier pour des raisons de simplicité. Tout d’abord, créez un fichier main.py et importez les modules requis pour créer un serveur websocket à l’aide de Python :

#main.py

import tornado.escape  #for escaping/unescaping methods for HTML, JSON, URLs, etc
import tornado.ioloop  #event loop for non-blocking sockets
import tornado.options  #command line parsing module to define options
import tornado.web  #provides a simple web framework with asynchronous featuresfrom tornado.options import define, options
import tornado.websocket  #implementation of the WebSocket protocol and bidirectional communication
import logging
import os.path
import uuid

from tornado.options import define, options

Définir le reste des fonctions, y compris le côté clients. Toutes les choses que vous pourriez avoir besoin de savoir sont prédéfinies dans le code ci-dessous :

define("port", default=8080, type=int)  #setting the port to 8000 which can easily be changed


class Application(tornado.web.Application):  #setting a tornado class as a web application
    def __init__(self):
        handlers = [(r"/", MainHandler), (r"/chatsocket", ChatSocketHandler)] #setting the nesassary urls
        settings = dict(
            cookie_secret="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__",
            template_path=os.path.join(os.path.dirname(__file__), "templates"),  #providing the templates path
            static_path=os.path.join(os.path.dirname(__file__), "static"),  #providing the static folder's path
            xsrf_cookies=True,
        )
        super().__init__(handlers, **settings)


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", messages=ChatSocketHandler.cache)

Définir la poignée de main WebSocket

class ChatSocketHandler(tornado.websocket.WebSocketHandler):  #creating our main websocket class
    waiters = set()  #set number of joinable users
    cache = []
    cache_size = 200

    def get_compression_options(self):
        #non-none enables compression with default options.
        return {}

    def open(self):  #accepts a connection request and stores the parameters, a socket object for that user
        ChatSocketHandler.waiters.add(self)

    def on_close(self):  #removes the user by removing the object
        ChatSocketHandler.waiters.remove(self)

    @classmethod 
    def update_cache(cls, chat):  #Maintains a list of chat for broadcasting the messages
        cls.cache.append(chat)
        if len(cls.cache) > cls.cache_size:
            cls.cache = cls.cache[-cls.cache_size :]

    @classmethod 
    def send_updates(cls, chat):  #mange sending messages
        logging.info("sending message to %d waiters", len(cls.waiters))
        for waiter in cls.waiters:
            try:
                waiter.write_message(chat)  #outputting the messages
            except:  #except, in case of any errors
                logging.error("Error sending message", exc_info=True)

    def on_message(self, message):
        logging.info("got message %r", message)
        parsed = tornado.escape.json_decode(message)
        chat = {"id": str(uuid.uuid4()), "body": parsed["body"]}
        chat["html"] = tornado.escape.to_basestring(
            self.render_string("message.html", message=chat)
        )

        ChatSocketHandler.update_cache(chat)
        ChatSocketHandler.send_updates(chat)

Le protocole WebSocket définit deux types de transmissions spécialisés : la poignée de main WebSocket (send_updates) et le message WebSocket (on_message). La poignée de main WebSocket est utilisée pour établir une connexion entre le client et le serveur, tandis que le message WebSocket est envoyé une fois la connexion établie.

def main():  #and to close up everything
    tornado.options.parse_command_line()
    app = Application()
    app.listen(options.port)
    tornado.ioloop.IOLoop.current().start()


if __name__ == "__main__":
    main()

Et enfin, fermez la fonction principale (qui contient les WebSockets).

Configuration des modèles

Après avoir créé le fichier ci-dessus, nous devons maintenant créer le modèle et les fichiers statiques. Comme défini précédemment, nous devrons d’abord créer les dossiers pour conserver ces fichiers, qui seraient nommés « templates » et « static ». Nous pouvons commencer par créer les modèles comme indiqué :

#templates/index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Tornado Chat Demo</title>
        <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
  </head>
  <body>
    <div id="body">
      <div id="inbox">
        {% for message in messages %}
          {% include "message.html" %}
        {% end %}
      </div>
      <div id="input">
        <form action="/a/message/new" method="post" id="messageform">
          <table>
            <tr>
              <td><input type="text" name="body" id="message" style="width:500px"></td>
              <td style="padding-left:5px">
                <input type="submit" value="{{ _("Post") }}">
                <input type="hidden" name="next" value="{{ request.path }}">
                {% module xsrf_form_html() %}
              </td>
            </tr>
          </table>
        </form>
      </div>
    </div>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js" type="text/javascript"></script>
    <script src="{{ static_url("chat.js") }}" type="text/javascript"></script>
  </body>
</html>

Et maintenant les fichiers statiques :

#static/chat.js

$(document).ready(function() {
    if (!window.console) window.console = {};
    if (!window.console.log) window.console.log = function() {};

    $("#messageform").on("submit", function() {
        newMessage($(this));
        return false;
    });
    $("#messageform").on("keypress", function(e) {
        if (e.keyCode == 13) {
            newMessage($(this));
            return false;
        }
    });
    $("#message").select();
    updater.start();
});

function newMessage(form) {
    var message = form.formToDict();
    updater.socket.send(JSON.stringify(message));
    form.find("input[type=text]").val("").select();
}

jQuery.fn.formToDict = function() {
    var fields = this.serializeArray();
    var json = {}
    for (var i = 0; i < fields.length; i++) {
        json[fields[i].name] = fields[i].value;
    }
    if (json.next) delete json.next;
    return json;
};

var updater = {
    socket: null,

    start: function() {
        var url = "ws://" + location.host + "/chatsocket";
        updater.socket = new WebSocket(url);
        updater.socket.onmessage = function(event) {
            updater.showMessage(JSON.parse(event.data));
        }
    },

    showMessage: function(message) {
        var existing = $("#m" + message.id);
        if (existing.length > 0) return;
        var node = $(message.html);
        node.hide();
        $("#inbox").append(node);
        node.slideDown();
    }
};

Créez également un fichier message.html qui contiendra une partie du code js supplémentaire.

#template/message.html

<div class="message" id="m{{ message["id"] }}">{% module linkify(message["body"]) %}</div>

Et tout est prêt !

Sortir 

Exécutez le projet en exécutant le nom du fichier dans le terminal, mais n’afficherait aucune réponse, sauf en cas d’erreur. Nous devrons aller au port que nous avons défini précédemment, qui était 8080. Ouvrez ce lien http://127.0.0.1:8080/ et vous devriez voir quelque chose comme ceci :

Vous pouvez essayer d’ouvrir un autre navigateur pour voir s’il fonctionne ou non, et après avoir posté quelque chose, vous devriez également voir les réponses imprimées sur votre terminal :

Pour vérifier ses capacités asynchrones ou ce que nous avons réellement fait avec WebSockets, vous pouvez inspecter ses performances réseau à l’aide de n’importe quel ancien navigateur :

Le mot de la fin

Et voilà, dans cet article, nous avons appris à créer un serveur websocket en utilisant Python et la bibliothèque réseau Tornado. Maintenant, il y a beaucoup de place à l’amélioration ici, comme l’authentification. Il existe également de nombreuses autres bibliothèques que vous pouvez également utiliser, mais Tornado est de loin la plus conviviale pour les débutants et possède une bibliothèque de documentation. Si vous souhaitez approfondir vos recherches, c’est le meilleur endroit où aller.

En outre, vous pouvez vous référer à cette structure de fichiers si vous vous perdez :

│   main.py
│   playbook.yml
│
├───static
│       chat.js
│
└───templates
        index.html
        message.html

Voici quelques tutoriels utiles que vous pouvez lire :