Samouczek: tworzenie aplikacji do czatu za pomocą usługi Azure Web PubSub

W samouczku Publikowanie i subskrybowanie komunikatów poznasz podstawy publikowania i subskrybowania komunikatów za pomocą usługi Azure Web PubSub. W tym samouczku poznasz system zdarzeń usługi Azure Web PubSub i użyjesz go do utworzenia kompletnej aplikacji internetowej z funkcją komunikacji w czasie rzeczywistym.

Z tego samouczka dowiesz się, jak wykonywać następujące czynności:

  • Utwórz wystąpienie usługi Web PubSub
  • Konfigurowanie ustawień programu obsługi zdarzeń dla usługi Azure Web PubSub
  • Obsługa zdarzeń na serwerze aplikacji i tworzenie aplikacji do czatu w czasie rzeczywistym

Jeśli nie masz jeszcze konta platformy Azure, przed rozpoczęciem utwórz bezpłatne konto.

Wymagania wstępne

  • Ta konfiguracja wymaga wersji 2.22.0 lub nowszej interfejsu wiersza polecenia platformy Azure. W przypadku korzystania z usługi Azure Cloud Shell najnowsza wersja jest już zainstalowana.

Tworzenie wystąpienia usługi Azure Web PubSub

Tworzenie grupy zasobów

Grupa zasobów to logiczny kontener przeznaczony do wdrażania zasobów platformy Azure i zarządzania nimi. Użyj polecenia az group create, aby utworzyć grupę zasobów o nazwie myResourceGroup w eastus lokalizacji.

az group create --name myResourceGroup --location EastUS

Utwórz wystąpienie usługi Web PubSub

Uruchom az extension add, aby zainstalować rozszerzenie webpubsub lub uaktualnić je do bieżącej wersji.

az extension add --upgrade --name webpubsub

Użyj polecenia interfejsu wiersza polecenia platformy Azure az webpubsub create, aby utworzyć usługę Web PubSub w utworzonej wcześniej grupie zasobów. Poniższe polecenie tworzy zasób Web PubSub w warstwie Free w grupie zasobów myResourceGroup w regionie EastUS:

Ważne

Każdy zasób Web PubSub musi mieć unikatową nazwę. Zastąp <your-unique-resource-name> nazwą Twojego zasobu Web PubSub w poniższych przykładach.

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

Dane wyjściowe tego polecenia pokazują właściwości nowo utworzonego zasobu. Zanotuj dwie poniższe właściwości:

  • Nazwa zasobu: nazwa podana w parametrze --name powyżej.
  • hostName: w przykładzie nazwa hosta to <your-unique-resource-name>.webpubsub.azure.com/.

W tym momencie Twoje konto platformy Azure jest jedynym autoryzowanym do wykonywania jakichkolwiek operacji na tym nowym zasobie.

Pobierz ConnectionString do późniejszego użycia

Ważne

Nieprzetworzone parametry połączenia są wyświetlane tylko w tym artykule w celach demonstracyjnych.

parametry połączenia zawiera informacje autoryzacyjne potrzebne Twojej aplikacji, aby uzyskać dostęp do usługi Azure Web PubSub. Klucz dostępu w parametry połączenia działa jak hasło root dla Twojej usługi. W środowiskach produkcyjnych zawsze chroń klucze dostępu. Użyj usługi Azure Key Vault, aby bezpiecznie zarządzać kluczami i obracać je oraz zabezpieczać połączenie za pomocą usługi WebPubSubServiceClient.

Unikaj dystrybuowania kluczy dostępu do innych użytkowników, kodowania ich lub zapisywania ich w dowolnym miejscu w postaci zwykłego tekstu, który jest dostępny dla innych użytkowników. Obracanie kluczy, jeśli uważasz, że mogły one zostać naruszone.

Użyj polecenia interfejsu wiersza polecenia platformy Azure az webpubsub key, aby pobrać parametr ConnectionString usługi. Zastąp symbol zastępczy <your-unique-resource-name> nazwą swojej instancji usługi Azure Web PubSub.

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

Skopiuj parametry połączenia do późniejszego użycia.

