Zelfstudie: Een chat-app maken met de Azure Web PubSub-service

In de zelfstudie Berichten publiceren en abonneren leert u de basisprincipes van het publiceren en abonneren van berichten met Azure Web PubSub. In deze zelfstudie leert u het gebeurtenissysteem van Azure Web PubSub en gebruikt u dit om een volledige webtoepassing te bouwen met realtime communicatiefunctionaliteit.

In deze zelfstudie leert u het volgende:

  • Een Web PubSub-service-exemplaar maken
  • Instellingen voor gebeurtenis-handler configureren voor Azure Web PubSub
  • Gebeurtenissen in de app-server afhandelen en een realtime chat-app bouwen

Als u geen Azure-account hebt, maak dan een gratis account aan voordat u begint.

Vereisten

  • Voor deze installatie is versie 2.22.0 of hoger van de Azure CLI vereist. Als u Azure Cloud Shell gebruikt, is de nieuwste versie al geïnstalleerd.

Een Azure Web PubSub-exemplaar maken

Een brongroep maken

Een resourcegroep is een logische container waarin Azure-resources worden geïmplementeerd en beheerd. Gebruik de opdracht az group create om een resourcegroep te maken met de naam myResourceGroup op de eastus locatie.

az group create --name myResourceGroup --location EastUS

Een Web PubSub-exemplaar maken

Voer az extension add uit om de webpubsub-extensie te installeren of bij te werken naar de huidige versie.

az extension add --upgrade --name webpubsub

Gebruik de Azure CLI-opdracht az webpubsub create om een Web PubSub te maken in de resourcegroep die u hebt gemaakt. Met de volgende opdracht maakt u een gratis Web PubSub-resource in de resourcegroep myResourceGroup in EastUS:

Belangrijk

Elke Web PubSub-resource moet een unieke naam hebben. Vervang <uw unieke resourcenaam> door de naam van uw Web PubSub in de volgende voorbeelden.

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

In de uitvoer van deze opdracht ziet u eigenschappen van de zojuist gemaakte resource. Let op de onderstaande twee eigenschappen:

  • Resourcenaam: de naam die u hebt opgegeven voor de --name bovenstaande parameter.
  • hostName: In het voorbeeld is <your-unique-resource-name>.webpubsub.azure.com/de hostnaam .

Op dit moment is uw Azure-account de enige die gemachtigd is om bewerkingen uit te voeren op deze nieuwe resource.

ConnectionString ophalen voor toekomstig gebruik

Belangrijk

Ruwe verbindingsstrings worden in dit artikel alleen weergegeven voor demonstratiedoeleinden.

Een verbindingsreeks bevat de autorisatie-informatie die je applicatie nodig heeft om toegang te krijgen tot de Azure Web PubSub-service. De toegangssleutel in de verbindingsreeks fungeert als een rootwachtwoord voor je dienst. Beveilig uw toegangssleutels altijd in productieomgevingen. Gebruik Azure Key Vault om uw sleutels veilig te beheren en te roteren en uw verbinding te beveiligen.WebPubSubServiceClient

Vermijd het distribueren van toegangssleutels naar andere gebruikers, het coderen ervan of het opslaan van ze ergens in tekst zonder opmaak die toegankelijk is voor anderen. Draai uw sleutels als u denkt dat ze mogelijk zijn aangetast.

Gebruik de azure CLI az webpubsub key command om de ConnectionString van de service op te halen. Vervang de <your-unique-resource-name> plaatsaanduiding door de naam van uw Azure Web PubSub-instantie.

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

Kopieer de verbindingsreeks om later te gebruiken.

Kopieer de opgehaalde ConnectionString en stel deze in als omgevingsvariabele WebPubSubConnectionString, die later in de tutorial wordt gebruikt. Vervang <connection-string> hieronder door de ConnectionString die u hebt opgehaald.

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

Het project instellen

Vereisten

De toepassing maken

