Tutorial: Publicar e assinar mensagens entre clientes WebSocket usando subprotocolo

No tutorial Criar um aplicativo de chat, você aprendeu como usar APIs WebSocket para enviar e receber dados com o Azure Web PubSub. Você pode ver que não há nenhum protocolo necessário quando o cliente está se comunicando com o serviço. Por exemplo, pode enviar qualquer tipo de dado usando WebSocket.send(), e o servidor recebe-o tal como o envia. O processo de APIs WebSocket é fácil de usar, mas a funcionalidade é limitada. Por exemplo, não é possível especificar o nome do evento ao enviar o evento para o servidor ou publicar uma mensagem para outros clientes em vez de enviá-la para o servidor. Neste tutorial, você aprenderá a usar o subprotocolo para estender a funcionalidade do cliente.

Neste tutorial, irá aprender a:

  • Criar uma instância de serviço Web PubSub
  • Gere a URL completa para estabelecer a conexão WebSocket
  • Publicar mensagens entre clientes WebSocket usando subprotocolo

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.

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.

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

As cadeias de conexão brutas aparecem neste artigo apenas para fins de demonstraçã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.

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 a ConnectionString obtida e utilize-a mais tarde neste tutorial como o valor de .

Configurar o projeto

Pré-requisitos

Usando um subprotocolo

O cliente pode iniciar uma conexão WebSocket usando um subprotocolo específico. O serviço Azure Web PubSub dá suporte a um subprotocolo chamado json.webpubsub.azure.v1 para capacitar os clientes a publicar/assinar diretamente por meio do serviço Web PubSub em vez de uma viagem de ida e volta para o servidor upstream. Consulte o subprotocolo JSON WebSocket suportado pelo Azure Web PubSub para obter detalhes sobre o subprotocolo.

Se você usar outros nomes de protocolo, eles serão ignorados pelo serviço e passarão para o servidor no manipulador de eventos connect, para que você possa criar seus próprios protocolos.

Agora vamos criar um aplicativo Web usando o json.webpubsub.azure.v1 subprotocolo.

  1. Instalar dependências

    mkdir logstream
    cd logstream
    dotnet new web
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Messaging.WebPubSub
    
  2. Crie a parte do servidor para alojar a /negotiate API e a página web.

    Atualize Program.cs com o código abaixo.

    • Use AddAzureClients para adicionar o cliente de serviço e leia a cadeia de conexão da configuração.
    • Adicionar app.UseStaticFiles(); antes app.Run(); para suportar arquivos estáticos.
    • E atualize app.MapGet para gerar o token de acesso do cliente com /negotiate solicitações.
    using Azure.Messaging.WebPubSub;
    using Microsoft.Extensions.Azure;
    
    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddAzureClients(s =>
    {
        s.AddWebPubSubServiceClient(builder.Configuration["Azure:WebPubSub:ConnectionString"], "stream");
    });
    
    var app = builder.Build();
    app.UseStaticFiles();
    app.MapGet("/negotiate", async context =>
    {
        var service = context.RequestServices.GetRequiredService<WebPubSubServiceClient>();
        var response = new
        {
            url = service.GetClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" }).AbsoluteUri
        };
        await context.Response.WriteAsJsonAsync(response);
    });
    
    app.Run();
    
  3. Criar a página Web

    Crie uma página HTML com o conteúdo abaixo e salve-a como wwwroot/index.html:

    <html>
      <body>
        <div id="output"></div>
        <script>
          (async function () {
            let res = await fetch('/negotiate')
            let data = await res.json();
            let ws = new WebSocket(data.url, 'json.webpubsub.azure.v1');
            ws.onopen = () => {
              console.log('connected');
            };
    
            let output = document.querySelector('#output');
            ws.onmessage = event => {
              let d = document.createElement('p');
              d.innerText = event.data;
              output.appendChild(d);
            };
          })();
        </script>
      </body>
    </html>                                                                
    

    O código acima se conecta ao serviço e imprime qualquer mensagem recebida na página. A principal mudança é que especificamos o subprotocolo ao criar a conexão WebSocket.

  4. Inicie o servidor

    Usamos a ferramenta Secret Manager para .NET Core para definir a cadeia de conexão. Execute o comando abaixo, substituindo <connection_string> pelo obtido na etapa anterior, e abra http://localhost:5000/index.html no navegador:

    dotnet user-secrets init
    dotnet user-secrets set Azure:WebPubSub:ConnectionString "<connection-string>"
    dotnet run
    

    Se estiver a utilizar o Chrome, pode premir F12 ou clicar com o botão direito do rato em ->Inspecionar ->Ferramentas de programador e selecionar o separador Rede. Carregue a página Web e poderá ver que a ligação WebSocket foi estabelecida. Selecione para inspecionar a ligação WebSocket; pode ver abaixo que o cliente recebe a mensagem de evento connected. Pode ver que é possível obter o connectionId gerado para este cliente.

    {"type":"system","event":"connected","userId":null,"connectionId":"<the_connection_id>"}
    

Você pode ver que, com a ajuda do subprotocolo, você pode obter alguns metadados da conexão quando a conexão é connected.

