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.
Installation
Pour commencer à créer des applications basées sur WebSockets, installez d’abord le package requis :
$ 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)
où 80
est un numéro de port choisi. Vous pouvez également définir un namespace utilisé par la gateway en utilisant la construction suivante :
@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 :
@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.
@SubscribeMessage('events')handleEvent(@MessageBody() data: string): string { return data;}
Une fois la gateway créée, nous pouvons l’enregistrer dans notre 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 :
@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 :
@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()
.
@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 :
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 :
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.
@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.
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é.
@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 :
Hook | Description |
---|---|
OnGatewayInit | Force à implémenter la méthode afterInit() . Prend une instance de serveur spécifique à la bibliothèque en tant qu’argument. |
OnGatewayConnection | Force à implémenter la méthode handleConnection() . Prend une instance de socket client spécifique à la bibliothèque en tant qu’argument. |
OnGatewayDisconnect | Force à 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()
.
@WebSocketServer()server: Server;
Vous pouvez également récupérer le namespace correspondant en utilisant l’attribut namespace
, comme suit :
@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.