Tutoriel : Création d’une application de conversation avec le service Azure Web PubSub

Dans le tutoriel Publication de messages et abonnement, vous apprenez les bases de la publication de messages et de l’abonnement aux messages avec Azure Web PubSub. Dans ce tutoriel, vous découvrez le système d’événements d’Azure Web PubSub et vous l’utilisez pour créer une application web complète avec une fonctionnalité de communication en temps réel.

Dans ce tutoriel, vous allez apprendre à :

  • Créer une instance de service Azure Web PubSub
  • Configurer les paramètres du gestionnaire d’événements pour Azure Web PubSub
  • Gérez les événements dans le serveur d’application et créez une application de conversation en temps réel

Si vous n’avez pas d’abonnement Azure, créez un compte gratuit Azure avant de commencer.

Prérequis

  • La version 2.22.0 (ou une version ultérieure) d’Azure CLI est requise pour cette configuration. Si vous utilisez Azure Cloud Shell, la version la plus récente est déjà installée.

Création d’une instance Azure Web PubSub

Créer un groupe de ressources

Un groupe de ressources est un conteneur logique dans lequel les ressources Azure sont déployées et gérées. Utilisez la commande az group create pour créer un groupe de ressources nommé myResourceGroup à l’emplacement eastus.

az group create --name myResourceGroup --location EastUS

Créer une instance Web PubSub

Exécutez la commande az extension add pour installer ou mettre à niveau l’extension webpubsub vers la version actuelle.

az extension add --upgrade --name webpubsub

Utilisez la commande az webpubsub create d’Azure CLI pour créer une instance Web PubSub dans le groupe de ressources que vous avez créé. La commande suivante crée une ressource Web PubSub GRATUITE sous le groupe de ressources myResourceGroup dans la zone EastUS :

Important

Chaque ressource Web PubSub doit avoir un nom unique. Remplacez <your-unique-keyvault-name> par le nom de votre Web PubSub dans les exemples suivants.

az webpubsub create --name "<your-unique-resource-name>" --resource-group "myResourceGroup" --location "EastUS" --sku Free_F1

La sortie de cette commande affiche les propriétés de la ressource que vous venez de créer. Notez les deux propriétés ci-dessous :

  • Nom de la ressource : nom que vous avez fourni au paramètre --name ci-dessus.
  • Nom d’hôte : dans l’exemple, le nom d’hôte est <your-unique-resource-name>.webpubsub.azure.com/.

À ce stade, votre compte Azure est le seul autorisé à effectuer des opérations sur cette nouvelle ressource.

Récupération de la chaîne de connexion pour une utilisation ultérieure

Important

Une chaîne de connexion contient les informations d’autorisation requises pour que votre application accède au service Azure Web PubSub. La clé d’accès à l’intérieur dans la chaîne de connexion est semblable à un mot de passe racine pour votre service. Veillez toujours à protéger vos clés d’accès dans les environnements de production. Utilisez Azure Key Vault pour gérer et effectuer la rotation de vos clés en toute sécurité. Évitez de distribuer des clés d’accès à d’autres utilisateurs, de les coder en dur ou de les enregistrer en texte brut dans un emplacement accessible à d’autres personnes. Effectuez une rotation de vos clés si vous pensez qu’elles ont pu être compromises.

Utilisez la commande Azure CLI az webpubsub key pour obtenir la valeur ConnectionString du service. Remplacez l’espace réservé <your-unique-resource-name> par le nom de votre instance Azure Web PubSub.

az webpubsub key show --resource-group myResourceGroup --name <your-unique-resource-name> --query primaryConnectionString --output tsv

Copiez la chaîne de connexion à utiliser plus tard.

Copiez la ConnectionString extraite et définissez-la dans la variable d’environnement WebPubSubConnectionString, que le didacticiel lit ultérieurement. Remplacez <connection-string> ci-dessous par la ConnectionString que vous avez récupérée.

export WebPubSubConnectionString="<connection-string>"
SET WebPubSubConnectionString=<connection-string>

Configuration du projet

Prérequis

Création de l'application

