Passer au contenu

Adaptateurs WebSocket

Le module WebSockets est agnostique à la plateforme, vous pouvez donc apporter votre propre bibliothèque (ou même une implémentation native) en utilisant l’interface WebSocketAdapter. Cette interface impose d’implémenter quelques méthodes décrites dans le tableau suivant :

MéthodeDescription
createCrée une instance de socket basée sur les arguments passés
bindClientConnectLie l’événement de connexion du client
bindClientDisconnectLie l’événement de déconnexion du client (facultatif*)
bindMessageHandlersLie le message entrant au gestionnaire de messages correspondant
closeTermine une instance de serveur

Étendre socket.io

Le package socket.io est encapsulé dans une classe IoAdapter. Que se passerait-il si vous souhaitiez améliorer la fonctionnalité de base de l’adaptateur ? Par exemple, vos exigences techniques nécessitent une capacité de diffusion d’événements à travers plusieurs instances équilibrées du service web. Pour cela, vous pouvez étendre IoAdapter et remplacer une seule méthode dont la responsabilité est d’instancier de nouveaux serveurs socket.io. Mais d’abord, installons le package requis.

Fenêtre de terminal
$ npm i --save redis socket.io @socket.io/redis-adapter

Une fois le package installé, nous pouvons créer une classe RedisIoAdapter.

RedisIoAdapter
import { IoAdapter } from '@nestjs/platform-socket.io';
import { ServerOptions } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
export class RedisIoAdapter extends IoAdapter {
private adapterConstructor: ReturnType<typeof createAdapter>;
async connectToRedis() {
const pubClient = createClient({ url: `redis://localhost:6379` });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
this.adapterConstructor = createAdapter(pubClient, subClient);
}
createIOServer(port: number, options?: ServerOptions) {
const server = super.createIOServer(port, options);
server.adapter(this.adapterConstructor);
return server;
}
}

Ensuite, changez simplement pour votre nouvel adaptateur Redis.

main.ts
const app = await NestFactory.create(AppModule);
const redisIoAdapter = new RedisIoAdapter(app);
await redisIoAdapter.connectToRedis();
app.useWebSocketAdapter(redisIoAdapter);

Bibliothèque Ws

Un autre adaptateur disponible est un WsAdapter qui agit comme un proxy entre le framework et la bibliothèque ws qui est rapide et soigneusement testée. Cet adaptateur est pleinement compatible avec les WebSockets natifs du navigateur et est beaucoup plus rapide que le package socket.io. Malheureusement, il dispose de fonctionnalités significativement moins nombreuses disponibles en standard. Dans certains cas, vous n’en aurez peut-être pas nécessairement besoin.

Pour utiliser ws, nous devons d’abord installer le package requis :

Fenêtre de terminal
$ npm i --save @nestjs/platform-ws

Une fois le package installé, nous pouvons changer d’adaptateur :

main.ts
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));

Avancé (adaptateur personnalisé)

À des fins de démonstration, nous allons intégrer la bibliothèque ws manuellement. Comme mentionné, l’adaptateur pour cette bibliothèque est déjà créé et est exposé à partir du package @nestjs/platform-ws sous la forme d’une classe WsAdapter. Voici à quoi pourrait ressembler l’implémentation simplifiée :

ws-adapter.ts
import * as WebSocket from 'ws';
import { WebSocketAdapter, INestApplicationContext } from '@nestjs/common';
import { MessageMappingProperties } from '@nestjs/websockets';
import { Observable, fromEvent, EMPTY } from 'rxjs';
import { mergeMap, filter } from 'rxjs/operators';
export class WsAdapter implements WebSocketAdapter {
constructor(private app: INestApplicationContext) {}
create(port: number, options: any = {}) {
return new WebSocket.Server({ port, ...options });
}
bindClientConnect(server, callback: Function) {
server.on('connection', callback);
}
bindMessageHandlers(
client: WebSocket,
handlers: MessageMappingProperties[],
process: (data: any) => Observable<any>,
) {
fromEvent(client, 'message')
.pipe(
mergeMap(data => this.bindMessageHandler(data, handlers, process)),
filter(result => result),
)
.subscribe(response => client.send(JSON.stringify(response)));
}
bindMessageHandler(buffer, handlers: MessageMappingProperties[], process: (data: any) => Observable<any>): Observable<any> {
const message = JSON.parse(buffer.data);
const messageHandler = handlers.find(handler => handler.message === message.event);
if (!messageHandler) {
return EMPTY;
}
return process(messageHandler.callback(message.data));
}
close(server) {
server.close();
}
}

Ensuite, nous pouvons configurer un adaptateur personnalisé en utilisant la méthode useWebSocketAdapter() :

main.ts
const app = await NestFactory.create(AppModule);
app.useWebSocketAdapter(new WsAdapter(app));

Exemple

Un exemple fonctionnel qui utilise WsAdapter est disponible ici.