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

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

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

  • 建立 Web PubSub 服務實例
  • 設定 Azure Web PubSub 的事件處理程式設定
  • 應用程式伺服器中的 Hanlde 事件,並建置即時聊天應用程式

如果您沒有 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 資源都必須有唯一的名稱。 在下列範例中,將 <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 帳戶是唯一獲得授權在此新資源上執行任何作業的帳戶。

取得 連線 ionString 以供日後使用

重要

連接字串 包含應用程式存取 Azure Web PubSub 服務所需的授權資訊。 連接字串內的存取金鑰類似於您服務的根密碼。 在生產環境中,請務必小心保護您的存取密鑰。 使用 Azure Key Vault,以安全的方式管理及輪替金鑰。 避免將存取金鑰散發給其他使用者、寫入程式碼,或將其以純文字儲存在他人可以存取的位置。 如果您認為金鑰可能已遭盜用,請輪替金鑰。

使用 Azure CLI az webpubsub key 命令來取得服務的 連線 ionString。 將 <your-unique-resource-name> 佔位元取代為您的 Azure Web PubSub 實例名稱。

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

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

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

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

下載並安裝wps-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 服務中運作方式的基本概念。

請查看其他教學課程,以進一步探討如何使用服務。