Dans Azure Web PubSub, il existe deux rôles : serveur et client. Ce concept est similaire aux rôles serveur et client d’une application web. Le serveur est chargé de gérer les clients, d’écouter et de répondre aux messages clients. Le client est chargé d’envoyer et de recevoir les messages de l’utilisateur(-trice) à partir du serveur et de les visualiser pour l’utilisateur(-trice) final(e).

Dans ce tutoriel, nous créons une application web de conversation en temps réel. Dans une application web réelle, la responsabilité du serveur implique également d’authentifier les clients et de traiter les pages web statiques pour l’interface utilisateur de l’application.

Nous utilisons ASP.NET Core 8 pour héberger les pages web et gérer les requêtes entrantes.

Commençons par créer une application web ASP.NET Core dans un dossier chatapp.

  1. Créez une application web.

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. Ajoutez app.UseStaticFiles() Program.cs pour prendre en charge l’hébergement de pages web statiques.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. Créez un fichier HTML et enregistrez-le sous wwwroot/index.html. Nous l’utilisons par la suite pour l’interface utilisateur de l’application de conversation.

    <html>
      <body>
        <h1>Azure Web PubSub Chat</h1>
      </body>
    </html>
    

Vous pouvez tester le serveur en exécutant dotnet run --urls http://localhost:8080 et en accédant à http://localhost:8080/index.html dans le navigateur.

Ajouter un point de terminaison de négociation

Dans le didacticiel Publier et s’abonner au message, l’abonné(e) consomme directement la chaîne de connexion. Dans une application réelle, il n’est pas sûr de partager la chaîne de connexion avec n’importe quel client, car la chaîne de connexion a le privilège d’effectuer n’importe quelle opération sur le service. À présent, supposons que votre serveur consomme la chaîne de connexion et expose un point de terminaison negotiate pour que le client obtienne l’URL complète avec le jeton d’accès. De cette façon, le serveur peut ajouter un intergiciel d’authentification avant le point de terminaison negotiate pour empêcher l’accès non autorisé.

Tout d’abord, installez les dépendances.

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

Nous allons maintenant ajouter un point de terminaison /negotiate pour que le client appelle pour générer le jeton.

using Azure.Core;
using Microsoft.Azure.WebPubSub.AspNetCore;
using Microsoft.Azure.WebPubSub.Common;
using Microsoft.Extensions.Primitives;

// Read connection string from environment
var connectionString = Environment.GetEnvironmentVariable("WebPubSubConnectionString");
if (connectionString == null)
{
    throw new ArgumentNullException(nameof(connectionString));
}

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddWebPubSub(o => o.ServiceEndpoint = new WebPubSubServiceEndpoint(connectionString))
    .AddWebPubSubServiceClient<Sample_ChatApp>();
var app = builder.Build();

app.UseStaticFiles();

// return the Client Access URL with negotiate endpoint
app.MapGet("/negotiate", (WebPubSubServiceClient<Sample_ChatApp> service, HttpContext context) =>
{
    var id = context.Request.Query["id"];
    if (StringValues.IsNullOrEmpty(id))
    {
        context.Response.StatusCode = 400;
        return null;
    }
    return new
    {
        url = service.GetClientAccessUri(userId: id).AbsoluteUri
    };
});
app.Run();

sealed class Sample_ChatApp : WebPubSubHub
{
}

AddWebPubSubServiceClient<THub>() est utilisé pour injecter le client de service WebPubSubServiceClient<THub>, que nous pouvons utiliser dans l’étape de négociation pour générer un jeton de connexion client et dans les méthodes du hub pour appeler des API REST de service lorsque des événements du hub sont déclenchés. Ce code de génération de jeton est similaire à celui que nous avons utilisé dans le tutoriel Publication de messages et abonnement, à ceci près qu’un argument de plus (userId) est transmis lors de la génération du jeton. L’ID d’utilisateur peut servir à identifier l’identité du client. Ainsi, lorsque vous recevez un message, vous en connaissez l’origine.

Le code lit la chaîne de connexion à partir de la variable d’environnement WebPubSubConnectionString que nous avons définie à l’étape précédente.

