Développement d’applications web et mobiles qui consomment des données en temps réel

Voici le second d’une série d’articles consacrée au développement d’applications web et mobiles qui utilisent des données en temps réel. Cette série a débuté sur le blog de Julien Corioland, avec qui j’ai eu le plaisir d’animer une session sur ce sujet lors du récent AzureCamp (mardi 24 Juin 2014). J’en profite d’ailleurs pour signaler qu’une session Hands-On Online dédiée à la mise en pratique des sujets dévoilés lors de cet évènement et à la réponse aux questions techniques sur Azure aura lieu le 10 juillet après-midi.

Vous trouverez ces articles publiés alternativement sur nos blogs respectifs ;

· Développement d’applications web et mobiles qui consomment des données en temps réel 

· Utilisation de SignalR pour implémenter un service Backend Cloud dans Azure

· Consommer un hub SignalR depuis une application web cliente

· Consommer un hub SignalR depuis une application native (Windows / Windows Phone)

· Utilisation de Socket.IO et Node.js pour implémenter un service Backend Cloud

· Consommation d’un service socket.io depuis une application web cliente

· Implémenter une infrastructure “IoT” (AMQP, Service Bus,…)

Introduction

Comme l’a indiqué Julien, pour illustrer ces différents scénarios, nous avons choisi de développer une application orientée “IoT” (Internet Of Things), totalement hébergée sur la plateforme Cloud Microsoft Azure.

Les flux applicatifs correspondant à notre prototype iot sont les suivants :

image

Cet article presente l’utilisation de SignalR comme composante technique du Backend du prototype iot.

Les limites du protocole HTTP

HTTP est un protocole « half duplex » et sans état. Il est par conséquent peu adapté à l’affichage d’une information mise à jour directement sur le browser, sans requête de la part de l’utilisateur, comme peuvent le nécessiter un tableau de suivi de la valeur d’une action, une réponse de messagerie instantanée, une application métier répondant à des mises à jour multiples et simultanées, ou un évènement dans un jeu.

Evolution du modèle applicatif

Dans un premier temps, le développement de ce type d’usages a donc favorisé le contournement des limites du protocole par la proposition d’un nouveau modèle d’application (baptisé “Comet”) fondé sur différentes approches technologiques (XMLHttpRequest, Ajax long polling, pluggins ActiveX, Flash, Applet Java,…) pour aboutir finalement, avec les WebSockets, à une évolution du protocole (normalisé par l’IETF) et de l’API (normalisé par le W3C) qui puisse offrir une connexion persistante et bidirectionnelle entre le browser et le serveur.

Il suffit donc maintenant d’implémenter un serveur HTTP piloté par évènement et exploitant les WebSockets lorsque cela est possible (support côté serveur et côté browser). Pour ce faire, de multiples frameworks sont disponibles parmi lesquels SignalR et socket.IO. Une fois le développement du logiciel serveur achevé, il ne reste plus qu’à le déployer en se basant sur les multiples possibilités d’hébergement sur Azure : Azure Web Site, Cloud Service PaaS, Cloud Service IaaS, Azure Mobile Service…

Remarque ; dans le cas des Azure Web Sites, le support des WebSockets est fonction du Web Hosting Plan.

image

image

Le Framework SignalR

SignalR 2.x est un framework Open Source permettant la communication asynchrone d'un client (javaScript + jQuery) avec un serveur .Net en offrant des mécanismes de push d’événements serveur vers le navigateur client fondés sur l’utilisation d’une connexion WebSockets ou, à défaut, en long polling.

La capture HTTP suivante illustre la négociation du protocole entre le browser et le serveur. Dans notre contexte, c’est le transport WebSockets qui a été retenu. Le device client ne supportant pas nécessairement les WebSockets, et l’overhead étant spécifique à chaque protocole de transport, il faut dimensionner l’infrastructure en conséquence...

image

SignalR propose deux modèles de programmation : « PersistentConnection » et « Hubs ».

« PersistentConnection » propose une API de bas niveau intégrant la sémantique de connexion, reconnexion et de déconnection, ainsi que le broadcast vers tous les clients, groupes ou clients ciblés.

« Hubs » est fondé sur l’API « PersistentConnection » et propose un mode d’invocation de type RPC client-serveur et server-client ainsi que la génération automatique du proxy client (JavaScript).

image

C’est le modèle que nous avons retenu pour l’implémentation du serveur de relais des messages de notification signalR. Le code du proxy JavaScript généré automatiquement par le serveur signalR peut être étudié en capturant l’échange entre le browser et le serveur.

