Mettez du temps réel dans votre Drupal avec NodeJS
Mettez du temps réel dans votre Drupal avec NodeJS
Qui sommes nous ?
● Matthieu Guillermin - MultitaskConsultant chez @mguillermin
● Nicolas Chambrier - Nodefag TrollDéveloppeur chez @naholyr
● On va de plus en plus vers des architectures
hétérogènes
● Utiliser chaque techno pour ce qu'elle sait
bien faire
● Les besoins de "temps-réels" sont de plus
en plus présents
Pourquoi intégrer Drupal / NodeJS ?
Le Cas d'Utilisation
Le Cas d'Utilisation
● Notre but était de :○ Utiliser Drupal pour ce qu'il fait de mieux
○ Utiliser NodeJS pour ce qu'il fait de mieux
○ Trouver un cas d'utilisation réellement utile
● En utilisant des fonctionnalités classiques à
la fois pour Drupal et pour NodeJS
Le Cas d'Utilisation
● Drupal :○ Gestion de contenu
○ Contribution
○ Workflow
● NodeJS :○ Mise en place d'une API REST
○ Application "temps réel"
Le Cas d'Utilisation
● Système de notification○ Messages affichés directement dans le navigateur
○ En réponse à des événements Drupal
○ Envoi en "temps-réel"
● Paramétrable○ Définition de règles d'envoi de messages
○ Définition des destinataires des message
Comment le réaliser ?
Comment le réaliser ?
● Utilisation de standards Web
● Communication depuis Drupal○ Web Services
○ REST
● Communication depuis le client○ Accès direct au serveur NodeJS
Comment le réaliser ?
Serveur NodeJSDrupal
Navigateur client
Navigateur clientNavigateur
client
Action
Appel Web Service
Dispatch & Notification
Mise en oeuvre NodeJS
Comment on a travaillé
● Pas de super-héros !○ Je n'y connais rien à Drupal○ Il n'y connait pas grand chose à Node
● Il faut:○ Savoir installer et faire tourner le travail de l'autre
■ Drupal: LAMP + dump MySQL■ Node: NodeJS → `node app.js`
○ Définir l'API avant tout■ Sur un coin de table 3 jours avant
○ C'est tout !
Un serveur HTTP + WebSocket
● On veut que ce soit simple○ Toute la stack dans une seule application○ Notifier = une requête HTTP simple○ Afficher les notifications = une inclusion de script +
une ligne de config
● On veut que ce soit sécurisé, performant, et scalable○ Oui mais c'est un tout autre sujet ;)
One app to rule them all
Notifications
Fichiers statiques WebSockets
var app = http.createServer(handler) function handler (req, res) {
if (req.url === "/" && req.method === "POST") { newNotification(req, res) }
else { serveStatic(req, res); }}
var io = require('socket.io') io.listen(app)
Modèle de données
● Identifier les paramètres requis:
○ On veut un message→ "message": String
○ On veut cibler les utilisateurs par rôle→ "roles": [String]
○ On veut éventuellement cibler par utilisateur→ "users": [String]
● JSON: { "roles": ["administrator"], "users": [1], "message": "hello, world" }
API REST
● JSON
● Création = POST● Réponse adaptée:
201 - Created400 - Bad Request500 - Server Error
● Broadcast viaWebSocket
function newNotification (req, res) { if (!isJSON(req)) { error400() } var notif = JSON.parse(req.body) validateNotif(notif) notif.id = uuid() broadcastViaWebSocket(notif) respond(res, 201, "application/json", JSON.stringify(notif))}
API Client
● Script servi parl'application
● Une seule fonction
● Ajoute seul lesdépendancesJS & CSS
● Enrichit le DOM
● Gère le WebSocket
function display (id, roles) { addJS("socket.io.js") addCSS("notifications.css") addDOMContainer() var socket = io.connect() socket.on('connect', function () { socket.emit('roles', roles) socket.emit('user_id', id) }) socket.on('error', function (e) { notifyError(e) }) socket.on('notif', function (n) { notifyInfo(n) }) }
API : Utilisation
Client
Serveur
Simple enough ?
<script src="http://localhost:8080/notifications.js"></script><script>notifications.display(1, ["administrator"])</script>
POST http://localhost:8080Content-Type: application/json {"roles":["administrator"],"message":"Show me your big power"}
Socket.IO
● Pourquoi Socket.IO ?○ Puissant, simple, efficace, hyper-compatible.
● Comment distribuer les messages ?○ Filtrer côté client ? Pas efficace○ Côté serveur: comment router les message ?
● Socket.IO à la rescousse: les "rooms":○ Client: socket.join("room")○ Serveur: io.sockets.in("room").emit(data)○ Nos salons: "USER:$id" & "ROLE:$role"
● Problème des notifications en double
Socket.IO - implémentation
● Client
● Serveur
● Notification
socket.on('connect', function () { socket.emit('user_id', user_id);});
socket.on('user_id', function (user_id) { socket.join('USER:' + user_id);});
notification.users.forEach(function (id) { io.sockets .in('USER:' + id) .emit('notification', notification);});
Résultat [WARNING: PROTOTYPE]
naholyr/node-drupal-notifications
Mise en oeuvre Drupal
Appel de Web Service
● Utilisation du module wsclient○ Permet de définir des "services"
○ Et sur chaque services, des "opérations"
● Différents types de services : ○ SOAP
○ REST
Appel de Web Service
● Les opérations sont définies avec○ une URL
○ une Method HTTP
○ des paramètres
○ un type de retour
● Paramètres○ Type, Required, Multiple, Default value
Appel de Web Service
● Paramétrage possible ○ depuis l'interface
○ via l'API
● L'utilisation de l'API offre plus de possibilités
○ Ex : HTTP Method de l'opération
○ Ex : Format à utiliser (Json, Form,...)
Appel de Web Service
● Une fois définis, les opérations peuvent être
appelées via l'API
$serviceName = "notification";$operationName = "send_message"; $service = wsclient_service_load($serviceName); $service->invoke($operationName,
array("message" => "Ceci est mon message"));
Déclenchement des envois de message
● La solution : ○ Utiliser l'API de wsclient directement en
l'implémentant dans un module
● Problème :○ Tout doit être prévu dans le code (quand envoyer le
message ? à qui ? ...)
Déclenchement des envois de message
● Utilisation de Rules○ wsclient expose les opérations sous forme d'actions
Rules
○ Avec possibilité de renseigner les paramètres
● Avantages○ Tout est configurable depuis le backoffice
○ Les valeurs des paramètres peuvent contenir des
tokens
Intégration javascript "client"
● Pour communiquer avec NodeJS, il faut
inclure un javascript côté client○ Ce script doit être chargé systématiquement sur
chaque page
● On doit aussi initialiser en js l'Id et les rôles
de l' utilisateur○ Utilisation de drupal_add_js()
Intégration javascript "client"function node_demo_init() { drupal_add_js('http://localhost:8080/notifications.js', 'file'); global $user; drupal_add_js(array( 'node_demo' => array( 'uid' => $user->uid, 'roles' => array_values($user->roles), ) ), 'setting');}
Résultat [WARNING: PROTOTYPE]
mguillermin/node-drupal-notifications-demo
Démonstration
Pistes d'amélioration
● Ajout de nouveaux attributs aux messages○ Type
○ Priorité / "Sticky"
○ Emetteur
○ ...
● A ajouter à l'API REST, dans la définition de
service wsclient et à gérer dans le javascript
client
Pistes d'amélioration
● Gérer l'authentification utilisateur côté
NodeJS○ Pour s'assurer que l'ID utilisateur envoyé est bien
celui de l'utilisateur courant
● Mécanisme de jeton envoyé par le client et
vérifié par NodeJS auprès de Drupal (SSO)○ Cette vérification renverrait l'ID et les rôles
utilisateurs
Pistes d'amélioration
● Sécuriser l'API REST○ Eviter qu'un tiers n'utilise l'API REST pour envoyer
des messages en n'autorisant les envois de
message que depuis Drupal
● Utilisation d'une clé d'API○ la clé serait validée à chaque appel d'API
● Améliorer l'UI des notifications○ Afficher/cacher
○ Persister les notifications entre les pages (statut
lu/non lu)
● Utiliser un vrai pub/sub pour la transmission○ Redis, RabbitMQ, ØMQ…
● Les améliorations de performance du serveur
Pistes d'amélioration
@naholyr / @mguillermin