Tutorial: Criar um aplicativo de chat com o serviço Azure Web PubSub

No tutorial Publicar e assinar mensagem, você aprende as noções básicas de publicação e assinatura de mensagens com o Azure Web PubSub. Neste tutorial, você aprende o sistema de eventos do Azure Web PubSub e o usa para criar um aplicativo Web completo com funcionalidade de comunicação em tempo real.

Neste tutorial, irá aprender a:

  • Criar uma instância de serviço Web PubSub
  • Definir configurações do manipulador de eventos para o Azure Web PubSub
  • Manipule eventos no servidor de aplicativos e crie um aplicativo de bate-papo em tempo real

Se não tiver uma conta do Azure, crie uma conta gratuita antes de começar.

Pré-requisitos

  • Esta configuração requer a versão 2.22.0 ou superior da CLI do Azure. Se estiver usando o Azure Cloud Shell, a versão mais recente já está instalada.

Criar uma instância do Azure Web PubSub

Criar um grupo de recursos

Um grupo de recursos é um contentor lógico no qual os recursos do Azure são implementados e geridos. Use o comando az group create para criar um grupo de recursos nomeado myResourceGroup no eastus local.

az group create --name myResourceGroup --location EastUS

Criar uma instância do Web PubSub

Execute az extension add para instalar ou atualizar a extensão webpubsub para a versão atual.

az extension add --upgrade --name webpubsub

Use o comando CLI do Azure az webpubsub create para criar um Web PubSub no grupo de recursos que você criou. O comando a seguir cria um recurso Free Web PubSub no grupo de recursos myResourceGroup em EastUS:

Importante

Cada recurso Web PubSub deve ter um nome exclusivo. Substitua <your-unique-resource-name> pelo nome do seu Web PubSub nos exemplos a seguir.

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

A saída deste comando mostra as propriedades do recurso recém-criado. Tome nota das duas propriedades listadas abaixo:

  • Nome do recurso: O nome que você forneceu para o --name parâmetro acima.
  • hostName: No exemplo, o nome do host é <your-unique-resource-name>.webpubsub.azure.com/.

Neste ponto, sua conta do Azure é a única autorizada a executar quaisquer operações neste novo recurso.

Obtenha o ConnectionString para uso futuro

Importante

As cadeias de conexão brutas aparecem neste artigo apenas para fins de demonstração.

Uma cadeia de ligação inclui a informação de autorização que a sua aplicação precisa para aceder ao serviço Azure Web PubSub. A chave de acesso na cadeia de ligação funciona como uma palavra-passe root para o seu serviço. Em ambientes de produção, proteja sempre as suas chaves de acesso. Utilize o Azure Key Vault para gerir e rodar as suas chaves com segurança e proteger a sua ligação com WebPubSubServiceClient.

Evite distribuir chaves de acesso para outros usuários, codificá-las ou salvá-las em qualquer lugar em texto simples acessível a outras pessoas. Altere as suas chaves se suspeitar que possam ter sido comprometidas.

Use o comando CLI do Azure az webpubsub key para obter o ConnectionString do serviço. Substitua o espaço reservado <your-unique-resource-name> pelo nome da sua instância do Azure Web PubSub.

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

Copie a cadeia de conexão para usar mais tarde.

Copie o ConnectionString buscado e defina-o na variável WebPubSubConnectionStringde ambiente , que o tutorial lê posteriormente. Substitua <connection-string> abaixo por ConnectionString que obteve.

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

Configurar o projeto

Pré-requisitos

Criar a aplicação

No Azure Web PubSub, há duas funções, servidor e cliente. Esse conceito é semelhante às funções de servidor e cliente em um aplicativo Web. O servidor é responsável por gerenciar os clientes, ouvir e responder às mensagens do cliente. O cliente é responsável por enviar e receber mensagens do usuário do servidor e visualizá-las para o usuário final.

Neste tutorial, construímos uma aplicação web de chat em tempo real. Em um aplicativo Web real, a responsabilidade do servidor também inclui a autenticação de clientes e o serviço de páginas da Web estáticas para a interface do usuário do aplicativo.

Usamos o ASP.NET Core 8 para hospedar as páginas da Web e lidar com solicitações recebidas.

Primeiro, vamos criar um aplicativo Web ASP.NET Core em uma chatapp pasta.

  1. Crie um novo aplicativo Web.

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. Adicione app.UseStaticFiles() Program.cs para suportar a hospedagem de páginas da Web estáticas.

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. Crie um arquivo HTML e salve-o como wwwroot/index.html, nós o usamos para a interface do usuário do aplicativo de bate-papo mais tarde.

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

