Archive

Articles taggués ‘Leilas’

Node.js et Websocket sur mobile : Une combinaison gagnante !

Dans le cadre du développement de l’application iPhone du projet Leilas, j’ai voulu mettre en place un système de communication multi-plex ou bidirectionnelle, pour permettre des mises à jour temps réel de mes données.

Le projet Leilas consiste à géo localiser le contenu de documents ou de sites web pour leur donner une dimension géographique. Concrètement, on traite des documents via un portail web pour en extraire des lieux et construire ainsi une base de connaissance exploitable.

Comme application, nous avons réalisé un portail orienté tourisme et des applications mobiles permettant de visualiser la base de connaissance sur une carte et de créer des itinéraires à partir de ces lieux. Les plateformes mobiles accédent aux données via des web services de type REST.

Le rendu sur  iPhone est présenté sur une carte (voir ci-dessous).


Le problème

Lorsqu’un utilisateur insère un document via le portail et de  nouveaux lieux sont trouvés, les utilisateurs sur mobile ne sont pas notifiés et la carte n’est pas mise à jour. Beaucoup de solutions existent pour résoudre ce problème mais elles ne sont pas toutes simples et surtout pas toutes appropriées pour le mobile.

Une première solution naïve serait de faire du   « server polling » pour vérifier si nos données sont à jour. À titre d’expérimentation, j’ai implémenté une solution faisant un appel au serveur toutes les « X » millisecondes pour faire un « timestamp check ». Cette approche comporte plusieurs problèmes. Premièrement, une forte consommation de la batterie des téléphones, nous le confirmerons un peu plus tard. Deuxièmement, même si moins problématique, nous constatons une surcharge de connexions coté serveur et de bande passante dû à l’en-tête-http de chaque polling.

Alors comment faire proprement du push sur mobile?

 Pour éviter de faire du polling mais pour garder les avantages du protocole http, je me suis appuyé sur la solution WebSocket, un standard dont le « handshake » repose sur http et le port 80,  qui permet des communications bidirectionnels. On peut également utiliser Websocket en SSL avec WSS pour s’assurer de passer la plupart des pare-feux.

Faire du Websocket coté client sur un mobile n’est pas très complexe, vous trouverez plusieurs solutions open source, y compris en Objective-C compatible iOS. Coté serveur, il existe également plusieurs implémentations, mais pas toutes scalables. Voici un article comparant Atmosphere, Netty, Vert.x et Node.js : http://weblog.plexobject.com/?p=1698).

Je m’impose aussi comme contrainte de ne pas modifier ma stack JEE existante (Tomcat6, Jersey, Spring…). J’ai donc choisi d’ajouter un serveur « on the side » qui fera lui -même le polling et se chargera de notifier tous les mobiles via Websocket.

Mon choix s’est porté sur Node.js. Cette technonologie propose une approche évènementielle permettant des appels à des webservices de façon complètement asynchrones et intègre WebSocket via un module Socket.io d’une simplicité diabolique.

Voici un schéma simplifié de l’architecture mise en place :

Un peu plus d’explication sur Node.js

Le modèle de Node est diffèrent des architectures plus classiques, qui tendent à coupler une connexion à un thread. Node possède un « front thread » qui traite toutes les requêtes entrantes et délègue tout I/O à un « background thread pool ». De par ses API JavaScript, il est très facile de créer ses propres « events » et d’interagir avec la « run loop » principale. Cet article, « Future proofing your apps : Cloudfoundry and Node.js » explique clairement pourquoi Node ne se résume pas seulement à un framework web JavaScript et pourquoi son approche est parfaitement « scalable ».

D’autres technos reprennent ces principes, donc pourquoi Node.js plutôt qu’une autre ?

Il est vrai qu’il existe des alternatives, par exemple Vert.x (http://vertx.io/) est l’équivalent++ de Node en java. La réalité c’est que Node.js possède déjà une énorme communauté, avec un « repository » de Modules (npm : https://npmjs.org/) pour faire à peu près tout ce qu’on veut. Un autre argument en sa faveur est le nombre de PaaS qui le supportent aujourd’hui, dont plusieurs sont « WebSocket compatible ».
Personnellement, ce qui m’a attiré le plus c’est sa simplicité.

Voici un bout de code JavaScript/Node pour récupérer le timestamp d’un seul user et de le notifier en cas de mise à jour :

Pour une gestion multi-utilisateur, le code n’est guère plus compliqué.

Coté iOS, j’ai utilisé une librairie tiers pour initier la connexion Socket.io :
[socketIO connectToHost:url onPort:80];
et recevoir des « events » :
didReceiveEvent:(SocketIOPacket *)packet.
Une fois l’ « event » reçu, j’ai utilisé une NSNotification pour mettre à jour ma carte. Toujours rien de très compliqué.

Voici une comparaison de l’utilisation CPU (Faite par Xcode Instruments) d’un iPhone3GS entre la solution naïve décrite plus haut et la solution avec WebSocket. Le scenario consiste en 1 min d’activité avec une seule mise à jour des  entités, logiquement représenté par les pics dans les deux cas.

Avec WebSocket, on a une activité de l’application quasi inexistante lorsqu’aucune mise à jour n’est nécessaire. Une indication qui nous permet de dire que sur cette fonctionnalité, on a un gain de batterie évident.

Une dernière chose intéressante à noter sur Socket.io est qu’il offre une réel interface pour créer des applications de type comet, non seulement avec Websocket. Cette solution, Node.js/Socket.io peut donc facilement se « plugger » à beaucoup d’existants, même des applications web nécessitant le même genre de mécanismes.

Voila pour la petite anecdote.