Réexécutez le serveur à l’aide de dotnet run --urls http://localhost:8080.

Pour tester cette API, accédez à http://localhost:8080/negotiate?id=user1. Vous obtenez ainsi l’URL complète d’Azure Web PubSub avec un jeton d’accès.

Gérez les événements

Dans Azure Web PubSub, lorsque certaines activités se produisent côté client (par exemple, un client se connecte, se connecte, se déconnecte, ou un client envoie des messages), le service envoie des notifications au serveur afin qu’il puisse réagir à ces événements.

Les événements sont remis au serveur sous la forme d’un Webhook. Celui-ci est traité et exposé par le serveur d’applications, et inscrit du côté du service Azure Web PubSub. Le service appelle le Webhook chaque fois qu’un événement se produit.

Azure Web PubSub suit CloudEvents pour décrire les données d’événement.

Ci-dessous, nous gérons les événements système connected lorsqu’un client est connecté et qu’il gère message événements utilisateur lorsqu’un client envoie des messages pour générer l’application de conversation.

Le Kit de développement logiciel (SDK) Web PubSub pour AspNetCore Microsoft.Azure.WebPubSub.AspNetCore que nous avons installé à l’étape précédente peut également aider à analyser et traiter les requêtes CloudEvents.

Tout d’abord, ajoutez des gestionnaires d’événements avant app.Run(). Spécifiez le chemin du point de terminaison des événements, par exemple /eventhandler.

app.MapWebPubSubHub<Sample_ChatApp>("/eventhandler/{*path}");
app.Run();

À présent, dans la classe Sample_ChatApp que nous avons créée à l’étape précédente, ajoutez un constructeur pour travailler avec WebPubSubServiceClient<Sample_ChatApp> que nous utilisons pour appeler le service Web PubSub. Et OnConnectedAsync() pour répondre lorsque connected événement est déclenché, OnMessageReceivedAsync() pour gérer les messages du client.

sealed class Sample_ChatApp : WebPubSubHub
{
    private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;

    public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public override async Task OnConnectedAsync(ConnectedEventRequest request)
    {
        Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
    }

    public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
    {
        await _serviceClient.SendToAllAsync(RequestContent.Create(
        new
        {
            from = request.ConnectionContext.UserId,
            message = request.Data.ToString()
        }),
        ContentType.ApplicationJson);

        return new UserEventResponse();
    }
}

Dans le code ci-dessus, nous utilisons le client de service pour diffuser un message de notification au format JSON à tous ceux qui sont liés à SendToAllAsync.

Mettez à jour la page web

Mettons maintenant à jour index.html pour ajouter la logique de connexion, d’envoi de message et d’affichage des messages reçus dans la page.

<html>
  <body>
    <h1>Azure Web PubSub Chat</h1>
    <input id="message" placeholder="Type to chat...">
    <div id="messages"></div>
    <script>
      (async function () {
        let id = prompt('Please input your user name');
        let res = await fetch(`/negotiate?id=${id}`);
        let data = await res.json();
        let ws = new WebSocket(data.url);
        ws.onopen = () => console.log('connected');

        let messages = document.querySelector('#messages');
        
        ws.onmessage = event => {
          let m = document.createElement('p');
          let data = JSON.parse(event.data);
          m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
          messages.appendChild(m);
        };

        let message = document.querySelector('#message');
        message.addEventListener('keypress', e => {
          if (e.charCode !== 13) return;
          ws.send(message.value);
          message.value = '';
        });
      })();
    </script>
  </body>

</html>

Vous pouvez voir dans le code ci-dessus que nous connectons utiliser l’API WebSocket native dans le navigateur et utiliser WebSocket.send() pour envoyer des messages et des WebSocket.onmessage pour écouter les messages reçus.

Vous pouvez également utiliser sdk client pour vous connecter au service, ce qui vous permet de vous reconnecter automatiquement, de gérer les erreurs et bien plus encore.

Il y a maintenant une étape à gauche pour que la conversation fonctionne. Nous allons configurer les événements auxquels nous nous soucions et où envoyer les événements dans le service Web PubSub.

