共用方式為


教學課程:使用 Azure Web PubSub 服務建立聊天應用程式

發佈和訂閱訊息教學課程中,您會了解使用 Azure Web PubSub 發佈和訂閱訊息的基本概念。 在本教學課程中,您將瞭解 Azure Web PubSub 的事件系統,並用它來建置具有即時通訊功能的完整 Web 應用程式。

在本教學課程中,您會了解如何:

  • 建立 Web PubSub 服務執行個體
  • 設定 Azure Web PubSub 的事件處理常式設定
  • 處理應用程式伺服器中的事件,並建置即時聊天應用程式

如果您沒有 Azure 訂閱,請在開始之前,先建立 Azure 免費帳戶

必要條件

  • 此設定需要 2.22.0 版或更新版本的 Azure CLI。 如果您是使用 Azure Cloud Shell,就已安裝最新版本。

建立 Azure Web PubSub 執行個體

建立資源群組

資源群組是在其中部署與管理 Azure 資源的邏輯容器。 使用 az group create 命令,在 eastus 位置中建立名為 myResourceGroup 的資源群組。

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 資源都必須有唯一的名稱。 使用下列範例中的 Web PubSub 名稱取代 <your-unique-resource-name>。

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 Web PubSub 服務所需的授權資訊。 連接字串內的存取金鑰類似於服務的根密碼。 在生產環境中,請一律保護您的存取金鑰。 使用 Azure 金鑰保存庫,安全地管理和輪替密鑰,並使用保護連線WebPubSubServiceClient

避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。

使用 Azure CLI az webpubsub key 命令取得服務的 ConnectionString。 使用您的 Azure Web PubSub 執行個體名稱取代 <your-unique-resource-name> 預留位置。

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

複製連接字串以供後續使用。

複製擷取的 ConnectionString ,並將其設定為環境變數 WebPubSubConnectionString,本教學課程稍後會加以讀取。 將下方取代 <connection-string> 為您擷取的 ConnectionString

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

設定專案

必要條件

建立應用程式

Azure Web PubSub 中有兩個角色:伺服器和用戶端。 此概念類似於 Web 應用程式中的伺服器和用戶端角色。 伺服器負責管理用戶端、接聽及回應用戶端訊息。 用戶端負責傳送和接收來自伺服器的使用者訊息,並將其呈現給終端使用者。

在此教學課程中,我們會建置即時聊天 Web 應用程式。 在實際的 Web 應用程式中,伺服器的責任也包括驗證用戶端,以及為應用程式 UI 提供靜態網頁。

我們使用 ASP.NET Core 8 來裝載網頁並處理傳入要求。

首先,讓我們在資料夾中建立 ASP.NET Core Web 應用程式 chatapp

  1. 建立新的 Web 應用程式。

    mkdir chatapp
    cd chatapp
    dotnet new web
    
  2. 新增 app.UseStaticFiles() Program.cs以支援裝載靜態網頁。

    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    app.UseStaticFiles();
    
    app.Run();
    
  3. 建立 HTML 檔案,並將其儲存為 wwwroot/index.html,我們稍後會將其用於聊天應用程式的 UI。

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

您可以在瀏覽器中執行 dotnet run --urls http://localhost:8080 和存取 http://localhost:8080/index.html 來測試伺服器。

新增交涉端點

在發佈和訂閱訊息教學課程中,訂閱者會直接取用 連接字串。 在真實世界應用程式中,與任何用戶端共用 連接字串 並不安全,因為 連接字串 具有對服務執行任何作業的高許可權。 現在,讓我們讓伺服器取用 連接字串,並公開negotiate用戶端的端點,以取得具有存取令牌的完整 URL。 如此一來,伺服器就可以在端點之前 negotiate 新增驗證中間件,以防止未經授權的存取。

請先安裝相依性。

dotnet add package Microsoft.Azure.WebPubSub.AspNetCore

現在讓我們新增 /negotiate 端點,讓用戶端呼叫 以產生令牌。

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>() 用來插入服務用戶端 WebPubSubServiceClient<THub>,藉此我們可以在交涉步驟中產生用戶端連線權杖,以及在中樞事件觸發時以中樞方法叫用服務 REST API。 此權杖產生程式碼類似於我們在發佈和訂閱訊息教學課程中使用的程式碼,差別在於,產生權杖時會多傳遞一個引數 (userId)。 使用者識別碼可用來識別用戶端的身分識別,讓您在收到訊息時能知道訊息來自何處。

程序代碼會從我們在上一個步驟設定的環境變數WebPubSubConnectionString讀取 連接字串。

使用 dotnet run --urls http://localhost:8080重新執行伺服器。

您可以藉由存取 http://localhost:8080/negotiate?id=user1 來測試此 API,並提供 Azure Web PubSub 的完整 URL 與存取令牌。

處理事件

在 Azure Web PubSub 中,當用戶端發生某些活動時(例如用戶端正在連線、連線、中斷連線或用戶端正在傳送訊息),服務會將通知傳送至伺服器,以便回應這些事件。

事件會以 Webhook 的形式傳遞至伺服器。 Webhook 由應用程式伺服器提供並公開,並註冊於 Azure Web PubSub 服務端。 每當有事件發生時,服務就會叫用 Webhook。

Azure Web PubSub 會依照 CloudEvents 來描述事件資料。

以下我們會在用戶端連線時處理 connected 系統事件,並在用戶端傳送訊息以建置聊天應用程式時處理 message 使用者事件。

我們在上一個步驟中安裝的適用於 AspNetCore Microsoft.Azure.WebPubSub.AspNetCore 的 Web PubSub SDK 也可以協助剖析及處理 CloudEvents 要求。

