Azure Web PubSub CloudEvents handlers for Express

Azure Web PubSub 服務 是一項 Azure 管理服務,幫助開發者輕鬆建置具備即時功能及發佈-訂閱模式的網頁應用程式。 任何需要伺服器與用戶端之間即時發佈-訂閱訊息,或用戶端間的情境,都可以使用 Azure Web PubSub 服務。 傳統的即時功能通常需要從伺服器輪詢或提交 HTTP 請求,也可以使用 Azure Web PubSub 服務。

當 WebSocket 連線連接時,Web PubSub 服務會將連線生命週期與訊息轉換為 CloudEvents 格式的事件。 此函式庫提供一個快速中介軟體,用以處理代表 WebSocket 連線生命週期與訊息的事件,如下圖所示:

雲事件

關於此處所用術語的細節,請參閱 關鍵概念 章節。

原始碼 | 封裝(NPM) | API 參考文件 | 產品文件 | 取樣

入門指南

目前支援的環境

先決條件

1. 安裝@azure/web-pubsub-express套件

npm install @azure/web-pubsub-express

2. 建立 WebPubSubEventHandler

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat");

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

關鍵概念

連線

連線,也稱為用戶端或用戶端連線,代表連接到 Web PubSub 服務的單一 WebSocket 連線。 成功連接後,Web PubSub 服務會為此連線指派一個獨特的連接 ID。

樞紐

中樞是一組用戶端連線的邏輯概念。 通常你會用一個中樞來做一個目的,例如聊天中樞或通知中樞。 建立用戶端連線時,它會連線到中樞,並在其存留期間屬於該中樞。 不同的應用程式可以使用不同的中樞名稱來共用一個 Azure Web PubSub 服務。

群組

群組是中樞連線的子集。 您可以隨時將用戶端連線新增至某個群組,或從群組中移除用戶端連線。 例如,當用戶端加入聊天室時,或當用戶端離開聊天室時,便可將此聊天室視為群組。 用戶端可以加入多個群組,群組則可以包含多個用戶端。

User

Web PubSub 的連線可以屬於一個使用者。 使用者可能會有多個連線,例如,當單一使用者跨多個裝置或多個瀏覽器索引標籤連線時。

客戶活動

事件是在用戶端連線的生命週期中產生的。 例如,一個簡單的 WebSocket 用戶端連線在嘗試連接服務時會產生事件,成功連接服務時會產生connectconnected事件,發送訊息給服務時會產生message事件,當它與服務斷開連線時也會產生disconnected事件。

事件處理程式

事件處理程序包含處理客戶端事件的邏輯。 事件處理程式必須事先透過入口網站或 Azure CLI 註冊並設定。 通常,事件處理邏輯的託管地點被視為伺服器端。

範例