In Azure Web PubSub zijn er twee functies, server en client. Dit concept is vergelijkbaar met de server- en clientrollen in een webtoepassing. Server is verantwoordelijk voor het beheren van de clients, luisteren en reageren op clientberichten. De client is verantwoordelijk voor het verzenden en ontvangen van berichten van de gebruiker van de server en visualiseert deze voor de eindgebruiker.

In deze zelfstudie bouwen we een realtime-webtoepassing voor chats. In een echte webtoepassing omvat de verantwoordelijkheid van de server ook het verifiëren van clients en het leveren van statische webpagina's voor de gebruikersinterface van de toepassing.

We gebruiken ASP.NET Core 8 om de webpagina's te hosten en binnenkomende aanvragen te verwerken.

Laten we eerst een ASP.NET Core-web-app maken in een chatapp map.

  1. Maak een nieuwe web-app.

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. Voeg Program.cs toe app.UseStaticFiles() ter ondersteuning van het hosten van statische webpagina's.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. Maak een HTML-bestand en sla het op als wwwroot/index.html, we gebruiken het voor de gebruikersinterface van de chat-app later.

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

U kunt de server testen door dotnet run --urls http://localhost:8080 uit te voeren en http://localhost:8080/index.html in de browser te openen.

Onderhandelingseindpunt toevoegen

In de zelfstudie Berichten publiceren en erop abonneren gebruikt de abonnee de verbindingsreeks rechtstreeks. In een echte toepassing is het niet veilig om de verbindingsreeks te delen met een client, omdat verbindingsreeks hoge bevoegdheden heeft om bewerkingen voor de service uit te voeren. Laten we nu uw server de verbindingsreeks laten gebruiken en een negotiate eindpunt beschikbaar maken voor de client om de volledige URL met toegangstoken op te halen. Op deze manier kan de server verificatie-middleware toevoegen vóór het negotiate eindpunt om onbevoegde toegang te voorkomen.

Installeer eerst de afhankelijkheden.

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

Laten we nu een /negotiate-eindpunt toevoegen dat de clienttoepassing kan aanroepen om het token te laten genereren.

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>() wordt gebruikt om de serviceclient WebPubSubServiceClient<THub>te injecteren, waarmee we in de onderhandelingsstap een clientverbindingstoken en in hubmethoden kunnen genereren om service REST API's aan te roepen wanneer hubgebeurtenissen worden geactiveerd. Deze code voor het genereren van tokens is vergelijkbaar met de code die we hebben gebruikt in de zelfstudie voor het publiceren en abonneren van berichten, behalve dat we nog één argument (userId) doorgeven bij het genereren van het token. Gebruikers-id kan worden gebruikt om de identiteit van de client te identificeren, dus wanneer u een bericht ontvangt waarvan u weet waar het bericht vandaan komt.

De code leest verbindingsreeks uit de omgevingsvariabele WebPubSubConnectionString die we in de vorige stap hebben ingesteld.

Voer de server opnieuw uit met behulp van dotnet run --urls http://localhost:8080.

U kunt deze API testen door toegang te krijgen http://localhost:8080/negotiate?id=user1 en u krijgt de volledige URL van de Azure Web PubSub met een toegangstoken.

Gebeurtenissen afhandelen

Wanneer er in Azure Web PubSub bepaalde activiteiten plaatsvinden aan de clientzijde (bijvoorbeeld wanneer een client verbinding maakt, verbonden, verbroken of een client berichten verzendt), verzendt de service meldingen naar de server, zodat deze op deze gebeurtenissen kan reageren.

Gebeurtenissen worden geleverd aan de server in de vorm van Webhook. De webhook wordt gehost en beschikbaar gesteld door de applicatieserver en geregistreerd bij de Azure Web PubSub-service. De service roept de webhooks aan wanneer er een gebeurtenis plaatsvindt.

Azure Web PubSub volgt CloudEvents om de gebeurtenisgegevens te beschrijven.

