Обработчики CloudEvents в Azure Web PubSub для Express

Служба Azure Web PubSub — это управляемая Azure служба, которая помогает разработчикам легко создавать веб-приложения с функциями реального времени и шаблоном подписки на публикацию. Любой сценарий, требующий обмена сообщениями публикации и подписки в режиме реального времени между сервером и клиентами или среди клиентов, может использовать службу Azure Web PubSub. Традиционные функции реального времени, которые часто требуют опроса с сервера или отправки HTTP-запросов, также могут использовать службу Azure Web PubSub.

При подключении WebSocket служба Web PubSub преобразует жизненный цикл подключения и сообщения в события в формате CloudEvents. Эта библиотека предоставляет экспресс-ПО промежуточного слоя для обработки событий, представляющих жизненный цикл и сообщения подключения WebSocket, как показано на следующей схеме:

cloudevents

Подробности о используемых здесь терминах описаны в разделе «Ключевые понятия ».

Исходный код | Пакет (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}`),
);

Ключевые понятия

Подключение

Соединение, также известное как клиентское или клиентское соединение, представляет собой отдельное WebSocket-соединение, подключённое к сервису Web PubSub. При успешном подключении сервис Web PubSub присваивает этому соединению уникальный идентификатор соединения.

Хаб

Концентратор — это логическая концепция для набора клиентских подключений. Обычно вы используете один хаб для одной цели, например, чат-хаб или хаб уведомлений. При создании подключения клиента он подключается к концентратору и во время его существования принадлежит к нему. Разные приложения могут совместно использовать одну службу Azure Web PubSub с использованием разных имен центров.

Группа

Группа — это подмножество подключений к концентратору. Клиентское соединение можно добавлять к группе или удалять из нее в любое время. Например, если клиент присоединяется к комнате чата или покидает ее, такую комнату можно рассматривать как группу. Клиент может присоединиться к множеству групп, а группа может содержать множество клиентов.

User

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

События клиента

События создаются в течение жизненного цикла клиентского соединения. Например, простое клиентское соединение WebSocket создаёт connect событие при попытке подключиться к сервису, событие connected при успешном подключении к сервису, 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 запрос для 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}`),
);

Troubleshooting

Включение журналов

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

export AZURE_LOG_LEVEL=verbose

В альтернативном порядке, логирование можно включить во время выполнения, вызвав setLogLevel в @azure/logger:

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

setLogLevel("info");

Дополнительные инструкции по включению журналов см. в документации по пакету @azure/loger.

Интерактивная трассировка

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

Дальнейшие действия

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

Contributing

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