Tutorial: Publicación y suscripción de mensajes entre clientes de WebSocket mediante un subprotocolo

En el tutorial Creación de una aplicación de chat, ha aprendido a usar las API de WebSocket para enviar y recibir datos con Azure Web PubSub. Puede ver que no se necesita ningún protocolo cuando el cliente se comunica con el servicio. Por ejemplo, puede enviar cualquier tipo de datos mediante WebSocket.send()y el servidor lo recibe tal como lo es. El proceso de API de WebSocket es fácil de usar, pero la funcionalidad es limitada. Por ejemplo, no puede especificar el nombre del evento al enviar el evento al servidor o publicar un mensaje en otros clientes en lugar de enviarlo al servidor. En este tutorial, aprenderá a usar subprotocolo para ampliar la funcionalidad del cliente.

En este tutorial, aprenderá a:

  • Crear una instancia del servicio Web PubSub
  • Generar la dirección URL completa para establecer la conexión de WebSocket.
  • Publicar mensajes entre clientes de WebSocket mediante un subprotocolo.

Si no tiene una suscripción a Azure, cree una cuenta gratuita de Azure antes de empezar.

Requisitos previos

  • Esta configuración requiere la versión 2.22.0, o cualquier versión superior, de la CLI de Azure. Si usa Azure Cloud Shell, ya está instalada la versión más reciente.

Creación de una instancia de Azure Web PubSub

Crear un grupo de recursos

Un grupo de recursos es un contenedor lógico en el que se implementan y se administran los recursos de Azure. Use el comando az group create para crear un grupo de recursos denominado myResourceGroup en la ubicación eastus.

az group create --name myResourceGroup --location EastUS

Creación de una instancia de Web PubSub

Ejecute az extension add para instalar o actualizar la extensión webpubsub en la versión actual.

az extension add --upgrade --name webpubsub

Use el comando az webpubsub create de la CLI de Azure para crear una instancia de Web PubSub en el grupo de recursos que ha creado. El comando siguiente crea un recurso Free de Web PubSub en el grupo de recursos myResourceGroup en EastUS:

Importante

Cada recurso de Web PubSub debe tener un nombre único. Reemplace <your-unique-resource-name> por el nombre de Web PubSub en los ejemplos siguientes.

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

La salida de este comando muestra las propiedades del recurso que acaba de crear. Tome nota de las dos propiedades siguientes:

  • Nombre del recurso: nombre que proporcionó al parámetro --name anterior.
  • hostName: en el ejemplo, el nombre de host es <your-unique-resource-name>.webpubsub.azure.com/.

En este momento, su cuenta de Azure es la única autorizada para realizar operaciones en este nuevo recurso.

Obtención de ConnectionString para usarlo en el futuro

Importante

Una cadena de conexión incluye la información de autorización necesaria para que la aplicación acceda al servicio Azure Web PubSub. La clave de acceso dentro de la cadena de conexión es similar a una contraseña raíz para el servicio. En los entornos de producción, siempre debe proteger las claves de acceso. Use Azure Key Vault para administrar y rotar las claves de forma segura. Evite distribuirlas a otros usuarios, codificarlas de forma rígida o guardarlas en un archivo de texto sin formato al que puedan acceder otros usuarios. Rote sus claves si cree que se han puesto en peligro.

Use el comando az webpubsub key de la CLI de Azure para obtener el valor de ConnectionString del servicio. Reemplace el marcador de posición <your-unique-resource-name> por el nombre de la instancia de Azure Web PubSub.

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

Copie la cadena de conexión para usarla más adelante.

Copie el Conectar ionString capturado y úselo más adelante en este tutorial como valor de <connection_string>.

Configuración del proyecto

Prerrequisitos

Uso de un subprotocolo

El cliente puede iniciar una conexión de WebSocket mediante un subprotocolo concreto. El servicio Azure Web PubSub admite un subprotocolo denominado json.webpubsub.azure.v1 que permite a los clientes publicar o suscribirse directamente a través del servicio Web PubSub en lugar de tener que realizar un recorrido de ida y vuelta al servidor ascendente. Para obtener información sobre el protocolo, consulte el artículo sobre el protocolo WebSocket JSON compatible con Azure Web PubSub.

Si usa otros nombres de protocolo, el servicio los omitirá y el acceso directo al servidor en el controlador del eventos connect, para que pueda crear sus propios protocolos.

