Pourquoi WebSockets avec Django ?
Dans un monde où les applications web évoluent constamment vers une expérience utilisateur plus dynamique et réactive, les WebSockets sont devenus un atout majeur. En effet, ils permettent aux serveurs de transmettre des données en temps réel à leurs clients, sans la nécessité d'un rechargement complet de la page. C'est particulièrement utile pour des applications comme les chatbots, les jeux interactifs, les plateformes de collaboration en temps réel, ou tout autre système où le flux continu d'informations est essentiel.
Un cas concret de l'utilisation de WebSockets avec Django est une application de messagerie instantanée. Dans ce cas, chaque fois qu'un message est envoyé, il doit être immédiatement visible pour tous les utilisateurs connectés, sans avoir besoin d'actualiser la page manuellement.
Prerequis
- Connaissance avancée de Python et Django
- Connaissances en JavaScript et HTML/CSS (pour le frontend)
- Installation de Docker (facultatif mais recommandé pour une meilleure isolation des environnements)
Concepts fondamentaux
1. WebSockets vs HTTP
Les WebSockets sont différents des requêtes HTTP traditionnelles :
- HTTP : Basé sur un modèle request-response, chaque requête nécessite une réponse avant la prochaine.
- WebSockets : Établit une connexion persistante entre le client et le serveur. Une fois établie, les deux parties peuvent envoyer et recevoir des données à tout moment.
2. Channels
Django Channels est un package tiers qui permet d'ajouter la prise en charge de WebSockets à Django. Il étend les capacités du framework pour gérer le transport de messages dans une application concurrente.
3. Consumers
Les consumers sont des classes ou fonctions qui traitent les messages entrants et sortants. Ils peuvent être synchrones ou asynchrones, ce qui permet une grande flexibilité.
4. Channels Layer
Le channels layer est un mécanisme de communication entre les workers et les consumers. Il stocke les données en attente pour chaque consumer.
Mise en pratique : projet fil rouge
Nous allons construire une application simple d'un chat interactif en utilisant Django et Channels. L'application permettra aux utilisateurs de se connecter, d'échanger des messages en temps réel, et de voir les messages apparaître instantanément pour tous.
Étape 1 : Initialisation du projet
## Créer un nouveau projet Django
django-admin startproject chat_app
## Accéder au dossier du projet
cd chat_app
## Créer une nouvelle application Django
python manage.py startapp chat
Ajoutez l'application chat à la liste des applications dans settings.py :
INSTALLED_APPS = [
...
'channels',
'chat',
]
Étape 2 : Configuration de Channels
Créez un fichier asgi.py à la racine du projet pour configurer ASGI (Asynchronous Server Gateway Interface) :
## asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from chat import consumers
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'chat_app.settings')
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": URLRouter([
path("ws/chat/", consumers.ChatConsumer.as_asgi()),
]),
})
Étape 3 : Création du consumer
Créez un fichier consumers.py dans l'application chat :
## chat/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Broadcast the message to all connected clients
await self.channel_layer.group_send(
'chat_group',
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the received message back to the client
await self.send(text_data=json.dumps({
'message': message
}))
Étape 4 : Configuration des groupes
Ajoutez le code suivant à asgi.py pour gérer les groupes de clients :
## asgi.py
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter, ChannelNameRouter
from django.core.asgi import get_asgi_application
from chat.consumers import ChatConsumer
application = ProtocolTypeRouter({
"http": get_asgi_application(),
"websocket": AuthMiddlewareStack(
URLRouter([
path("ws/chat/", ChatConsumer.as_asgi()),
])
),
})
Étape 5 : Configuration de Channels dans settings.py
Ajoutez les configurations nécessaires pour Channels :
## settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
Étape 6 : Création du template HTML
Créez un fichier index.html dans le dossier templates/chat/ :
<!-- templates/chat/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat App</title>
<script type="text/javascript">
const socket = new WebSocket(`ws://${window.location.host}/ws/chat/`);
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
const messageElement = document.createElement('div');
messageElement.textContent = data.message;
document.getElementById('messages').appendChild(messageElement);
};
function sendMessage() {
const input = document.getElementById('message-input');
socket.send(JSON.stringify({
'message': input.value
}));
input.value = '';
}
</script>
</head>
<body>
<h1>Chat App</h1>
<div id="messages"></div>
<input type="text" id="message-input">
<button onclick="sendMessage()">Send</button>
</body>
</html>
Étape 7 : Création de la route pour le template
Ajoutez une URL pour accéder à la page du chat :
## chat/urls.py
from django.urls import path
from .views import index
urlpatterns = [
path('', index, name='index'),
]
Ajoutez cette URL au fichier urls.py principal du projet :
## chat_app/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('chat.urls')),
]
Étape 8 : Création de la vue pour le template
Créez un fichier views.py dans l'application chat :
## chat/views.py
from django.shortcuts import render
def index(request):
return render(request, 'chat/index.html')
Erreurs frequentes et debugging
1. Erreur : TypeError: 'NoneType' object is not callable
Code incorrect :
## consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Broadcast the message to all connected clients
await self.channel_layer.group_send(
'chat_group',
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the received message back to the client
await self.send(text_data=json.dumps({
'message': message
}))
Code correct :
## consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Broadcast the message to all connected clients
await self.channel_layer.group_send(
'chat_group',
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the received message back to the client
await self.send(text_data=json.dumps({
'message': message
}))
2. Erreur : AttributeError: module 'channels.layers' has no attribute 'InMemoryChannelLayer'
Code incorrect :
## settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
Code correct :
## settings.py
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels.layers.InMemoryChannelLayer',
},
}
3. Erreur : AttributeError: module 'channels.generic.websocket' has no attribute 'AsyncWebsocketConsumer'
Code incorrect :
## consumers.py
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Broadcast the message to all connected clients
await self.channel_layer.group_send(
'chat_group',
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the received message back to the client
await self.send(text_data=json.dumps({
'message': message
}))
Code correct :
## consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def disconnect(self, close_code):
pass
async def receive(self, text_data):
text_data_json = json.loads(text_data)
message = text_data_json['message']
# Broadcast the message to all connected clients
await self.channel_layer.group_send(
'chat_group',
{
'type': 'chat_message',
'message': message
}
)
async def chat_message(self, event):
message = event['message']
# Send the received message back to the client
await self.send(text_data=json.dumps({
'message': message
}))
Pour aller plus loin
- Authentification JWT avec Channels : Utilisez un token JWT pour l'authentification des utilisateurs avant de leur permettre d'échanger des messages.
- Gestion des groupes avancée : Implémentez la gestion des groupes pour différer les messages aux utilisateurs spécifiques ou aux groupes de utilisateurs.
- Personnalisation du consumer : Créez des consumers plus complexes pour gérer différents types de messages et des événements.
Défi pratique : Ajoutez une fonctionnalité permettant à chaque utilisateur de créer un groupe privé avec d'autres utilisateurs.