Chat en « temps réel » sous Django avec Channels

Par défaut

Le Développeur Django adore les poneys !

Je suis un développeur Django

Comme vous le savoir si vous avez lu la page d’accueil de ce site internet, je suis (entre autres) un Développeur Django, c’est l’un de mes Frameworks de prédilection pour le développement web back-end entre autres sous Python.

Un outil que j’utilise maintenant depuis une dizaine d’années, quasiment depuis le genèse de ce projet.

Python Django Framework

Pourquoi Django, Pourquoi les poneys ?

Contrairement à Flask et Pyramid sont plutot sympas. Le code source de celui-ci a été formateur pour moi et ses patterns m’ont souvent inspirés même sur d’autres technos.

Il y a pas très longtemps ( en 2016 ) j’ai découvert le projet Channels. je voulais donc vous en parler car avec du JavaScript on peut faire des choses assez intéressantes.

Voici donc pour illustrer quelques commandes et lignes de code pour illustrer le développement d’une application de chat en temps réel.

L’environnement

Pour ne pas mélanger toutes le dépendances je crée un environnement virtuel Python sur le quel j’installe Django et Channels.

virtualenv live
source live/bin/activate
pip install Django channels

Ensuite dans le dossier live, je génère la structure de mon projet

cd ./live
django-admin startproject live .

Je commence donc par configurer celui-ci en editant le fichier settings.py comme suite

INSTALLED_APPS = [
        ....
        'channels',
        'chat'
]
....
LANGUAGE_CODE = 'fr-fr'
....
TIME_ZONE = 'Europe/Paris'
....
CHANNEL_LAYERS = {
        'default': {
                'BACKEND': 'asgiref.inmemory.ChannelLayer',
                'ROUTING': 'live.routing.channel_routing'
        },
}

A quoi ressemble le code

Du coup je crée un fichier routing.py dans live avec la variable channel_routing pour aiguiller les requêtes asynchrones.

from channels import include, route
from .consumers import websocket_receive, websocket_add, websocket_disconnect

channel_routing = [
    route('websocket.connect', websocket_connect,
        path=r'^/chat/(?P<room>[-\w]+)/$'),
    route('websocket.receive', websocket_receive),
    route('websocket.disconnect', websocket_disconnect)
]

Maintenant je vais écrire les trois fonctions websocket_connect, websocket_receive et websocket_disconnect qui vont gérer les trois éventements respectivement de connexion, de reception de données et de deconnexion.

from channels import Group
from channels.sessions import channel_session
from channels.auth import channel_session_user_from_http

@channel_session_user_from_http
def websocket_add(message, room):
  name = 'Anonymous troll'
  if message.user.is_authenticated():
    name = message.user.get_full_name()
  message.reply_channel.send({
    'accept': True,
    'text': 'Bienvenue %s !' % name
  })

  message.channel_session['room'] = room
  message.channel_session['name'] = name

  Group(room).add(message.reply_channel)

@channel_session
def websocket_receive(message):
  room = message.channel_session.get('room')
  name = message.channel_session.get('name')
  Group(room).send({
    'text': "<%s> %s" % ( name,  message.content.get('text') )
  })

@channel_session
def websocket_disconnect(message):
  room = message.channel_session.get('room')
  Group(room).discard(message.reply_channel)

Ayant déjà fait du Twisted et du Celery pour gerer ce type d’architecture asyncrhone je trouve l’integration de Channels plutot élégante. avec les decorateurs, cert a ralonge, mais bon ça a le merite d’etre claire.

Ensuite comment ça marche

On peut commencer a s’amuser avec les web sockets une fois le serveur de développement lancé à l’aide de la commande

./manage.py runserver

Je navigue alors sur mon site, j’ouvre la console de mon navigateur préféré et je lance ces petites lignes

ws = new WebSocket("ws://" + window.location.host + "/chat/4chan-b/");
ws.onmessage = function(event){
  console.log(event.data);
}
ws.send("Salut les gens !");.

Voila, il reste plus qu’à développer l’application JavaScript.