Hieronder verwerken connected we systeem gebeurtenissen wanneer een client is verbonden en verwerken message gebruikers gebeurtenissen wanneer een client berichten verzendt om de chat-app te bouwen.

De Web PubSub SDK voor AspNetCore Microsoft.Azure.WebPubSub.AspNetCore die we in de vorige stap hebben geïnstalleerd, kan ook helpen bij het parseren en verwerken van de CloudEvents-aanvragen.

Voeg eerst gebeurtenisafhandelaars toe vóór app.Run(). Geef het eindpuntpad voor de gebeurtenissen op, laten we zeggen /eventhandler.

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

Voeg nu in de klasse Sample_ChatApp die we in de vorige stap hebben gemaakt, een constructor toe om mee WebPubSubServiceClient<Sample_ChatApp> te werken dat we gebruiken om de Web PubSub-service aan te roepen. En OnConnectedAsync() om te reageren wanneer connected een gebeurtenis wordt geactiveerd, OnMessageReceivedAsync() om berichten van de client te verwerken.

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();
    }
}

In de bovenstaande code gebruiken we de serviceclient om een meldingsbericht in JSON-indeling te verzenden naar iedereen die zich heeft aangesloten met SendToAllAsync.

De webpagina bijwerken

Nu gaan we bijwerken index.html om de logica toe te voegen om verbinding te maken, bericht te verzenden en ontvangen berichten weer te geven op de pagina.

<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>

U kunt in de bovenstaande code zien dat we verbinding maken met behulp van de systeemeigen WebSocket-API in de browser en gebruiken WebSocket.send() om berichten te verzenden en WebSocket.onmessage te luisteren naar ontvangen berichten.

U kunt ook client-SDK's gebruiken om verbinding te maken met de service, zodat u automatisch opnieuw verbinding kunt maken, foutafhandeling en meer.

Er is nu nog één stap over om de chat te laten werken. Laten we configureren welke gebeurtenissen voor ons van belang zijn en waar de gebeurtenissen naartoe moeten worden verzonden in de Web PubSub-service.

De eventhandler instellen

We stellen de gebeurtenis-handler in de Web PubSub-service in om de service te laten weten waar de gebeurtenissen naartoe moeten worden verzonden.

Wanneer de webserver lokaal wordt uitgevoerd, hoe de Web PubSub-service de localhost aanroept als deze geen eindpunt heeft dat toegankelijk is voor internet? Er zijn meestal twee manieren. De ene is om localhost openbaar te maken met behulp van een algemeen tunnelhulpprogramma en de andere is om awps-tunnel te gebruiken om het verkeer van de Web PubSub-service via het hulpprogramma naar uw lokale server te tunnelen.

In deze sectie gebruiken we Azure CLI om de gebeurtenis-handlers in te stellen en awps-tunnel te gebruiken om verkeer naar localhost te routeren.

Hubinstellingen configureren

We stellen de URL-sjabloon zo in dat deze het schema tunnel gebruikt, zodat Web PubSub berichten via de tunnelverbinding van awps-tunnel routeert. Gebeurtenis-handlers kunnen worden ingesteld vanuit de portal of de CLI, zoals beschreven in dit artikel. Hier stellen we deze in via CLI. Omdat we gebeurtenissen opvangen op het pad /eventhandler, zoals ingesteld in de vorige stap, stellen we de URL-sjabloon in op tunnel:///eventhandler.

Gebruik de azure CLI az webpubsub hub create command om de instellingen voor de gebeurtenis-handler voor de Sample_ChatApp hub te maken.

Belangrijk

Vervang <uw unieke resourcenaam door de naam van uw Web PubSub-resource> die u in de vorige stappen hebt gemaakt.

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"

Awps-tunnel lokaal uitvoeren

Awps-tunnel downloaden en installeren

Het hulpprogramma wordt uitgevoerd op Node.js versie 16 of hoger.

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

