Azure Web PubSub CloudEvents handlers for Express

Usługa Azure Web PubSub to usługa zarządzana przez platformę Azure, która ułatwia deweloperom łatwe tworzenie aplikacji internetowych przy użyciu funkcji w czasie rzeczywistym i wzorca publikowania-subskrybowania. Każdy scenariusz, który wymaga komunikatów publikowania i subskrybowania w czasie rzeczywistym między serwerem a klientami lub między klientami, może korzystać z usługi Azure Web PubSub. Tradycyjne funkcje w czasie rzeczywistym, które często wymagają sondowania z serwera lub przesyłania żądań HTTP, mogą również używać usługi Azure Web PubSub.

Po nawiązaniu połączenia protokołu WebSocket usługa Web PubSub przekształca cykl życia połączenia i komunikaty w zdarzenia w formacie CloudEvents. Ta biblioteka udostępnia ekspresowe oprogramowanie pośredniczące do obsługi zdarzeń reprezentujących cykl życia i komunikaty połączenia Protokołu WebSocket, jak pokazano na poniższym diagramie:

Cloudevents

Szczegóły dotyczące używanych tutaj terminów opisano w sekcji Kluczowe pojęcia .

Kod źródłowy | Pakiet (NPM) | Dokumentacja referencyjna interfejsu API | Dokumentacja produktu | Przykłady

Rozpoczęcie pracy

Obecnie obsługiwane środowiska

  • Wersje LTS systemu Node.js
  • Express w wersji 4.x.x lub nowszej

Wymagania wstępne

1. Zainstaluj @azure/web-pubsub-express pakiet

npm install @azure/web-pubsub-express

2. Utwórz element 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}`),
);

Kluczowe pojęcia

Połączenie

Połączenie, znane również jako klient lub połączenie klientskie, reprezentuje pojedyncze połączenie WebSocket połączone z usługą Web PubSub. Po pomyślnym połączeniu usługa Web PubSub przypisuje mu unikalny identyfikator połączenia.

Hub

Koncentrator to logiczne pojęcie dla zestawu połączeń klientów. Zazwyczaj używa się jednego huba do jednego celu, na przykład chathuba lub centrum powiadomień. Po utworzeniu połączenia klienta łączy się z koncentratorem i w okresie jego istnienia należy do tego centrum. Różne aplikacje mogą udostępniać jedną usługę Azure Web PubSub przy użyciu różnych nazw centrów.

Grupa

Grupa jest podzbiorem połączeń z koncentratorem. Możesz dodać połączenie klienta do grupy lub usunąć połączenie klienta z grupy w dowolnym momencie. Na przykład gdy klient dołącza do pokoju rozmów lub gdy klient opuszcza pokój rozmów, ten pokój rozmów może być uważany za grupę. Klient może dołączyć do wielu grup, a grupa może zawierać wielu klientów.

User

Połączenia z usługą Web PubSub mogą należeć do jednego użytkownika. Użytkownik może mieć wiele połączeń, na przykład gdy jeden użytkownik jest połączony z wieloma urządzeniami lub wieloma kartami przeglądarki.

Zdarzenia klienta

Zdarzenia powstają w trakcie cyklu życia połączenia klienta. Na przykład proste połączenie klienta WebSocket tworzy connect zdarzenie, gdy próbuje połączyć się z usługą, zdarzenie, connected gdy połączenie się z usługą pomyślnie się łączy, zdarzenie message wysyłające wiadomości do usługi oraz disconnected zdarzenie po rozłączeniu się z usługą.

Obsługiwacz zdarzeń

Obsługa zdarzeń zawiera logikę do obsługi zdarzeń klientów. Obsługa zdarzeń musi być zarejestrowana i skonfigurowana w usłudze przez portal lub Azure CLI wcześniej. Miejsce, gdzie można hostować logikę obsługi zdarzeń, jest zazwyczaj po stronie serwera.

Przykłady

Obsłużyć connect żądanie i przypisać <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}`),
);

Obsłuż żądanie i odrzuć connect połączenie, jeśli uwierzytelnienie się nie powiodło

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}`),
);

Obsługuj prośbę 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}`),
);

Obsługuj prośbę 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}`),
);

Obsługuj prośbę 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}`),
);

Obsługuj prośbę 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}`),
);

Obsłuży żądanie mqtt oraz przypisz connect<userId> i <mqtt> właściwości

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}`),
);

Obsłużyć żądanie mqtt i odrzucić connect połączenie, jeśli uwierzytelnienie się nie powiodło

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}`),
);

Obsłuż żądanie 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}`),
);

Zezwalaj tylko na określone punkty końcowe

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}`),
);

Ustaw niestandardową ścieżkę obsługi zdarzeń

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}`),
);

Ustawianie i odczytywanie stanu połączenia

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

Włącz dzienniki

Włączenie rejestrowania może pomóc odkryć przydatne informacje o błędach. Aby wyświetlić dziennik żądań i odpowiedzi HTTP, ustaw zmienną środowiskową AZURE_LOG_LEVEL na info.

export AZURE_LOG_LEVEL=verbose

Alternatywnie logowanie można włączyć w czasie działania, wywołując setLogLevel w @azure/logger:

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

setLogLevel("info");

Aby uzyskać bardziej szczegółowe instrukcje dotyczące włączania dzienników, zapoznaj się z dokumentacją pakietu @azure/logger.

Śledzenie w czasie rzeczywistym

Użyj funkcji Live Trace z portalu usługi Web PubSub, aby wyświetlić ruch na żywo.

Następne kroki

Proszę zajrzyj do katalogu samples, gdzie znajdziesz szczegółowe przykłady korzystania z tej biblioteki.

Contributing

Jeśli chcesz przyczynić się do tej biblioteki, przeczytaj przewodnik wkład aby dowiedzieć się więcej o budowaniu i testowaniu kodu.