Passer au contenu

Gateways

La plupart des concepts discutés ailleurs dans cette documentation, tels que l’injection de dépendances, les décorateurs, les filtres d’exceptions, les pipes, les gardes et les interceptors, s’appliquent également aux gateways. Dans la mesure du possible, Nest abstrait les détails d’implémentation pour que les mêmes composants puissent fonctionner sur des plateformes basées sur HTTP, WebSockets, et Microservices. Cette section couvre les aspects de Nest qui sont spécifiques aux WebSockets.

Dans Nest, une gateway est simplement une classe annotée avec le décorateur @WebSocketGateway(). Techniquement, les gateways sont indépendantes de la plateforme, ce qui les rend compatibles avec n’importe quelle bibliothèque WebSocket dès qu’un adaptateur est créé. Il existe deux plateformes WS supportées par défaut : socket.io et ws. Vous pouvez choisir celle qui convient le mieux à vos besoins. De plus, vous pouvez créer votre propre adaptateur en suivant ce guide.

Gateways

Installation

Pour commencer à créer des applications basées sur WebSockets, installez d’abord le package requis :

Installation WebSockets
$ npm i --save @nestjs/websockets @nestjs/platform-socket.io

Vue d’ensemble

En général, chaque gateway écoute sur le même port que le serveur HTTP, sauf si votre application n’est pas une application web, ou si vous avez changé le port manuellement. Ce comportement par défaut peut être modifié en passant un argument au décorateur @WebSocketGateway(80)80 est un numéro de port choisi. Vous pouvez également définir un namespace utilisé par la gateway en utilisant la construction suivante :

Définition d'un namespace
@WebSocketGateway(80, { namespace: 'events' })

Vous pouvez passer n’importe quelle option supportée à la construction du socket avec le deuxième argument du décorateur @WebSocketGateway(), comme montré ci-dessous :

Options du socket
@WebSocketGateway(81, { transports: ['websocket'] })

La gateway écoute maintenant, mais nous n’avons pas encore souscrit à aucun message entrant. Créons un gestionnaire qui s’abonnera aux messages events et répondra à l’utilisateur avec les mêmes données.

Gestion de l'événement
@SubscribeMessage('events')
handleEvent(@MessageBody() data: string): string {
return data;
}

Une fois la gateway créée, nous pouvons l’enregistrer dans notre module.

Enregistrement dans le module
@Module({
providers: [EventsGateway],
})
export class EventsModule {}

Vous pouvez également passer une clé de propriété au décorateur pour l’extraire du corps du message entrant :

Extraction de la clé
@SubscribeMessage('events')
handleEvent(@MessageBody('id') id: number): number {
// id === messageBody.id
return id;
}

Si vous préférez ne pas utiliser les décorateurs, le code suivant est fonctionnellement équivalent :

Sans décorateur
@SubscribeMessage('events')
handleEvent(client: Socket, data: string): string {
return data;
}

Dans l’exemple ci-dessus, la fonction handleEvent() prend deux arguments. Le premier est une instance de socket spécifique à la plateforme, tandis que le second est les données reçues du client. Cette approche n’est pas recommandée car elle nécessite de simuler l’instance de socket dans chaque test unitaire.

Lorsque le message events est reçu, le gestionnaire envoie un accusé de réception avec les mêmes données qui ont été envoyées sur le réseau. De plus, il est possible d’émettre des messages en utilisant une approche spécifique à la bibliothèque, par exemple, en utilisant la méthode client.emit().

Émettre un message
@SubscribeMessage('events')
handleEvent(
@MessageBody() data: string,
@ConnectedSocket() client: Socket,
): string {
return data;
}

Cependant, dans ce cas, vous ne pourrez pas tirer parti des interceptors. Si vous ne souhaitez pas répondre à l’utilisateur, vous pouvez simplement ignorer l’instruction return (ou renvoyer explicitement une valeur “falsy”, par exemple undefined).

Maintenant, lorsque client émet un message comme suit :

Emettre un événement
socket.emit('events', { name: 'Nest' });

La méthode handleEvent() sera exécutée. Pour écouter les messages émis à partir du gestionnaire ci-dessus, le client doit attacher un écouteur d’accusé de réception correspondant :

Écouter les réponses
socket.emit('events', { name: 'Nest' }, (data) => {
console.log(data);
});

Réponses multiples

L’accusé de réception est dispatché uniquement une fois. En outre, il n’est pas supporté par l’implémentation native de WebSockets. Pour résoudre cette limitation, vous pouvez retourner un objet qui consiste en deux propriétés : l’événement, qui est un nom de l’événement émis, et les données qui doivent être transférées au client.

Réponses multiples
@SubscribeMessage('events')
handleEvent(@MessageBody() data: unknown): WsResponse<unknown> {
const event = 'events';
return { event, data };
}

Pour écouter les réponses entrantes, le client doit appliquer un autre écouteur d’événements.

Écoute des événements
socket.on('events', (data) => console.log(data));

Réponses asynchrones

Les gestionnaires de messages peuvent répondre soit de manière synchrone, soit asynchrone. Les méthodes async sont donc prises en charge. Un gestionnaire de message peut également retourner un Observable, auquel cas les valeurs de résultats seront émises jusqu’à ce que le flux soit complété.

Gestion d'événements asynchrones
@SubscribeMessage('events')
onEvent(@MessageBody() data: unknown): Observable<WsResponse<number>> {
const event = 'events';
const response = [1, 2, 3];
return from(response).pipe(
map(data => ({ event, data })),
);
}

Dans l’exemple ci-dessus, le gestionnaire de message répondra 3 fois (avec chaque élément du tableau).

Hooks de cycle de vie

Il existe 3 hooks de cycle de vie utiles disponibles. Tous ont des interfaces correspondantes et sont décrits dans le tableau suivant :

HookDescription
OnGatewayInitForce à implémenter la méthode afterInit(). Prend une instance de serveur spécifique à la bibliothèque en tant qu’argument.
OnGatewayConnectionForce à implémenter la méthode handleConnection(). Prend une instance de socket client spécifique à la bibliothèque en tant qu’argument.
OnGatewayDisconnectForce à implémenter la méthode handleDisconnect(). Prend une instance de socket client spécifique à la bibliothèque en tant qu’argument.

Serveur et Namespace

Parfois, vous pouvez vouloir avoir un accès direct à l’instance de serveur spécifique à la plateforme native. La référence à cet objet est passée en tant qu’argument à la méthode afterInit() (interface OnGatewayInit). Une autre option est d’utiliser le décorateur @WebSocketServer().

Serveur WebSocket
@WebSocketServer()
server: Server;

Vous pouvez également récupérer le namespace correspondant en utilisant l’attribut namespace, comme suit :

Namespace
@WebSocketServer({ namespace: 'my-namespace' })
namespace: Namespace;

Le décorateur @WebSocketServer() est importé du package @nestjs/websockets.

Nest attribuera automatiquement l’instance du serveur à cette propriété une fois qu’elle sera prête à être utilisée.

Exemple

Un exemple fonctionnel est disponible ici.