O cliente agora recebe uma mensagem JSON em vez de um texto sem formatação. A mensagem JSON contém mais informações, como tipo e origem da mensagem. Assim, você pode usar essas informações para fazer mais processamento para a mensagem (por exemplo, exibir a mensagem em um estilo diferente se for de uma fonte diferente), que você pode encontrar em seções posteriores.

Publicar mensagens do cliente

No tutorial Criar um aplicativo de chat, quando o cliente envia uma mensagem através da conexão WebSocket para o serviço Web PubSub, o serviço dispara um evento de usuário no lado do servidor. Com o subprotocolo, o cliente tem mais funcionalidades enviando uma mensagem JSON. Por exemplo, você pode publicar mensagens diretamente do cliente por meio do serviço Web PubSub para outros clientes.

Isso é útil se você quiser transmitir uma grande quantidade de dados para outros clientes em tempo real. Vamos usar esse recurso para criar um aplicativo de streaming de logs, que pode transmitir logs do console para o navegador em tempo real.

  1. Criação do programa de streaming

    Crie um stream programa:

    mkdir stream
    cd stream
    dotnet new console
    

    Atualize Program.cs com o seguinte conteúdo:

    using System;
    using System.Net.Http;
    using System.Net.WebSockets;
    using System.Text;
    using System.Text.Json;
    using System.Threading.Tasks;
    
    namespace stream
    {
        class Program
        {
            private static readonly HttpClient http = new HttpClient();
            static async Task Main(string[] args)
            {
                // Get client url from remote
                var stream = await http.GetStreamAsync("http://localhost:5000/negotiate");
                var url = (await JsonSerializer.DeserializeAsync<ClientToken>(stream)).url;
                var client = new ClientWebSocket();
                client.Options.AddSubProtocol("json.webpubsub.azure.v1");
    
                await client.ConnectAsync(new Uri(url), default);
    
                Console.WriteLine("Connected.");
                var streaming = Console.ReadLine();
                while (streaming != null)
                {
                    if (!string.IsNullOrEmpty(streaming))
                    {
                        var message = JsonSerializer.Serialize(new
                        {
                            type = "sendToGroup",
                            group = "stream",
                            data = streaming + Environment.NewLine,
                        });
                        Console.WriteLine("Sending " + message);
                        await client.SendAsync(Encoding.UTF8.GetBytes(message), WebSocketMessageType.Text, true, default);
                    }
    
                    streaming = Console.ReadLine();
                }
    
                await client.CloseAsync(WebSocketCloseStatus.NormalClosure, null, default);
            }
    
            private sealed class ClientToken
            {
                public string url { get; set; }
            }
        }
    }
    
    

    Você pode ver que há um novo conceito "grupo" aqui. Grupo é um conceito lógico em um hub onde você pode publicar mensagens em um grupo de conexões. Em um hub, você pode ter vários grupos e um cliente pode se inscrever em vários grupos ao mesmo tempo. Ao usar o subprotocolo, você só pode publicar em um grupo em vez de transmitir para todo o hub. Para obter detalhes sobre os termos, verifique os conceitos básicos.

  2. Como usamos o group aqui, também precisamos de atualizar a página web index.html para aderir ao group quando a ligação WebSocket é estabelecida dentro do callback ws.onopen.

    let ackId = 0;
    ws.onopen = () => {
      console.log('connected');
      ws.send(JSON.stringify({
        type: 'joinGroup',
        group: 'stream',
        ackId: ++ackId
      }));
    };
    

    Pode ver que um cliente entra no grupo ao enviar uma mensagem do tipo joinGroup.

  3. Além disso, atualize ligeiramente a lógica de callback ws.onmessage para analisar a resposta JSON e imprimir apenas as mensagens do grupo stream, para que funcione como um impressor de fluxo em tempo real.

    ws.onmessage = event => {
      let message = JSON.parse(event.data);
      if (message.type === 'message' && message.group === 'stream') {
        let d = document.createElement('span');
        d.innerText = message.data;
        output.appendChild(d);
        window.scrollTo(0, document.body.scrollHeight);
      }
    };
    
  4. Por motivos de segurança, por padrão, um cliente não pode publicar ou assinar um grupo sozinho. Então você notou que definimos roles para o cliente ao gerar o token:

    Defina roles quando GenerateClientAccessUri em Startup.cs como abaixo:

    service.GenerateClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" })
    
  5. Por fim, aplique também algum estilo a index.html para que seja apresentado corretamente.

    <html>
    
      <head>
        <style>
          #output {
            white-space: pre;
            font-family: monospace;
          }
        </style>
      </head>
    

Agora execute o código abaixo e digite qualquer texto e eles são exibidos no navegador em tempo real:

ls -R | dotnet run

# Or call `dir /s /b | dotnet run` when you are using CMD under Windows

Ou você o torna mais lento para que você possa ver os dados são transmitidos para o navegador em tempo real:

for i in $(ls -R); do echo $i; sleep 0.1; done | dotnet run

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

Próximos passos

Este tutorial fornece uma ideia básica de como se conectar ao serviço Web PubSub e como publicar mensagens para os clientes conectados usando o subprotocolo.

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