Руководство: публикация сообщений и подписка на них между клиентами WebSocket с использованием подпротокола

В руководстве по созданию приложения чата вы узнали, как использовать API WebSocket для отправки и получения данных с помощью Azure Web PubSub. Для обмена данными между клиентом и службой протокол не требуется. Например, можно отправить любой тип данных с помощью WebSocket.send(), и сервер получает их в том же виде. Процесс API WebSocket прост в использовании, но функциональность ограничена. Например, нельзя указать имя события при отправке события на сервер или опубликовать сообщение другим клиентам вместо отправки на сервер. В этом руководстве описано, как использовать подпротокол для расширения функциональности клиента.

В этом руководстве описано следующее:

  • Создайте экземпляр службы Web PubSub
  • Создание полного URL-адреса для установки подключения WebSocket
  • публикация сообщений между клиентами WebSocket с использованием подпротокола.

Если у вас нет аккаунта Azure, создайте бесплатную учетную запись перед началом.

Предварительные требования

  • Для этой настройки требуется Azure CLI версии 2.22.0 или выше. Если вы используете Azure Cloud Shell, последняя версия уже установлена.

Внимание

Необработанные строки подключения приводятся в этой статье исключительно в демонстрационных целях.

A строка подключения содержит информацию об авторизации, необходимую вашему приложению для доступа к сервису Azure Web PubSub. Ключ доступа в строка подключения действует как root-пароль для вашего сервиса. В рабочих средах всегда защищать ключи доступа. Используйте Azure Key Vault для безопасного управления ключами и их ротации, а также защиты подключения с помощью WebPubSubServiceClient.

Старайтесь не распространять ключи доступа среди других пользователей, жестко программировать их или где-то сохранять в виде обычного текста в открытом доступе для других пользователей. Смените ключи, если считаете, что они могли быть скомпрометированы.

Создайте экземпляр Azure Web PubSub

Создайте группу ресурсов

Группа ресурсов — это логический контейнер, в котором происходит развертывание ресурсов Azure и управление ими. Используйте команду az group create, чтобы создать группу ресурсов с именем myResourceGroup в расположенииeastus.

az group create --name myResourceGroup --location EastUS

Создайте экземпляр Web PubSub

Запустите az extension add, чтобы установить или обновить расширение webpubsub до текущей версии.

az extension add --upgrade --name webpubsub

Используйте команду Azure CLI az webpubsub create, чтобы создать ресурс Web PubSub в созданной вами группе ресурсов. Следующая команда создает ресурс Free Web PubSub в группе ресурсов myResourceGroup в EastUS:

Внимание

Каждый ресурс Web PubSub должен иметь уникальное имя. В следующих примерах замените <your-unique-resource-name> именем своей службы Web PubSub.

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

В выходных данных команды будут показаны свойства созданного ресурса. Запишите значения двух указанных ниже свойств.

  • Имя ресурса: имя, которое вы ввели для указанного выше параметра --name.
  • hostName: в примере имя узла <your-unique-resource-name>.webpubsub.azure.com/.

На данном этапе любые операции в этом новом ресурсе могут выполняться только с использованием вашей учетной записи Azure.

Получение ConnectionString для будущего использования

Необработанные строки подключения приводятся в этой статье исключительно в демонстрационных целях. В рабочих средах всегда защищать ключи доступа. Используйте Azure Key Vault для безопасного управления ключами и их ротации, а также защиты подключения с помощью WebPubSubServiceClient.

Выполните команду Azure CLI az webpubsub key, чтобы получить значение ConnectionString службы. Замените заполнитель <your-unique-resource-name> на имя экземпляра Azure Web PubSub.

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

Скопируйте строку подключения, чтобы использовать её позже.

Скопируйте полученную ConnectionString и используйте её позже в этом руководстве в качестве значения <connection_string>.

Настройка проекта

Предварительные требования

Использование подпротокола

Клиент может установить подключение к WebSocket по определенному подпротоколу. Служба Azure Web PubSub поддерживает подпротокол, называемый json.webpubsub.azure.v1, который позволяет клиентам напрямую публиковать сообщения и подписываться через службу Azure Web PubSub, минуя обращение к вышестоящему серверу. Сведения о подпротоколе см. в статье Поддерживаемый в Azure Web PubSub подпротокол JSON WebSocket.

Если вы используете другие имена протоколов, они будут игнорироваться службой и передаваться на сервер в обработчике событий подключения, позволяя вам создавать собственные протоколы.