Configuration du gestionnaire d’événements

Nous définissons le gestionnaire d’événements dans le service Web PubSub pour indiquer au service où envoyer les événements.

Lorsque le serveur web s’exécute localement, comment le service Web PubSub appelle l’hôte local s’il n’a aucun point de terminaison accessible par Internet ? Il existe généralement deux façons. Il s’agit d’exposer localhost au public à l’aide d’un outil de tunnel général, et l’autre consiste à utiliser tunnel awps-tunnel pour tunneliser le trafic du service Web PubSub via l’outil vers votre serveur local.

Dans cette section, nous utilisons Azure CLI pour définir les gestionnaires d’événements et utiliser le tunnel awps-tunnel pour acheminer le trafic vers localhost.

Configurer les paramètres d’un hub

Nous définissons le modèle d’URL pour utiliser un tunnel schéma afin que Web PubSub route les messages via la connexion tunnel de awps-tunnel. Les gestionnaires d’événements peuvent être définis à partir du portail ou de l’interface CLI, comme décrit dans cet article. Ici, nous le définissons dans l’interface CLI. Étant donné que nous écoutons les événements dans le chemin d’accès /eventhandler comme jeu d’étapes précédentes, nous définissons le modèle d’URL sur tunnel:///eventhandler.

Utilisez la commande Azure CLI az webpubsub hub create pour créer les paramètres du gestionnaire d’événements pour le hub Sample_ChatApp.

Important

Remplacez <your-unique-resource-name> par le nom de votre ressource Web PubSub créée précédemment.

az webpubsub hub create -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected"

Exécutez awps-tunnel localement

Téléchargez et installez awps-tunnel

L’outil fonctionne avec la version 16 ou supérieure de Node.js.

npm install -g @azure/web-pubsub-tunnel-tool

Utilisez la chaîne de connexion du service et exécutez

export WebPubSubConnectionString="<your connection string>"
awps-tunnel run --hub Sample_ChatApp --upstream http://localhost:8080

Exécutez le serveur web

Maintenant, tout est configuré. Exécutez le serveur web et jouer avec l’application de conversation en action.

Exécutez maintenant le serveur à l’aide de dotnet run --urls http://localhost:8080.

L’exemple de code complet de ce tutoriel est disponible ici.

Ouvrez http://localhost:8080/index.html. Vous pouvez entrer votre nom d’utilisateur et commencer à discuter.

Authentification différée avec le gestionnaire d’événements connect

Dans les sections précédentes, nous avons démontré comment utiliser le point de terminaison negotiate pour retourner l’URL du service Web PubSub et le jeton d’accès JWT afin que les clients se connectent au service Web PubSub. Dans certains cas, par exemple, pour les périphériques qui ont des ressources limitées, les clients peuvent préférer se connecter directement aux ressources Web PubSub. Dans ce cas, vous pouvez configurer le gestionnaire d’événements connect pour authentifier les clients de manière différée, attribuer des identifiants utilisateur aux clients, spécifier les groupes auxquels les clients se joignent une fois connectés, configurer les autorisations dont disposent les clients et le sous-protocole WebSocket comme réponse WebSocket au client, etc. Pour plus d’informations, reportez-vous aux spécifications du gestionnaire d’événements connect.

Nous allons maintenant utiliser le gestionnaire d’événements connect pour obtenir le même résultat que ce que la section négocier .

Mettre à jour les paramètres du hub

Tout d’abord, nous allons mettre à jour les paramètres du hub pour inclure aussi un gestionnaire d’événements connect. Nous devons également autoriser la connexion anonyme afin que les clients sans jeton d’accès JWT puissent se connecter au service.

Utilisez la commande Azure CLI az webpubsub hub update pour créer les paramètres du gestionnaire d’événements pour le hub Sample_ChatApp.

Important

Remplacez <your-unique-resource-name> par le nom de votre ressource Web PubSub créée précédemment.

az webpubsub hub update -n "<your-unique-resource-name>" -g "myResourceGroup" --hub-name "Sample_ChatApp" --allow-anonymous true --event-handler url-template="tunnel:///eventhandler" user-event-pattern="*" system-event="connected" system-event="connect"