Ahora vamos a crear una aplicación web mediante el subprotocolo json.webpubsub.azure.v1.

  1. Instalar dependencias

    mkdir logstream
    cd logstream
    dotnet new web
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Messaging.WebPubSub
    
  2. Cree el servidor para hospedar /negotiate API y la página web.

    Actualice Program.cs con el código siguiente.

    • Actualice el método AddAzureClients para agregar al cliente del servicio y lea la cadena de conexión de la configuración.
    • Después, agregue app.UseStaticFiles(); antes de app.Run(); para admitir los archivos estáticos.
    • Y actualice app.MapGet para generar el token de acceso de cliente con solicitudes /negotiate.
    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. Creación de la página web

    Cree una página HTML con el contenido siguiente y guárdela 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>                                                                
    

    El código se conecta al servicio e imprime todos los mensajes que se reciban en la página. El principal cambio es que especificamos el subprotocolo al crear la conexión WebSocket.

  4. Ejecute el servidor.

    Usamos la herramienta Secret Manager para .NET Core para establecer la cadena de conexión. Ejecute el siguiente comando, y reemplace <connection_string> por el que se ha capturado en el paso anterior y abra http://localhost:5000/index.html en el explorador:

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

    Si usa Chrome, puede presionar F12 o hacer clic con el botón derecho en ->Inspeccionar ->Herramientas de desarrollo y seleccionar la pestaña Red. Cargue la página web y verá que se establece la conexión WebSocket. Seleccione esta opción para inspeccionar la conexión de WebSocket; puede ver connected que se recibe el siguiente mensaje de evento en el cliente. Verá que puede obtener el valor de connectionId generado para este cliente.

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

Puede ver que, con la ayuda del subprotocolo, puede obtener algunos metadatos de la conexión cuando la conexión es connected.

El cliente recibe ahora un mensaje JSON en lugar de un texto sin formato. El mensaje JSON contiene más información, como el tipo y el origen del mensaje. Por consiguiente, esta información se puede usar para realizar más procesamiento en el mensaje (por ejemplo, mostrar el mensaje en un estilo diferente si es de otro origen), que puede encontrar en las secciones posteriores.

Publicación de mensajes desde el cliente

En el tutorial Build a chat app, cuando el cliente envía un mensaje a través de una conexión de WebSocket al servicio Web PubSub, el servicio desencadena un evento de usuario en el lado del servidor. Con el subprotocolo, el cliente tiene más funcionalidades mediante el envío de un mensaje JSON. Por ejemplo, puede publicar mensajes directamente desde el cliente a través del servicio Web PubSub a otros clientes.

Esto resulta útil si desea transmitir una gran cantidad de datos a otros clientes en tiempo real. Vamos a usar esta característica para compilar una aplicación de streaming de registros, que pueda hacer streaming de registros de la consola al explorador en tiempo real.

  1. Creación del programa de streaming

    Cree un programa stream:

    mkdir stream
    cd stream
    dotnet new console
    

    Actualice Program.cs con el siguiente contenido:

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

    Puede ver que hay un nuevo concepto "grupo" aquí. El grupo es un concepto lógico en un centro en el que puede publicar mensajes a un grupo de conexiones. En un centro, puede tener varios grupos y un cliente puede suscribirse a más de uno a la vez. Al usar un subprotocolo, solo puede publicar en un grupo, en lugar de retransmitir en todo el centro. Para más información sobre los términos, consulte los conceptos básicos.

  2. Puesto que aquí se usa grupo, también es necesario actualizar la página web index.html para unirse al grupo cuando se establece la conexión de WebSocket dentro de la devolución de llamada ws.onopen.

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

    Para ver que el cliente se une al grupo, envíe un mensaje del tipo joinGroup.

  3. Actualice también ligeramente la lógica de devolución de llamada ws.onmessage para analizar la respuesta JSON e imprimir los mensajes solo desde el grupo stream para que actúe como impresora de streaming en vivo.

    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 seguridad, de forma predeterminada ningún cliente puede publicar en un grupo, ni suscribirse a él, por sí mismo. Por lo tanto, ha observado que se establece roles en el cliente al generar el token:

    Establezca roles cuando GenerateClientAccessUri en Startup.cs sea como se muestra a continuación:

    service.GenerateClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" })
    
  5. Por último, aplique también algún estilo a index.html para que tenga una apariencia atractiva.

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

Ahora ejecute el código siguiente y escriba cualquier texto y se muestren en el explorador en tiempo real:

ls -R | dotnet run

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

O bien, ralentícelo para que pueda ver que los datos se transmiten en secuencias al explorador en tiempo real:

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

El código de ejemplo completo de este tutorial se puede encontrar aquí.

Pasos siguientes

En este tutorial se proporciona una idea básica de cómo conectarse al servicio Web PubSub y cómo publicar mensajes en los clientes conectados mediante subprotocolo.

En otros tutoriales podrá profundizar más en cómo usar el servicio.