UNPKG

@nestjs/microservices

Version:

Nest - modern, fast, powerful node.js web framework (@microservices)

175 lines (174 loc) 5.93 kB
import { Logger } from '@nestjs/common'; import * as net from 'net'; import { EmptyError, lastValueFrom } from 'rxjs'; import { share, tap } from 'rxjs/operators'; import { connect as tlsConnect } from 'tls'; import { ECONNREFUSED, TCP_DEFAULT_HOST, TCP_DEFAULT_PORT, } from '../constants.js'; import { JsonSocket } from '../helpers/index.js'; import { ClientProxy } from './client-proxy.js'; /** * @publicApi */ export class ClientTCP extends ClientProxy { logger = new Logger(ClientTCP.name); port; host; socketClass; tlsOptions; maxBufferSize; socket = null; connectionPromise = null; pendingEventListeners = []; constructor(options) { super(); this.port = this.getOptionsProp(options, 'port', TCP_DEFAULT_PORT); this.host = this.getOptionsProp(options, 'host', TCP_DEFAULT_HOST); this.socketClass = this.getOptionsProp(options, 'socketClass', JsonSocket); this.tlsOptions = this.getOptionsProp(options, 'tlsOptions'); this.maxBufferSize = this.getOptionsProp(options, 'maxBufferSize'); this.initializeSerializer(options); this.initializeDeserializer(options); } connect() { if (this.connectionPromise) { return this.connectionPromise; } this.socket = this.createSocket(); this.registerConnectListener(this.socket); this.registerCloseListener(this.socket); this.registerErrorListener(this.socket); this.pendingEventListeners.forEach(({ event, callback }) => this.socket.on(event, callback)); this.pendingEventListeners = []; const source$ = this.connect$(this.socket.netSocket).pipe(tap(() => { this.socket.on('message', (buffer) => this.handleResponse(buffer)); }), share()); // For TLS connections, the connection is initiated when the socket is created if (!this.tlsOptions) { this.socket.connect(this.port, this.host); } this.connectionPromise = lastValueFrom(source$).catch(err => { if (err instanceof EmptyError) { return; } throw err; }); return this.connectionPromise; } async handleResponse(buffer) { const { err, response, isDisposed, id } = await this.deserializer.deserialize(buffer); const callback = this.routingMap.get(id); if (!callback) { return undefined; } if (isDisposed || err) { return callback({ err, response, isDisposed: true, }); } callback({ err, response, }); } createSocket() { let socket; /** * TLS enabled, "upgrade" the TCP Socket to TLS */ if (this.tlsOptions) { socket = tlsConnect({ ...this.tlsOptions, port: this.port, host: this.host, }); } else { socket = new net.Socket(); } // Pass maxBufferSize only if socketClass is JsonSocket // For custom socket classes, users should handle maxBufferSize in their own implementation if (this.maxBufferSize !== undefined && this.socketClass === JsonSocket) { return new this.socketClass(socket, { maxBufferSize: this.maxBufferSize, }); } return new this.socketClass(socket); } close() { this.socket && this.socket.end(); this.handleClose(); this.pendingEventListeners = []; } registerConnectListener(socket) { socket.on("connect" /* TcpEventsMap.CONNECT */, () => { this._status$.next("connected" /* TcpStatus.CONNECTED */); }); } registerErrorListener(socket) { socket.on("error" /* TcpEventsMap.ERROR */, err => { if (err.code !== ECONNREFUSED) { this.handleError(err); } else { this._status$.next("disconnected" /* TcpStatus.DISCONNECTED */); } }); } registerCloseListener(socket) { socket.on("close" /* TcpEventsMap.CLOSE */, () => { this._status$.next("disconnected" /* TcpStatus.DISCONNECTED */); this.handleClose(); }); } handleError(err) { this.logger.error(err); } handleClose() { this.socket = null; this.connectionPromise = null; if (this.routingMap.size > 0) { const err = new Error('Connection closed'); for (const callback of this.routingMap.values()) { callback({ err }); } this.routingMap.clear(); } } on(event, callback) { if (this.socket) { this.socket.on(event, callback); } else { this.pendingEventListeners.push({ event, callback }); } } unwrap() { if (!this.socket) { throw new Error('Not initialized. Please call the "connect" method first.'); } return this.socket.netSocket; } publish(partialPacket, callback) { try { const packet = this.assignPacketId(partialPacket); const serializedPacket = this.serializer.serialize(packet); this.routingMap.set(packet.id, callback); this.socket.sendMessage(serializedPacket); return () => this.routingMap.delete(packet.id); } catch (err) { callback({ err }); return () => { }; } } async dispatchEvent(packet) { const pattern = this.normalizePattern(packet.pattern); const serializedPacket = this.serializer.serialize({ ...packet, pattern, }); return this.socket.sendMessage(serializedPacket); } }