Gebruik de verbindingsreeks van de service en voer uit

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

De webserver uitvoeren

Nu is alles ingesteld. Laten we de webserver starten en de chat-app in werking zien.

Voer nu de server uit met behulp van dotnet run --urls http://localhost:8080.

Het volledige codevoorbeeld van deze tutorial vindt u hier.

Open http://localhost:8080/index.html. U kunt uw gebruikersnaam invoeren en chatten.

Uitgestelde authenticatie met connect gebeurtenishandler

In de vorige secties laten we zien hoe u een onderhandelingseindpunt gebruikt om de URL van de Web PubSub-service en het JWT-toegangstoken te retourneren voor de clients om verbinding te maken met de Web PubSub-service. In sommige gevallen, bijvoorbeeld edge-apparaten met beperkte resources, kunnen clients de voorkeur geven aan directe verbinding met Web PubSub-resources. In dergelijke gevallen kunt u de connect-gebeurtenishandler configureren om uitgestelde authenticatie voor de clients uit te voeren, een gebruikers-ID aan de clients toe te wijzen, de groepen op te geven waar de clients zich bij aansluiten zodra ze verbinding maken, de machtigingen te configureren die de clients hebben, en het WebSocket-subprotocol op te geven in het WebSocket-antwoord aan de client, enzovoort. Raadpleeg de specificatie van de gebeurtenishandler voor verbinding maken voor meer informatie.

We gaan nu de gebeurtenis-handler gebruiken connect om hetzelfde te bereiken als wat de sectie onderhandelen doet.

Hubinstellingen bijwerken

Eerst gaan we hubinstellingen bijwerken om ook gebeurtenis-handler op te nemen connect . We moeten ook anonieme verbinding toestaan, zodat clients zonder JWT-toegangstoken verbinding kunnen maken met de service.

Gebruik de Azure CLI-opdracht az webpubsub hub update om de instellingen voor gebeurtenisafhandeling voor de hub Sample_ChatApp te maken.

Belangrijk

Vervang <uw unieke resourcenaam door de naam van uw Web PubSub-resource> die u in de vorige stappen hebt gemaakt.

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"

Upstream-logica bijwerken om verbindingsgebeurtenissen te verwerken

Nu gaan we upstreamlogica bijwerken om verbindingsevenementen af te handelen. We kunnen nu ook het onderhandelingseindpunt verwijderen.

Net zoals we voor demonstratiedoeleinden in het onderhandelingsendpoint doen, lezen we ook de ID uit de queryparameters. In een verbindingsgebeurtenis blijft de oorspronkelijke clientquery behouden in de hoofdtekst van de aanvraag voor de verbindingsgebeurtenis.

Overschrijf binnen de klasse Sample_ChatAppOnConnectAsync() om de connect-gebeurtenis af te handelen:

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();
    }
}

Index.html bijwerken om rechtstreeks verbinding te maken

Nu gaan we de webpagina bijwerken om rechtstreeks verbinding te maken met de Web PubSub-service. Belangrijk om te vermelden is dat het eindpunt van de Web PubSub-service nu voor demonstratiedoeleinden hardcoded in de clientcode is opgenomen. Werk in de onderstaande HTML de hostnaam van de service <the host name of your service> bij met de waarde van uw eigen service. Het kan nog steeds nuttig zijn om de waarde van het Web PubSub-service-eindpunt op te halen van uw server, waardoor u meer flexibiliteit en controle hebt over de locatie waar de client verbinding mee maakt.

<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>

De server opnieuw uitvoeren

Voer de server nu opnieuw uit en ga naar de webpagina volgens de instructies daarvoor. Als u bent gestopt awps-tunnel, voert u ook het tunnelhulpprogramma opnieuw uit.

Volgende stappen

Deze zelfstudie biedt een basisidee van de werking van het gebeurtenissysteem in de Azure Web PubSub-service.

Bekijk andere zelfstudies voor meer informatie over het gebruik van de service.