Você pode testar o servidor executando dotnet run --urls http://localhost:8080 e acessando http://localhost:8080/index.html no navegador.

Adicionar ponto de extremidade de negociação

No tutorial Publicar e assinar mensagem, o assinante consome cadeia de conexão diretamente. Numa aplicação real, não é seguro partilhar a cadeia de ligação com nenhum cliente, porque a cadeia de ligação tem privilégios elevados para executar qualquer operação no serviço. Agora, vamos fazer com que o seu servidor consuma a cadeia de ligação e exponha um ponto de extremidade negotiate para que o cliente obtenha o URL completo com o token de acesso. Desta forma, o servidor pode adicionar middleware de autenticação antes do endpoint negotiate para impedir o acesso não autorizado.

Primeiro, instale as dependências.

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

Agora, vamos adicionar um endpoint /negotiate para o cliente invocar e gerar o 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>() é usado para injetar o cliente WebPubSubServiceClient<THub>de serviço , com o qual podemos usar na etapa de negociação para gerar token de conexão do cliente e nos métodos de hub para invocar APIs REST de serviço quando os eventos de hub são acionados. Este código de geração de token é semelhante ao que usamos no tutorial de publicar e assinar mensagem, exceto que passamos mais um argumento (userId) ao gerar o token. O ID de usuário pode ser usado para identificar a identidade do cliente para que, quando você receber uma mensagem, saiba de onde a mensagem está vindo.

O código lê a cadeia de conexão da variável WebPubSubConnectionString de ambiente que definimos na etapa anterior.

Volte a executar o servidor com dotnet run --urls http://localhost:8080.

Você pode testar essa API acessando http://localhost:8080/negotiate?id=user1 e ela fornece a URL completa do Azure Web PubSub com um token de acesso.

Processar eventos

No Azure Web PubSub, quando determinadas atividades acontecem no lado do cliente (por exemplo, um cliente está se conectando, conectado, desconectado ou um cliente está enviando mensagens), o serviço envia notificações ao servidor para que ele possa reagir a esses eventos.

Os eventos são entregues ao servidor sob a forma de um webhook. O Webhook é disponibilizado e exposto pelo servidor da aplicação e registado no serviço Azure Web PubSub. O serviço invoca os webhooks sempre que um evento acontece.

O Azure Web PubSub segue o CloudEvents para descrever os dados do evento.

Abaixo, lidamos com connected eventos do sistema quando um cliente está conectado e lidamos com message eventos do usuário quando um cliente está enviando mensagens para criar o aplicativo de bate-papo.

O SDK do Web PubSub para AspNetCore Microsoft.Azure.WebPubSub.AspNetCore que instalamos na etapa anterior também pode ajudar a analisar e processar as solicitações do CloudEvents.

Primeiro, adicione manipuladores de eventos antes de app.Run(). Especifique o caminho do ponto final para os eventos, por exemplo /eventhandler.

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

Agora, dentro da classe Sample_ChatApp que criamos na etapa anterior, adicione um construtor para trabalhar com WebPubSubServiceClient<Sample_ChatApp> o qual usamos para invocar o serviço Web PubSub. E OnConnectedAsync() para responder quando connected o evento é acionado, OnMessageReceivedAsync() para lidar com mensagens do cliente.

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

No código acima, usamos o cliente de serviço para transmitir uma mensagem de notificação no formato JSON para todos os que estão associados ao SendToAllAsync.

Atualizar a página Web

Agora vamos atualizar index.html para adicionar a lógica para conectar, enviar mensagens e exibir mensagens recebidas na página.

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

Pode ver no código acima que usamos a API WebSocket nativa no navegador e WebSocket.send() para enviar mensagens e WebSocket.onmessage para escutar as mensagens recebidas.

Você também pode usar SDKs de cliente para se conectar ao serviço, o que permite que você reconecte automaticamente, tratamento de erros e muito mais.

Agora falta um passo para o bate-papo funcionar. Vamos configurar quais eventos nos interessam e para onde enviá-los no serviço Web PubSub.

Configurar o manipulador de eventos

Definimos o manipulador de eventos no serviço Web PubSub para informar ao serviço para onde enviar os eventos.

Quando o servidor Web é executado localmente, como o serviço Web PubSub invoca o localhost se ele não tiver um ponto de extremidade acessível pela Internet? Geralmente, há dois caminhos. Uma opção é expor o localhost publicamente usando uma ferramenta genérica de tunelização, e a outra é usar awps-tunnel para encaminhar o tráfego do serviço Web PubSub através dessa ferramenta até ao seu servidor local.