image

L’url cible (https://iotsignalrhub.azurewebsites.net/signalr) correspond à l’url de publication du serveur hébergeant le relais SignalR (dans notre contexte, il s’agit d’un Azure Web Site) complétée par la « route » assignée pour le pipeline de traitement des requêtes SignalR (définie, côté serveur, dans la méthode Configuration de la classe Startup, que nous étudierons un peu plus loin dans cet article).

Dans notre contexte, le code du Hub Serveur est le suivant :

[HubName("iotHub")]

public class IOTHub : Hub

    {

public IOTHub() : base()

        {

        }

public async Task SendNewMeasure(Measure measure)

        {

             Clients.All.newMeasureAvailable(measure);

        } 

    }

La méthode SendNewMeasure peut-être appelée depuis l’extérieur (par exemple par un WorkRole Azure) et donne lieu à activation de l’appel d’une fonction JavaScript qui sera associée côté script client sur la méthode this.iotHub.client.newMeasureAvailable.

Le nom du hub renseigné dans l’attribut “HubName” est celui qui sera repris dans le proxy JavaScript automatiquement généré et devra donc être utilisé (avec respect de la casse) dans le code JavaScript qui gèrera, côté client, le traitement des notifications signalR. L’ensemble de ces points seront détaillés dans le prochain article de Julien qui détaillera le code Javascript permettant de consommer les messages SignalR sur le frontEnd.

Sécurité SignalR

Le protocole SSL peut être utilisé pour sécuriser le transport des données entre un client et un serveur SignalR. 

Le serveur génère aléatoirement un identifiant de connexion unique lorsqu'une connexion est créée et le persiste pour la durée de la connexion. Chaque client qui se connecte à un concentrateur utilise cet identifiant que l’on peut récupérer dans la propriété Context.ConnectionId du Hub. Cet identifiant n’est plus valable dès lors que la connexion est coupée. Si l’application a besoin d’associer un utilisateur à cet identifiant de connexion et de persister ce lien, différentes solutions sont envisageables (comme le User ID Provider par exemple). SignalR utilise une signature numérique et un chiffrement pour protéger ce jeton de connexion.

L'identifiant de connexion identifie ainsi de manière unique chaque client connecté. Toutefois, il ne permet pas d’établir une authentification. SignalR ne proposant pas de mécanismes correspondant à ce besoin, le principe est d’intégrer le service SignalR dans la structure d'authentification existante qui elle même dépend des multiples possibilités d’hébergement de SignalR sous Windows (ASP.NET, OWIN, Self-Host,… ). Une fois l’utilisateur authentifié par l’un ou l’autre des mécanismes d'authentification supportés (mode basic, digest, jeton Kerberos, certificate X509V3,…), il devient alors possible de conditionner l’utilisation de telle ou telle méthode SignalR en fonction du nom d'utilisateur ou de son appartenance à un rôle.

Le mode « Hubs » permet de se fonder sur l’utilisation de l’attribut [Authorize] pour appliquer des règles d'autorisation. Le mode « PersistentConnection » ne peut utiliser cet attribut et nécessite la surcharge de la méthode « AuthorizeRequest ». Celle-ci est invoquée côté serveur par l'infrastructure SignalR avant chaque requête afin de vérifier que l'utilisateur est autorisé à effectuer l'action demandée.

SignalR supporte nativement la configuration de mécanismes CORS (« Cross-Origin Resource Sharing » : https://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-javascript-client#connequivalence) afin de permettre au browser d’accéder à des ressources (en l’occurrence la notification issue du serveur SignalR) n’appartenant pas au domaine d’origine (en l’occurrence la page Web affichée dans le browser). Il n’est donc pas nécessaire d’héberger la génération de push d’évènements directement sur le serveur Web. Cela permet de découpler les deux types de fonctions et offre plus de granularité dans l’architecture de la solution cible (scalabilité, périmètre de sécurité, type d’hébergement,…).

La configuration CORS est définie, côté serveur, dans la méthode « Configuration » de la classe Startup :

public void Configuration(IAppBuilder app)

   {

    app.Map("/signalr", map =>

            {

                map.UseCors(CorsOptions.AllowAll);

                var hubConfiguration = new HubConfiguration

                {

                };

                hubConfiguration.EnableDetailedErrors = true;

                map.RunSignalR(hubConfiguration);

            });

            GlobalHost.TraceManager.Switch.Level = SourceLevels.Information;

        }

    }

L’option activée dans ce code (map.UseCors(CorsOptions.AllowAll) donne potentiellement la capacité à un site malveillant de relayer une notification en lieu et place d’un utilisateur préalablement authentifié (il s’agit alors d’une requête de type CSFR : Cross-Site Request Forgery). Il est possible de modifier cette option en appliquant des stratégies de configuration plus restrictives.

En outre, le risque d'exécution de commandes malveillantes peut être atténué en validant l'identité de l'expéditeur. Comme vu précédemment, pour chaque appel, le client et le serveur SignalR passent un jeton de connexion qui contient l'identifiant de connexion et le nom d'utilisateur pour les utilisateurs authentifiés. Il suffit donc de rejeter toute requête émanant d'un identifiant de connexion ne correspondant pas au nom de l'utilisateur.

Dans notre contexte, ne s’agissant pas d’informations sensibles, le Hub reste accessible à des utilisateurs anonymes.

Mécanismes natifs de scaleout

Quoique SignalR permette de gérer un nombre très élevé de connexions sur un seul ordinateur, il peut devenir nécessaire de déployer les hubs sur plusieurs serveurs pour pallier le risque de « single point of failure » ou augmenter ce nombre de connexions. Il existe plusieurs solutions pour offrir un “backplane” afin de distribuer les messages sur un groupe de serveurs. Elles sont respectivement fondées sur l’usage de SQL Server, Redis, ou Service Bus.

Dans notre contexte, nous avons fait le choix d’utiliser Azure Service Bus, service middleware Azure fondé sur la mise en œuvre d’un pattern de « publication/souscription ». Il permet de séparer le traffic de messagerie entre de multiples rubriques (les Service Bus Topics) afin de maximiser le débit.

image

Chaque message à destination du Hub SignalR sera envoyé sur une rubrique correspondant à l’application. Il pourra ainsi être relayé sur l’ensemble des applications Azure hébergeant le hub qui se seront abonnées à cette rubrique.

Remarque : la mise en œuvre de ce mécanisme de scalabilité peut induire une perte de performance : chaque message est routé vers chaque serveur, donc dans le cas où le trafic augmente, la limite est liée à la capacité du serveur à dépiler les messages du backplane Service Bus.

La premièré étape de la mise en place du BackPlane consiste à créer un NameSpace Service Bus depuis le portail

image

L’utilisation d’Azure Service Bus en tant que BackPlane de SignalR se fonde sur les points d’extensibilité offerts par SignalR et nécessite une légère modification de l'application qui héberge le Hub de notification dans la classe Startup.cs.

            string serviceBusConnectionString = "";

            if (RoleEnvironment.IsAvailable)

            {

                serviceBusConnectionString = RoleEnvironment.GetConfigurationSettingValue("ServiceBusConnectionString");

                GlobalHost.DependencyResolver.UseServiceBus(serviceBusConnectionString, "iotHub");

            }

            else

            {

                serviceBusConnectionString = ConfigurationManager.ConnectionStrings["ServiceBusConnectionString"].ConnectionString;

            }

Tests de charge

La technologie cible (SignalR) ne se teste pas en charge avec nos outils habituels (Visual Studio Load Tests avec ou sans Azure). Il faut donc s’appuyer sur des injecteurs codés spécifiquement pour cet usage.

Signalons toutefois que dans le code de source de SignalR disponible sur Github sont fournis différents projets qui permettent de tester en charge une solution de ce type.
Elle se fonde sur plusieurs projets (notamment “crank.exe” pour la génération de charge SignalR et “Load test Harness” pour l’hébergement du serveur et l’affichage des résultats) et présente l’avantage de pouvoir exploiter des compteurs de performance dédiés à SignalR (installable avec la commande “Microsoft.AspNet.SignalR.Utils, signalr.exe –ipc”).
image

Le Blog de Martin Richard explique dans le detail comment capturer ces compteurs dans le cas d’un Cloud Service PaaS avec Azure Diagnostics.

Une façon de récupérer des informations est alors d’utiliser l’excellent outil Azure Management Studio de la société Cérebrata. Il faut attendre au moins 5mn pour voir apparaître les compteurs customs…

image

Synthèse

SignalR 2.x est un framework Open Source permettant la communication asynchrone entre un client et un serveur avec ou sans WebSockets. L’implémentation d’une infrastructure SignalR est grandement facilitée par l’utilisation du modèle de programmation “hubs” (et notamment par la fonction de génération automatique du proxy client JavaScript), les mécanismes natifs de scaleout, la sécurité ou les compléments proposés sur github pour l’exécution de tests de charge.