處理請求並 connect 指派 <userId>

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  handleConnect: (req, res) => {
    // auth the connection and set the userId of the connection
    res.success({
      userId: "<userId>",
    });
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理 connect 請求,若驗證失敗則拒絕連線

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  handleConnect: (req, res) => {
    // auth the connection and reject the connection if auth failed
    res.fail(401, "Unauthorized");
    // the following method is also a valid approach
    // res.failWith({ code: 401, detail: "Unauthorized" });
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理這個 connected 請求

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  onConnected: (connectedRequest) => {
    // Your onConnected logic goes here
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理這個 onGroupJoined 請求

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  onGroupJoined: (groupJoinedRequest) => {
    console.log(
      `Connection ${groupJoinedRequest.context.connectionId} joined group ${groupJoinedRequest.group}`,
    );
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理這個 onGroupLeft 請求

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  onGroupLeft: (groupLeftRequest) => {
    console.log(
      `Connection ${groupLeftRequest.context.connectionId} left group ${groupLeftRequest.group}`,
    );
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理這個 onDisconnected 請求

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  onDisconnected: (disconnectedRequest) => {
    // Your onDisconnected logic goes here
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理 connect mqtt 請求,並指派 <userId><mqtt> 屬性

import { WebPubSubEventHandler, MqttConnectRequest } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  handleConnect: (req, res) => {
    if (req.context.clientProtocol === "mqtt") {
      // return mqtt response when request is of MQTT kind
      // get connect request as mqtt request and print it
      const mqttRequest = req as MqttConnectRequest;
      console.log(mqttRequest);

      // auth the connection and return mqtt response
      res.success({
        userId: "user1",
        mqtt: { userProperties: [{ name: "a", value: "b" }] },
      });
    } else {
      res.success({
        userId: "user1",
      });
    }
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理 connect mqtt 請求,若驗證失敗則拒絕連線

import { WebPubSubEventHandler, MqttConnectRequest } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  handleConnect: (req, res) => {
    // auth the connection and reject the connection if auth failed
    if (req.context.clientProtocol === "mqtt") {
      // return mqtt error response when request is of MQTT kind
      // get connect request as mqtt request and print it
      const mqttRequest = req as MqttConnectRequest;
      console.log(mqttRequest);

      // auth the connection and return mqtt failure response
      res.fail(401, "Not Authorized");

      // Or use below method for more fine-grained control over the MQTT return code
      // res.failWith({ mqtt: { code: MqttV500ConnectReasonCode.NotAuthorized } });
    } else res.success();
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

處理 onDisconnected for mqtt 請求

import { WebPubSubEventHandler, MqttDisconnectedRequest } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  onDisconnected: (disconnectedRequest) => {
    if (disconnectedRequest.context.clientProtocol === "mqtt") {
      // get disconnect request as mqtt request and print it
      const mqttRequest = disconnectedRequest as MqttDisconnectedRequest;
      console.log(mqttRequest.mqtt);
      // Your onDisconnected logic goes here
    } else {
      console.log(disconnectedRequest);
      // Your onDisconnected logic goes here
    }
  },
  allowedEndpoints: ["https://<yourAllowedService>.webpubsub.azure.com"],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

只允許指定的端點

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  allowedEndpoints: [
    "https://<yourAllowedService1>.webpubsub.azure.com",
    "https://<yourAllowedService2>.webpubsub.azure.com",
  ],
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

設定自訂事件處理路徑

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  path: "/customPath1",
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  // Azure WebPubSub Upstream ready at http://localhost:3000/customPath1
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

設定並讀取連線狀態

import { WebPubSubEventHandler } from "@azure/web-pubsub-express";
import express from "express";

const handler = new WebPubSubEventHandler("chat", {
  handleConnect(req, res) {
    // You can set the state for the connection, it lasts throughout the lifetime of the connection
    res.setState("calledTime", 1);
    res.success();
  },
  handleUserEvent(req, res) {
    const calledTime = req.context.states.calledTime++;
    console.log(calledTime);
    // You can also set the state here
    res.setState("calledTime", calledTime);
    res.success();
  },
});

const app = express();

app.use(handler.getMiddleware());

app.listen(3000, () =>
  console.log(`Azure WebPubSub Upstream ready at http://localhost:3000${handler.path}`),
);

故障排除

啟用日誌

啟用記錄可能有助於找出有關失敗的實用資訊。 若要查看 HTTP 要求和回應的記錄,請將 AZURE_LOG_LEVEL 環境變數設定為 info

export AZURE_LOG_LEVEL=verbose

或者,執行時也可以透過呼叫 setLogLevel 中的 @azure/logger 來啟用日誌:

import { setLogLevel } from "@azure/logger";

setLogLevel("info");

如需如何啟用記錄的詳細指示,請參閱@azure/記錄器套件檔。

即時追蹤

請使用Web PubSub服務入口網站的 即時追蹤(Live Trace )查看即時流量。

下一步

請參考 samples 目錄,裡面有詳細的使用範例。

Contributing

如果你想為這個函式庫貢獻,請閱讀 contributing guide,了解更多如何建置與測試程式碼。