Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Mettez du temps réel dans votre Drupal avec Node JS

Plus De Contenu Connexe

Mettez du temps réel dans votre Drupal avec Node JS

  1. 1. Mettez du temps réel dans votre Drupal avec NodeJS
  2. 2. Qui sommes nous ? ● Matthieu Guillermin - Multitask Consultant chez @mguillermin ● Nicolas Chambrier - Nodefag Troll Développeur chez @naholyr
  3. 3. Pourquoi intégrer Drupal / NodeJS ? ● 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
  4. 4. Le Cas d'Utilisation
  5. 5. 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
  6. 6. Le Cas d'Utilisation ● Drupal : ○ Gestion de contenu ○ Contribution ○ Workflow ● NodeJS : ○ Mise en place d'une API REST ○ Application "temps réel"
  7. 7. 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
  8. 8. Comment le réaliser ?
  9. 9. Comment le réaliser ? ● Utilisation de standards Web ● Communication depuis Drupal ○ Web Services ○ REST ● Communication depuis le client ○ Accès direct au serveur NodeJS
  10. 10. Comment le réaliser ? Appel Web Service Serveur Drupal NodeJS Dispatch & Action Notification Navigateur Navigateur Navigateur client client client
  11. 11. Mise en oeuvre NodeJS
  12. 12. 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 !
  13. 13. 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 ;)
  14. 14. One app to rule them all var app = http.createServer(handler) Notifications function handler (req, res) { if (req.url === "/" && req.method === "POST") { newNotification(req, res) } else { Fichiers statiques serveStatic(req, res); } } WebSockets var io = require('socket.io') io.listen(app)
  15. 15. 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" }
  16. 16. API REST function newNotification (req, res) ● JSON { if (!isJSON(req)) { ● Création = POST error400() } ● Réponse adaptée: 201 - Created var notif = JSON.parse(req.body) 400 - Bad Request validateNotif(notif) 500 - Server Error notif.id = uuid() ● Broadcast via broadcastViaWebSocket(notif) WebSocket respond(res, 201, "application/json", JSON.stringify(notif)) }
  17. 17. function display (id, roles) { API Client addJS("socket.io.js") addCSS("notifications.css") ● Script servi par addDOMContainer() l'application var socket = io.connect() ● Une seule fonction socket.on('connect', function () { socket.emit('roles', roles) ● Ajoute seul les socket.emit('user_id', id) dépendances }) JS & CSS socket.on('error', function (e) { notifyError(e) }) ● Enrichit le DOM socket.on('notif', function (n) { notifyInfo(n) }) ● Gère le WebSocket }
  18. 18. API : Utilisation Client <script src="http://localhost:8080/notifications.js" ></script> <script>notifications.display(1, ["administrator"])</script> Serveur POST http://localhost:8080 Content-Type: application/json {"roles":["administrator"],"message":"Show me your big power"} Simple enough ?
  19. 19. 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
  20. 20. Socket.IO - implémentation ● Client socket.on('connect', function () { socket.emit('user_id', user_id); }); socket.on('user_id', function (user_id) ● Serveur { socket.join('USER:' + user_id); }); notification.users.forEach(function (id) { ● Notification io.sockets .in('USER:' + id) .emit('notification', notification); });
  21. 21. Résultat [WARNING: PROTOTYPE] naholyr/node-drupal-notifications
  22. 22. Mise en oeuvre Drupal
  23. 23. 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
  24. 24. 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
  25. 25. 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,...)
  26. 26. 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"));
  27. 27. 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 ? ...)
  28. 28. 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
  29. 29. 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()
  30. 30. 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'); }
  31. 31. Résultat [WARNING: PROTOTYPE] mguillermin/node-drupal-notifications-demo
  32. 32. Démonstration
  33. 33. 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
  34. 34. 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
  35. 35. 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
  36. 36. Pistes d'amélioration ● 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
  37. 37. @naholyr / @mguillermin

×