Skopiuj pobrane parametry połączenia i ustaw je w zmiennej środowiskowej WebPubSubConnectionString, którą samouczek później odczyta. Zastąp znajdujący się poniżej element <connection-string> pobranym elementem ConnectionString.

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

Konfigurowanie projektu

Wymagania wstępne

Tworzenie aplikacji

W usłudze Azure Web PubSub istnieją dwie role, serwer i klient. Ta koncepcja jest podobna do ról serwera i klienta w aplikacji internetowej. Serwer jest odpowiedzialny za zarządzanie klientami, nasłuchiwanie i odpowiadanie na komunikaty klienta. Klient jest odpowiedzialny za wysyłanie i odbieranie komunikatów użytkownika z serwera i wizualizowanie ich dla użytkownika końcowego.

W tym samouczku utworzymy aplikację internetową do czatu w czasie rzeczywistym. W prawdziwej aplikacji internetowej odpowiedzialność serwera obejmuje również uwierzytelnianie klientów i obsługę statycznych stron internetowych dla interfejsu użytkownika aplikacji.

Używamy ASP.NET Core 8 do hostowania stron internetowych i obsługi żądań przychodzących.

Najpierw utwórzmy aplikację internetową ASP.NET Core w folderze chatapp .

  1. Utwórz nową aplikację internetową.

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. Dodaj app.UseStaticFiles() Program.cs do obsługi hostowania statycznych stron internetowych.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. Utwórz plik HTML i zapisz go jako wwwroot/index.html, użyjemy go do interfejsu użytkownika aplikacji czatu później.

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

Serwer można przetestować, uruchamiając polecenie dotnet run --urls http://localhost:8080, a następnie otwierając http://localhost:8080/index.html w przeglądarce.

Dodawanie negocjowanego punktu końcowego

W samouczku Publikowanie i subskrybowanie komunikatów subskrybent bezpośrednio używa ciągu połączenia. W rzeczywistej aplikacji udostępnianie parametrów połączenia jakiemukolwiek klientowi nie jest bezpieczne, ponieważ parametr połączenia zapewnia szerokie uprawnienia do wykonywania dowolnych operacji w usłudze. Teraz skonfiguruj serwer tak, aby korzystał z parametrów połączenia i udostępniał punkt końcowy negotiate, dzięki któremu klient może pobrać pełny adres URL z tokenem dostępu. W taki sposób serwer może dodać oprogramowanie pośredniczące uwierzytelniania przed negotiate punktem końcowym, aby zapobiec nieautoryzowanemu dostępowi.

Najpierw zainstaluj zależności.

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

Teraz dodajmy punkt końcowy /negotiate, który klient wywoła, aby wygenerować token.

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>() Służy do wstrzykiwania klienta WebPubSubServiceClient<THub>usługi , za pomocą którego możemy użyć w kroku negocjacji do generowania tokenu połączenia klienta i w metodach centrum do wywoływania interfejsów API REST usługi po wyzwoleniu zdarzeń centrum. Ten kod generowania tokenu jest podobny do kodu użytego w samouczku dotyczącym publikowania i subskrybowania komunikatów, z wyjątkiem przekazywania jeszcze jednego argumentu (userId) podczas generowania tokenu. Identyfikator użytkownika może służyć do identyfikowania tożsamości klienta, więc po otrzymaniu komunikatu wiadomo, skąd pochodzi komunikat.

Kod odczytuje parametry połączenia ze zmiennej środowiskowej WebPubSubConnectionString ustawionej w poprzednim kroku.

Uruchom ponownie serwer przy użyciu polecenia dotnet run --urls http://localhost:8080.

Możesz przetestować ten interfejs API, uzyskując dostęp do http://localhost:8080/negotiate?id=user1, który zwraca pełny adres URL usługi Azure Web PubSub wraz z tokenem dostępu.

Obsługa zdarzeń

W usłudze Azure Web PubSub, gdy po stronie klienta występują pewne działania (na przykład klient łączy się, łączy, rozłącza lub wysyła komunikaty), usługa wysyła powiadomienia do serwera, aby mogła reagować na te zdarzenia.

Zdarzenia są dostarczane do serwera w formie webhooka. Webhook jest obsługiwany i udostępniany przez serwer aplikacji oraz rejestrowany w usłudze Azure Web PubSub. Usługa wywołuje webhooki za każdym razem, gdy wystąpi zdarzenie.