Теперь создадим веб-приложение с использованием подпротокола json.webpubsub.azure.v1.

  1. Установка зависимостей

    mkdir logstream
    cd logstream
    dotnet new web
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Messaging.WebPubSub
    
  2. Создайте серверную часть для размещения API /negotiate и веб-страницы.

    Измените файл Program.cs, добавив приведенный ниже код.

    • Используйте AddAzureClients, чтобы добавить клиент службы и прочитать строку подключения из конфигурации.
    • Добавьте app.UseStaticFiles(); перед app.Run(); для поддержки статических файлов.
    • И измените app.MapGet так, чтобы он генерировал токен доступа клиента, используя запросы /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. Создание веб-страницы

    Создайте HTML-страницу с приведенным ниже содержимым и сохраните ее как 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>                                                                
    

    Приведенный выше код подключается к службе и выводит любое сообщение, полученное на страницу. Основное изменение заключается в том, что при создании соединения WebSocket мы указываем подпротокол.

  4. Запуск сервера

    Мы используем средство Secret Manager для .NET Core, чтобы настроить строку подключения. Выполните приведенную ниже команду, заменив <connection_string> строкой, полученной на предыдущем шаге, и откройте http://localhost:5000/index.html в браузере.

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

    Если вы используете Chrome, вы можете нажать клавишу F12 или щёлкнуть правой кнопкой мыши ->Просмотреть код ->Инструменты разработчика и выбрать вкладку Сеть. Загрузите веб-страницу, и вы увидите, что соединение WebSocket установлено. Выберите подключение WebSocket, чтобы проверить его; ниже видно, что клиент получил сообщение о событии connected. Как видите, вы можете сгенерировать connectionId для этого клиента.

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

Как видите, с помощью подпротокола можно получить метаданные подключения, если оно имеет значение connected.

Теперь клиент получает сообщение JSON вместо обычного текста. Сообщение JSON содержит дополнительные сведения, такие как тип и источник сообщения. Эти сведения можно использовать для дальнейшей обработки сообщения (например, отобразить сообщение в другом стиле, если оно получено из другого источника). Это описано следующих разделах.

Публикация сообщений от клиента

В руководстве по созданию приложения чата, когда клиент отправляет сообщение через подключение WebSocket к службе Web PubSub, служба активирует событие пользователя на стороне сервера. При использовании подпротокол клиент имеет больше функций, отправляя сообщение JSON. Например, вы можете публиковать сообщения непосредственно из клиента через службу Web PubSub другим клиентам.

Это полезно, если требуется потоковая передача большого объема данных другим клиентам в режиме реального времени. Давайте воспользуемся этой возможностью, чтобы создать приложение потоковой передачи журналов, которое может выполнять потоковую передачу журналов консоли в браузер в реальном времени.

  1. Создание программы потоковой передачи

    Создайте программу stream:

    mkdir stream
    cd stream
    dotnet new console
    

    Измените Program.cs, добавив следующее содержимое:

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

    Как видите, здесь есть новое понятие «группа». Группа — это логическая концепция в концентраторе, где можно опубликовать сообщение для группы подключений. В концентраторе можно создать несколько групп. Один клиент может одновременно подписаться на несколько групп. При использовании подпротокола можно публиковать сообщения только в группу, а не выполнять широковещательную отправку по всему центру. Сведения о терминах см. в статье об основных понятиях.

  2. Так как мы используем здесь группу, нам также нужно обновить веб-страницу index.html, чтобы присоединиться к группе, если подключение WebSocket устанавливается в пределах обратного вызова ws.onopen.

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

    Как показано, клиент присоединяется к группе, отправляя сообщение типа joinGroup.

  3. Также можно немного обновить логику обратного вызова ws.onmessage, чтобы проанализировать ответ JSON и вывести сообщения только от группы stream, используя аналог принтера потока в реальном времени.

    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. По соображениям безопасности клиент по умолчанию не может публиковать данные или подписываться на группу. Итак, вы заметили, что при создании токена мы присвоили клиенту roles:

    Установите roles при GenerateClientAccessUri в Startup.cs, как показано ниже:

    service.GenerateClientAccessUri(roles: new string[] { "webpubsub.sendToGroup.stream", "webpubsub.joinLeaveGroup.stream" })
    
  5. Наконец, примените нужный стиль к index.html для отображения в удобном формате.

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

Теперь запустите приведенный ниже код и введите любой текст, и они отображаются в браузере в режиме реального времени:

ls -R | dotnet run

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

Или можно замедлить процесс, чтобы увидеть, как данные в реальном времени потоково передаются в браузер.

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

Полный пример кода для этого учебника можно найти здесь.

Следующие шаги

В этом руководстве вы узнаете, как подключиться к службе Web PubSub и как публиковать сообщения в подключенных клиентах с помощью подпротокола.

Ознакомьтесь с другими руководствами, чтобы глубже разобраться в использовании сервиса.