Nesta seção, usamos a CLI do Azure para definir os manipuladores de eventos e usar awps-tunnel para rotear o tráfego para localhost.

Definir configurações de hub

Definimos o modelo de URL para utilizar o esquema tunnel, para que o Web PubSub encaminhe mensagens através da ligação de túnel do awps-tunnel. Os manipuladores de eventos podem ser definidos a partir do portal ou da CLI, conforme descrito neste artigo, aqui nós o definimos por meio da CLI. Como escutamos os eventos no caminho /eventhandler, conforme definido na etapa anterior, definimos o modelo de URL como tunnel:///eventhandler.

Use o comando CLI do Azure az webpubsub hub create para criar as configurações do manipulador de eventos para o Sample_ChatApp hub.

Importante

Substitua <your-unique-resource-name> pelo nome do recurso Web PubSub criado a partir das etapas anteriores.

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"

Executar awps-tunnel localmente

Baixe e instale o awps-tunnel

A ferramenta é executada em Node.js versão 16 ou superior.

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

Utilize a cadeia de ligação ao serviço e execute

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

Executar o servidor Web

Agora está tudo definido. Vamos executar o servidor web e jogar com o aplicativo de bate-papo em ação.

Agora execute o servidor usando dotnet run --urls http://localhost:8080.

O exemplo de código completo deste tutorial pode ser encontrado aqui.

Abra http://localhost:8080/index.html. Você pode inserir seu nome de usuário e começar a conversar.

Lazy Auth com o manipulador de eventos connect

Nas secções anteriores, mostrámos como usar o ponto final negotiate para devolver o URL do serviço Web PubSub e o token de acesso JWT para os clientes se ligarem ao serviço Web PubSub. Em alguns casos, por exemplo no caso de dispositivos de borda com recursos limitados, os clientes podem preferir uma ligação direta aos recursos do Web PubSub. Nestes casos, é possível configurar o connect manipulador de eventos para efetuar a autenticação diferida dos clientes, atribuir um ID de utilizador aos clientes, especificar os grupos a que os clientes se juntam assim que se ligam, configurar as permissões de que os clientes dispõem e o subprotocolo WebSocket na resposta WebSocket ao cliente, etc. Para mais detalhes, consulte a especificação do manipulador de eventos de ligação.

Agora vamos usar connect o manipulador de eventos para obter o semelhante ao que a seção de negociação faz.

Atualizar configurações do hub

Primeiro, vamos atualizar as configurações do hub para incluir também connect o manipulador de eventos, precisamos também permitir a conexão anônima para que os clientes sem token de acesso JWT possam se conectar ao serviço.

Use o comando CLI do Azure az webpubsub hub update para criar as configurações do manipulador de eventos para o Sample_ChatApp hub.

Importante

Substitua <your-unique-resource-name> pelo nome do recurso Web PubSub criado a partir das etapas anteriores.

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"

Atualizar a lógica a montante para processar o evento de ligação

Agora, vamos atualizar a lógica a montante para processar o evento de ligação. Também poderíamos remover o ponto final de negociação agora.

À semelhança do que fazemos na negociação do ponto final como objetivo de demonstração, também lemos id dos parâmetros de consulta. No evento connect, a consulta do cliente original é preservada no corpo da solicitação de evento connect.

Na classe Sample_ChatApp, redefina OnConnectAsync() para tratar o evento 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();
    }
}

Atualizar index.html para conexão direta

Agora vamos atualizar a página da Web para conexão direta com o serviço Web PubSub. Convém mencionar que, neste momento, para efeitos de demonstração, o ponto final do serviço Web PubSub está definido de forma fixa no código do cliente. Atualize o nome do host do serviço <the host name of your service> no HTML abaixo pelo valor do seu próprio serviço. Pode ainda ser útil obter do seu servidor o valor do ponto final do serviço Web PubSub, pois isso dá-lhe mais flexibilidade e controlo sobre o local ao qual o cliente se liga.

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

Execute novamente o servidor

Agora execute novamente o servidor e visite a página da Web seguindo as instruções anteriores. Se você parou awps-tunnel, execute novamente a ferramenta de túnel.

Próximos passos

Este tutorial fornece uma ideia básica de como o sistema de eventos funciona no serviço Azure Web PubSub.

Confira outros tutoriais para se aprofundar em como usar o serviço.