Usługa Azure Web PubSub używa specyfikacji CloudEvents do opisywania danych zdarzeń.

Poniżej obsługujemy connected zdarzenia systemowe, gdy klient jest połączony i obsługuje message zdarzenia użytkownika, gdy klient wysyła komunikaty w celu skompilowania aplikacji do czatu.

Zestaw SDK Web PubSub dla AspNetCore, który zainstalowaliśmy w poprzednim kroku, może również pomóc w parsowaniu i przetwarzaniu żądań CloudEvents.

Najpierw dodaj procedury obsługi zdarzeń przed app.Run(). Określ ścieżkę punktu końcowego dla zdarzeń, powiedzmy /eventhandler.

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

Teraz, wewnątrz klasy Sample_ChatApp, utworzonej w poprzednim kroku, dodaj konstruktor współpracujący z WebPubSubServiceClient<Sample_ChatApp>, którego używamy do wywoływania usługi Web PubSub. Oraz OnConnectedAsync(), aby reagować po wywołaniu zdarzenia connected, OnMessageReceivedAsync() aby obsługiwać komunikaty od klienta.

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

W powyższym kodzie używamy klienta usługi do rozgłoszenia komunikatu powiadomienia w formacie JSON do wszystkich połączonych za pomocą SendToAllAsync.

Aktualizowanie strony internetowej

Teraz zaktualizujmy index.html, aby dodać logikę nawiązywania połączenia, wysyłania wiadomości i wyświetlania na stronie odebranych wiadomości.

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

W powyższym kodzie widać, że używamy natywnego interfejsu WebSocket API w przeglądarce oraz WebSocket.send() do wysyłania wiadomości i WebSocket.onmessage do nasłuchiwania przychodzących wiadomości.

Możesz również użyć zestawów SDK klienta, aby nawiązać połączenie z usługą, co umożliwia automatyczne ponowne nawiązywanie połączenia, obsługę błędów i nie tylko.

Teraz pozostało jeszcze jeden krok, aby czat działał. Skonfigurujmy, które zdarzenia nas interesują i dokąd mają być wysyłane w usłudze Web PubSub.

Konfigurowanie programu obsługi zdarzeń

Ustawiamy procedurę obsługi zdarzeń w usłudze Web PubSub, aby wskazać usłudze, dokąd ma wysyłać zdarzenia.

Kiedy serwer internetowy działa lokalnie, jak usługa Web PubSub wywołuje hosta lokalnego, jeśli nie ma punktu końcowego dostępnego dla Internetu? Zwykle istnieją dwa sposoby. Jednym sposobem jest udostępnienie localhosta publicznie przy użyciu uniwersalnego narzędzia do tunelowania, a drugim — użycie awps-tunnel do tunelowania ruchu z usługi Web PubSub przez to narzędzie do lokalnego serwera.

W tej sekcji użyjemy interfejsu wiersza polecenia platformy Azure, aby ustawić programy obsługi zdarzeń i użyć narzędzia awps-tunnel do kierowania ruchu do hosta lokalnego.

Konfigurowanie ustawień centrum

Ustawiliśmy szablon adresu URL tak, aby używał schematu tunnel, dzięki czemu usługa Web PubSub kierowała komunikaty przez połączenie tunelowe elementu awps-tunnel. Procedury obsługi zdarzeń można ustawić z poziomu portalu lub interfejsu wiersza polecenia zgodnie z opisem w tym artykule. W tym miejscu ustawiliśmy je za pomocą interfejsu wiersza polecenia. Ponieważ nasłuchujemy zdarzeń w ścieżce /eventhandler, jak ustawiono w poprzednim kroku, ustawiamy szablon adresu URL na tunnel:///eventhandler.

Użyj polecenia interfejsu wiersza polecenia platformy Azure (Azure CLI) az webpubsub hub create, aby utworzyć ustawienia obsługi zdarzeń dla centrum Sample_ChatApp.

Ważne

Zastąp <your-unique-resource-name> nazwą zasobu Web PubSub utworzonego w poprzednich krokach.

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"

Uruchamianie aplikacji awps-tunnel lokalnie

Pobieranie i instalowanie aplikacji awps-tunnel