首先,在 之前 app.Run()新增事件處理程式。 指定事件的端點路徑,假設是 /eventhandler

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

現在,在我們在上一個步驟中建立的類別 Sample_ChatApp 內,新增建構函式來使用 WebPubSubServiceClient<Sample_ChatApp> ,以便用來叫用Web PubSub服務。 以及在 OnConnectedAsync() 觸發事件時 connected 回應, OnMessageReceivedAsync() 以處理來自用戶端的訊息。

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

在上述程式代碼中,我們會使用服務用戶端,以 JSON 格式廣播通知訊息給所有已加入 SendToAllAsync的通知訊息。

更新網頁

現在讓我們更新 index.html 以新增邏輯,以連線、傳送訊息,以及在頁面中顯示已接收的訊息。

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

您可以在上述程式代碼中看到,我們在瀏覽器中使用原生 WebSocket API,並使用 WebSocket.send() 來傳送訊息和 WebSocket.onmessage 接聽已接收的訊息。

您也可以使用 用戶端 SDK 來連線到服務,讓您能夠自動重新連線、錯誤處理等等。

現在還有一個步驟可供聊天運作。 讓我們設定我們關心的事件,以及在 Web PubSub 服務中將事件傳送至何處。

設定事件處理常式

我們會在 Web PubSub 服務中設定事件處理程式,以告知服務將事件傳送至何處。

當 Web 伺服器在本機執行時,如果 Web PubSub 服務沒有可存取因特網的端點,如何叫用 localhost? 通常有兩種方式。 其中一個是使用一些一般通道工具向公用公開 localhost,另一個是使用 wps 通道 ,透過工具將來自 Web PubSub 服務的流量通道傳送至您的本地伺服器。

在本節中,我們會使用 Azure CLI 來設定事件處理程式,並使用 wps 通道 將流量路由傳送至 localhost。

設定中樞設定

我們將URL範本設定為使用 tunnel 配置,讓Web PubSub透過 awps-tunnel通道連線路由傳送訊息。 如本文所述,事件處理常式可以從入口網站或 CLI 設定,在此我們透過 CLI 來設定。 因為我們在上一個步驟設定時接聽路徑 /eventhandler 中的事件,所以我們將URL範本設定為 tunnel:///eventhandler

使用 Azure CLI az webpubsub hub create 命令來建立中樞的 Sample_ChatApp 事件處理程式設定。

重要

將 <your-unique-resource-name> 取代為從先前的步驟建立的 Web PubSub 資源名稱。

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"

在本機執行 awps-tunnel

下載並安裝 awps-tunnel

此工具執行於 Node.js 16 版或更新版本上。

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

使用服務連接字串並執行

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

執行網頁伺服器

現在一切都已設定。 讓我們執行網頁伺服器,並搭配聊天應用程式運作。

現在使用 dotnet run --urls http://localhost:8080執行伺服器。

您可以在這裡找到此教學課程的完整程式碼範例。

開啟 [http://localhost:8080/index.html]。 您可以輸入您的使用者名稱並開始聊天。

具有事件處理程式的 connect 延遲驗證

在上一節中,我們將示範如何使用 交涉 端點來傳回 Web PubSub 服務 URL,以及用戶端連線至 Web PubSub 服務的 JWT 存取令牌。 例如,在某些情況下,具有有限資源的邊緣裝置,用戶端可能偏好直接連線到 Web PubSub 資源。 在這種情況下,您可以設定 connect 事件處理程式來延遲驗證用戶端、將使用者標識碼指派給用戶端、指定客戶端在連線後加入的群組、設定用戶端擁有的許可權,以及將 WebSocket 子程式設定為用戶端的 WebSocket 回應等等。詳細數據請參閱 連線事件處理程式規格

現在,讓我們使用connect事件處理程式來達成與交涉區段所做的類似

更新中樞設定

首先,讓我們更新中樞設定以同時包含 connect 事件處理程式,我們也必須允許匿名連線,讓沒有 JWT 存取令牌的用戶端可以連線到服務。

使用 Azure CLI az webpubsub hub update 命令來建立中樞的 Sample_ChatApp 事件處理程式設定。

重要

將 <your-unique-resource-name> 取代為從先前的步驟建立的 Web PubSub 資源名稱。

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"

更新上游邏輯以處理連線事件

現在讓我們更新上游邏輯來處理連線事件。 我們現在可以移除交涉端點。

如同我們在交涉端點中做為示範用途,我們也從查詢參數讀取標識符。 在 connect 事件中,原始客戶端查詢會保留在 connect 事件要求本文中。

在類別 Sample_ChatApp內,覆寫 OnConnectAsync() 以處理 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();
    }
}

更新index.html以直接連線

現在讓我們更新網頁,以直接連線到 Web PubSub 服務。 其中一件事是,現在為了示範目的,Web PubSub 服務端點會硬式編碼到用戶端程序代碼中,請使用您自己的服務值更新下列 html 中的服務主機名 <the host name of your service> 。 從您的伺服器擷取 Web PubSub 服務端點值可能仍然很有用,它可讓您更有彈性且可控制用戶端連線的位置。

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

重新執行伺服器

現在 ,請重新執行伺服器 ,並依照先前的指示瀏覽網頁。 如果您已 awps-tunnel停止 ,請同時 重新執行通道工具

下一步

本教學課程提供關於事件系統在 Azure Web PubSub 服務中如何運作的基本概念。

請參考其他教學課程,進一步探討如何使用此服務。