UNPKG

@stoplight/moleculer

Version:

Fast & powerful microservices framework for Node.JS

216 lines (177 loc) 4.77 kB
/* * moleculer * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer) * MIT Licensed */ "use strict"; const net = require("net"); const EventEmitter = require("events"); const { MoleculerError } = require("../../errors"); const { PACKET_GOSSIP_REQ_ID, PACKET_GOSSIP_RES_ID, PACKET_GOSSIP_HELLO_ID } = require("./constants"); const HEADER_SIZE = 6; /** * TCP Writer for TcpTransporter * * @class TcpWriter * @extends {EventEmitter} */ class TcpWriter extends EventEmitter { /** * Creates an instance of TcpWriter. * * @param {any} transporter * @param {any} opts * @memberof TcpWriter */ constructor(transporter, opts) { super(); this.sockets = new Map(); this.opts = opts; this.transporter = transporter; this.Promise = transporter.broker.Promise; this.logger = transporter.logger; } /** * Connect to a remote node * * @param {String} nodeID */ connect(nodeID) { const node = this.transporter.getNode(nodeID); if (!node) return this.Promise.reject(new MoleculerError(`Missing node info for '${nodeID}'!`)); const host = this.transporter.getNodeAddress(node); const port = node.port; this.logger.debug(`Connecting to '${nodeID}' via ${host}:${port}`); return new this.Promise((resolve, reject) => { try { const socket = net.connect({ host, port }, () => { socket.nodeID = nodeID; socket.lastUsed = Date.now(); socket.setNoDelay(true); this.addSocket(nodeID, socket, true); this.logger.debug(`Connected successfully to '${nodeID}'.`); // Handle racing problem, we send first a HELLO packet with our connection info this.transporter .sendHello(nodeID) .then(() => resolve(socket)) .catch(err => reject(err)); if (this.sockets.size > this.opts.maxConnections) this.manageConnections(); }); socket.on("error", err => { this.removeSocket(nodeID); this.emit("error", err, nodeID); if (reject) reject(err); }); socket.on("end", () => { this.removeSocket(nodeID); this.emit("end", nodeID); if (reject) reject(new Error("Connection closed")); }); socket.unref(); } catch (err) { if (reject) reject(err); } }); } /** * Send a message to a remote node * * @param {String} nodeID * @param {Number} type * @param {Buffer} data */ send(nodeID, type, data) { return this.Promise.resolve() .then(() => { let socket = this.sockets.get(nodeID); if (socket && !socket.destroyed) return socket; return this.connect(nodeID); }) .then(socket => { if ( [PACKET_GOSSIP_REQ_ID, PACKET_GOSSIP_RES_ID, PACKET_GOSSIP_HELLO_ID].indexOf( type ) == -1 ) socket.lastUsed = Date.now(); return new this.Promise((resolve, reject) => { // Create binary payload const header = Buffer.alloc(HEADER_SIZE); header.writeInt32BE(data.length + HEADER_SIZE, 1); header.writeInt8(type, 5); const crc = header[1] ^ header[2] ^ header[3] ^ header[4] ^ header[5]; header[0] = crc; const payload = Buffer.concat([header, data]); try { socket.write(payload, () => { //this.logger.info(`${type} packet sent to ${nodeID}.`); //this.logger.info(data.toString()); resolve(); }); } catch (err) { this.removeSocket(nodeID); reject(err); } }); }); } /** * Manage maximum live connections * * @memberof TcpWriter */ manageConnections() { let count = this.sockets.size - this.opts.maxConnections; if (count <= 0) return; const list = []; this.sockets.forEach((socket, nodeID) => list.push({ nodeID, lastUsed: socket.lastUsed })); list.sort((a, b) => a.lastUsed - b.lastUsed); count = Math.min(count, list.length - 1); const removable = list.slice(0, count); this.logger.debug(`Close ${count} old sockets.`, removable); removable.forEach(({ nodeID }) => this.removeSocket(nodeID)); } /** * Save a socket by nodeID * * @param {String} nodeID * @param {Socket} socket * @param {Boolean} force * @returns * * @memberof TcpWriter */ addSocket(nodeID, socket, force) { const s = this.sockets.get(nodeID); if (!force && s && !s.destroyed) return; this.sockets.set(nodeID, socket); } /** * Remove & close socket by nodeID * * @param {String} nodeID */ removeSocket(nodeID) { const socket = this.sockets.get(nodeID); if (socket && !socket.destroyed) socket.destroy(); this.sockets.delete(nodeID); } /** * Close the TCP sockets. * * @memberof TcpWriter */ close() { // Close all live sockets this.sockets.forEach(socket => { if (!socket.destroyed) socket.end(); }); this.sockets.clear(); } } module.exports = TcpWriter;