チュートリアル: サブプロトコルを使用して、WebSocket クライアント間でメッセージを発行およびサブスクライブする

チャット アプリの構築チュートリアルでは、WebSocket API を使用して Azure Web PubSub でデータを送受信する方法について説明しました。 クライアントがサービスと通信する際に、プロトコルが必要ないことが分かります。 たとえば、任意の種類のデータを使用して WebSocket.send()送信でき、サーバーは、そのまま受信します。 WebSocket API プロセスは簡単に使用できますが、機能は制限されています。 たとえば、サーバーにイベントを送信するときにイベント名を指定したり、サーバーに送信する代わりにメッセージを他のクライアントに発行したりすることはできません。 このチュートリアルでは、サブプロトコルを使用してクライアントの機能を拡張する方法について説明します。

このチュートリアルでは、次の作業を行う方法について説明します。

  • Azure Web PubSub サービス インスタンスを作成する
  • 完全な URL を生成して WebSocket 接続を確立する
  • サブプロトコルを使用して WebSocket クライアント間でメッセージを発行する

Azure サブスクリプションをお持ちでない場合は、開始する前に Azure 無料アカウントを作成してください。

前提条件

  • Azure Cloud Shell で Bash 環境を使用します。 詳細については、「Azure Cloud Shell の Bash のクイックスタート」を参照してください。

  • CLI リファレンス コマンドをローカルで実行する場合、Azure CLI をインストールします。 Windows または macOS で実行している場合は、Docker コンテナーで Azure CLI を実行することを検討してください。 詳細については、「Docker コンテナーで Azure CLI を実行する方法」を参照してください。

    • ローカル インストールを使用する場合は、az login コマンドを使用して Azure CLI にサインインします。 認証プロセスを完了するには、ターミナルに表示される手順に従います。 その他のサインイン オプションについては、Azure CLI でのサインインに関するページを参照してください。

    • 初回使用時にインストールを求められたら、Azure CLI 拡張機能をインストールします。 拡張機能の詳細については、Azure CLI で拡張機能を使用する方法に関するページを参照してください。

    • az version を実行し、インストールされているバージョンおよび依存ライブラリを検索します。 最新バージョンにアップグレードするには、az upgrade を実行します。

  • このセットアップには、Azure CLI のバージョン 2.22.0 以降が必要です。 Azure Cloud Shell を使用している場合は、最新バージョンが既にインストールされています。

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 を作成します。 次のコマンドは、EastUS のリソース グループ myResourceGroup の下に "無料の" Web PubSub リソースを作成します。

重要

Web PubSub リソースには、それぞれ一意の名前を付ける必要があります。 次の例では、<your-unique-resource-name> をお使いの Web PubSub の名前に置き換えてください。

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

このコマンドの出力では、新しく作成したリソースのプロパティが表示されます。 次の 2 つのプロパティをメモしておきます。

  • Resource Name: 上記の --name パラメーターに指定した名前です。
  • hostName: この例では、ホスト名は <your-unique-resource-name>.webpubsub.azure.com/ です。

この時点で、お使いの Azure アカウントのみが、この新しいリソースで任意の操作を実行することを許可されています。

将来使用するために ConnectionString を取得する

重要

接続文字列には、アプリケーションが Azure Web PubSub サービスにアクセスするために必要な認可情報が含まれています。 接続文字列内のアクセス キーは、サービスのルート パスワードに似ています。 運用環境では、アクセス キーは常に慎重に保護してください。 キーを安全に管理およびローテーションするには、Azure Key Vault を使用します。 アクセス キーを他のユーザーに配布したり、ハードコーディングしたり、他のユーザーがアクセスできるプレーンテキストで保存したりしないでください。 キーが侵害された可能性があると思われる場合は、キーをローテーションしてください。

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 でサポートされる JSON WebSocket サブプロトコル」をご覧ください。

他のプロトコル名を使用すると、それはサービスによって無視され、接続イベント ハンドラー内のサーバーにパススルーされます。そのため、独自のプロトコルを構築することができます。

それでは、json.webpubsub.azure.v1 サブプロトコルを使用して Web アプリケーションを作成しましょう。

  1. 依存関係のインストール

    mkdir logstream
    cd logstream
    dotnet new web
    dotnet add package Microsoft.Extensions.Azure
    dotnet add package Azure.Messaging.WebPubSub
    
  2. /negotiate API と Web ページをホストするサーバー側を作成します。

    下のコードを使用して Program.cs を更新します。

    • AddAzureClients を使ってサービス クライアントを追加し、構成から接続文字列を読み取ります。
    • 静的ファイルをサポートするために、app.Run(); の前に app.UseStaticFiles(); を追加します。
    • 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. Web ページの作成

    以下のコンテンツを含む 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. サーバーを実行する

    .NET Core 用の Secret Manager ツールを使用して接続文字列を設定します。 <connection_string> を、前の手順でフェッチされたものに置き換えて下のコマンドを実行し、ブラウザで http://localhost:5000/index.html を開きます。

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

    Chrome を使用している場合は、F12 キーを押すか、右クリックして ->Inspect ->Developer Tools を選択し、[ネットワーク] タブを選択します。Web ページを読み込み、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; }
            }
        }
    }
    
    

    新しい概念 "グループ" がここにあることがわかります。 グループは、接続のグループにメッセージを発行できる、ハブの論理的な概念です。 ハブでは、複数のグループを使用でき、1 つのクライアントは同時に複数のグループをサブスクライブできます。 サブプロトコルを使用する場合には、ハブ全体にブロードキャストするのではなく、グループにのみ発行できます。 用語の詳細については、「基本的な概念」をご覧ください。

  2. ここではグループを使用するため、ws.onopen コールバック内で WebSocket 接続を確立する際に、Web ページ index.html を更新し、グループに参加する必要もあります。

    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 されていることがわかります。

    Startup.csGenerateClientAccessUri で、roles を下のように設定します。

    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 サービスに接続する方法と、サブプロトコルを使用して接続されたクライアントにメッセージを発行する方法の基本的なアイデアを提供します。

サービスの使用方法の詳細については、他のチュートリアルを参照してください。