Mettre à jour la logique en amont pour gérer l’événement de connexion

Nous allons maintenant mettre à jour la logique en amont pour gérer l’événement de connexion. Nous pourrions également supprimer le point de terminaison de négociation maintenant.

De manière similaire à ce que nous faisons dans le point de terminaison negotiate à des fins de démonstration, nous allons également lire l'identifiant à partir des paramètres de la requête. Dans l’événement connect, la requête d’origine du client est conservée dans le corps de la requête de l’événement connect.

À l’intérieur de la classe Sample_ChatApp, remplacez OnConnectAsync() pour gérer l’événement connect :

sealed class Sample_ChatApp : WebPubSubHub
{
    private readonly WebPubSubServiceClient<Sample_ChatApp> _serviceClient;

    public Sample_ChatApp(WebPubSubServiceClient<Sample_ChatApp> serviceClient)
    {
        _serviceClient = serviceClient;
    }

    public override ValueTask<ConnectEventResponse> OnConnectAsync(ConnectEventRequest request, CancellationToken cancellationToken)
    {
        if (request.Query.TryGetValue("id", out var id))
        {
            return new ValueTask<ConnectEventResponse>(request.CreateResponse(userId: id.FirstOrDefault(), null, null, null));
        }

        // The SDK catches this exception and returns 401 to the caller
        throw new UnauthorizedAccessException("Request missing id");
    }

    public override async Task OnConnectedAsync(ConnectedEventRequest request)
    {
        Console.WriteLine($"[SYSTEM] {request.ConnectionContext.UserId} joined.");
    }

    public override async ValueTask<UserEventResponse> OnMessageReceivedAsync(UserEventRequest request, CancellationToken cancellationToken)
    {
        await _serviceClient.SendToAllAsync(RequestContent.Create(
        new
        {
            from = request.ConnectionContext.UserId,
            message = request.Data.ToString()
        }),
        ContentType.ApplicationJson);

        return new UserEventResponse();
    }
}

Mettre à jour index.html pour vous connecter directement

Nous allons maintenant mettre à jour la page web pour se connecter directement au service Web PubSub. Une chose à mentionner est que maintenant, à des fins de démonstration, le point de terminaison du service Web PubSub est codé en dur dans le code client. Mettez à jour le nom d’hôte du service <the host name of your service> dans le code html ci-dessous avec la valeur de votre propre service. Il peut toujours être utile d’extraire la valeur du point de terminaison de service Web PubSub à partir de votre serveur. Cela vous offre plus de flexibilité et de contrôle sur l’emplacement où le client se connecte.

<html>
  <body>
    <h1>Azure Web PubSub Chat</h1>
    <input id="message" placeholder="Type to chat...">
    <div id="messages"></div>
    <script>
      (async function () {
        // sample host: mock.webpubsub.azure.com
        let hostname = "<the host name of your service>";
        let id = prompt('Please input your user name');
        let ws = new WebSocket(`wss://${hostname}/client/hubs/Sample_ChatApp?id=${id}`);
        ws.onopen = () => console.log('connected');

        let messages = document.querySelector('#messages');
        
        ws.onmessage = event => {
          let m = document.createElement('p');
          let data = JSON.parse(event.data);
          m.innerText = `[${data.type || ''}${data.from || ''}] ${data.message}`;
          messages.appendChild(m);
        };

        let message = document.querySelector('#message');
        message.addEventListener('keypress', e => {
          if (e.charCode !== 13) return;
          ws.send(message.value);
          message.value = '';
        });
      })();
    </script>
  </body>

</html>

Réexécuter le serveur

Maintenant réexécutez le serveur et visitez la page web en suivant les instructions précédentes. Si vous avez arrêté awps-tunnel, veuillez également réexécuter l’outil de tunnel.

Étapes suivantes

Ce tutoriel vous donne une idée de base du fonctionnement du système d’événements dans le service Azure Web PubSub.

Pour plus d’informations sur l’utilisation du service, consultez les autres tutoriels.