Narzędzie działa w Node.js w wersji 16 lub nowszej.

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

Użyj parametrów połączenia z usługą i uruchom

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

Uruchamianie serwera internetowego

Teraz wszystko jest ustawione. Uruchommy serwer WWW i pobawmy się działającą aplikacją czatu.

Teraz uruchom serwer przy użyciu polecenia dotnet run --urls http://localhost:8080.

Kompletny przykład kodu tego samouczka można znaleźć tutaj.

Otwórz http://localhost:8080/index.html. Możesz wprowadzić nazwę użytkownika i rozpocząć rozmowę.

Odroczone uwierzytelnianie z procedurą obsługi zdarzeń connect

W poprzednich sekcjach pokazano, jak używać punktu końcowego negocjowania w celu zwrócenia adresu URL usługi Web PubSub i tokenu dostępu JWT dla klientów w celu nawiązania połączenia z usługą Web PubSub. W niektórych przypadkach, na przykład urządzeń brzegowych dysponujących ograniczonymi zasobami, klienci mogą preferować bezpośrednie łączenie się z zasobami Web PubSub. W takich przypadkach można skonfigurować program obsługi zdarzenia connect, aby realizował opóźnione uwierzytelnianie klientów, przypisywał klientom identyfikator użytkownika, określał grupy, do których klienci dołączają po nawiązaniu połączenia, konfigurował uprawnienia klientów oraz podprotokół WebSocket jako odpowiedź WebSocket wysyłaną do klienta itd. Szczegółowe informacje można znaleźć w specyfikacji programu obsługi zdarzenia connect.

Teraz użyjemy connect obsługi zdarzenia, aby osiągnąć efekt podobny do tego, jaki daje sekcja negotiate.

Aktualizowanie ustawień centrum

Najpierw zaktualizujmy ustawienia centrum, aby uwzględnić connect również procedurę obsługi zdarzeń, musimy również zezwolić na anonimowe łączenie, aby klienci bez tokenu dostępu JWT mogli łączyć się z usługą.

Użyj polecenia interfejsu wiersza polecenia platformy Azure az webpubsub hub update, aby utworzyć ustawienia obsługi zdarzeń dla huba Sample_ChatApp.

Ważne

Zastąp <your-unique-resource-name> nazwą zasobu Web PubSub utworzonego w poprzednich krokach.

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"

Aktualizowanie logiki nadrzędnej w celu obsługi zdarzenia połączenia

Teraz zaktualizujmy logikę nadrzędną, aby obsłużyć zdarzenie połączenia. Możemy również usunąć teraz punkt końcowy negocjatowania.

Podobnie jak w przypadku negocjowania punktu końcowego jako celu demonstracyjnego, odczytamy również identyfikator z parametrów zapytania. W przypadku połączenia oryginalne zapytanie klienta jest zachowywane w treści żądania połączenia.

Wewnątrz klasy Sample_ChatApp nadpisz OnConnectAsync(), aby obsłużyć zdarzenie 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();
    }
}

Zaktualizuj plik index.html pod kątem połączenia bezpośredniego

Teraz zaktualizujmy stronę internetową, aby bezpośrednio nawiązać połączenie z usługą Web PubSub. Warto wspomnieć, że obecnie na potrzeby demonstracji punkt końcowy usługi Web PubSub jest na stałe zakodowany w kodzie klienta; zaktualizuj nazwę hosta usługi <the host name of your service> w poniższym kodzie HTML, używając wartości z własnej usługi. Nadal przydatne może być pobranie wartości punktu końcowego usługi Web PubSub z serwera, co zapewnia większą elastyczność i możliwość kontrolowania lokalizacji, z którą klient nawiązuje połączenie.

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

Ponowne uruchamianie serwera

Teraz ponownie uruchom serwer i odwiedź stronę internetową zgodnie z instrukcjami przed. Jeśli zatrzymano awps-tunnel, proszę również ponownie uruchomić narzędzie do tunelowania.

Następne kroki

Ten samouczek zawiera podstawowe informacje na temat działania systemu zdarzeń w usłudze Azure Web PubSub.

Zapoznaj się z innymi samouczkami, aby dowiedzieć się więcej na temat korzystania z usługi.