'use strict'; var net = require('net'); var os = require('os'); var http = require('http'); var querystring = require('querystring'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var net__namespace = /*#__PURE__*/_interopNamespaceDefault(net); var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os); var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; const REGISTER_RETRY_INTERVAL = 5000; const JSONRPC = '2.0'; function newRequest(id, method, params) { return { id, jsonrpc: JSONRPC, method, params }; } const METHOD_NOT_FOUND = 'MethodNotFound'; const INVALID_PARAMS = 'InvalidParams'; const messages = { ParseError: 'Parse error', InvalidRequest: 'Invalid request', MethodNotFound: 'Method not found', InvalidParams: 'Invalid params', InternalError: 'Internal error' }; const codes = { WithoutError: 0, ParseError: -32700, InvalidRequest: -32600, MethodNotFound: -32601, InvalidParams: -32602, InternalError: -32603, CustomError: -32000 }; function newResult(id, result) { return { id, jsonrpc: JSONRPC, result }; } function newError(id, code, message, data) { return { id, jsonrpc: JSONRPC, error: { code: codes[code], message: messages[code], data: data } }; } /** * Get the methods parameters name array. */ function getMethodParameters(method) { const p = method .toString() .replace(/\/\*.*\*\//, '') .match(/\((.*?)\)/)[1]; if (p == '') return []; return p.split(',').map((param) => param.trim()); } let serviceMethodParameters = new Map(); function parameterObject2Array(serviceName, methodName, params, method) { const key = `${serviceName}-${methodName}`; let names; if (serviceMethodParameters.has(key)) { names = serviceMethodParameters.get(key) || []; } else { names = getMethodParameters(method); serviceMethodParameters.set(key, names); } return names.map((name) => params[name]); } function handler(message, map) { let method = message.method; if (method.substring(0, 1) === '/') { method = method.substring(1); } const methodArray = method.split('/'); const serviceName = methodArray[0]; const methodName = methodArray[1]; const service = map.get(serviceName); if (service !== undefined) { const method = service[methodName]; if (method !== undefined) { try { let params = message.params; if (!Array.isArray(params)) { params = parameterObject2Array(serviceName, methodName, params, method); } const res = method(...params); return JSON.stringify(newResult(message.id, res)); } catch (e) { return JSON.stringify(newError(message.id, INVALID_PARAMS)); } } } else { return JSON.stringify(newError(message.id, METHOD_NOT_FOUND)); } return JSON.stringify(newError(message.id, METHOD_NOT_FOUND)); } function getLocalIp() { const networkInterfaces = os__namespace.networkInterfaces(); const ipv4Addresses = []; Object.keys(networkInterfaces).forEach((interfaceName) => { if (networkInterfaces === undefined || networkInterfaces[interfaceName] === undefined) { return; } const ifaces = networkInterfaces[interfaceName] || []; ifaces.forEach((iface) => { // Filter IPv4 addresses if (iface.family === 'IPv4') { ipv4Addresses.push(iface.address); } }); }); return ipv4Addresses.length > 0 ? ipv4Addresses[0] : null; } function splitAddress(address) { const addressArray = address.split(':'); return { host: addressArray[0], port: addressArray[1] ? Number(addressArray[1]) : 80 }; } function splitAddresses(address) { const addressArray = address.split(','); return addressArray.filter((item) => item != undefined).map((item) => splitAddress(item)); } function loadBalanceAddress(addresses) { if (addresses.length === 0) { return null; } const randomIndex = Math.floor(Math.random() * addresses.length); return addresses[randomIndex]; } let Tcp$1 = class Tcp { constructor(port, discovery, options) { /** * The method map. */ this.map = new Map(); /** * The server hostname. */ this.hostname = null; /** * The server options. */ this.options = { delimiter: '\r\n' }; this.port = port; if (discovery != undefined) { this.discovery = discovery; this.hostname = getLocalIp(); } if (options !== undefined) { this.options = Object.assign(this.options, options); } } /** * Set tcp options. */ setOptions(options) { } /** * Start the tcp server. */ start(callback) { var server = net__namespace.createServer((socket) => { socket.on('close', () => { }); socket.on('data', (data) => { const delimiter = this.options.delimiter; let json = data.toString(); json = json.substring(0, json.length - delimiter.length); const res = handler(JSON.parse(json), this.map); socket.write(res + delimiter); }); socket.on('end', function () { }); }); server.listen(this.port, () => { console.info(`Listening tcp://0.0.0.0:${this.port}`); if (callback) callback(server); }); } /** * Register the method to the map. */ register(o, name) { return __awaiter(this, void 0, void 0, function* () { if (name) { this.map.set(name, o); } else { this.map.set(o.constructor.name, o); } if (this.discovery !== undefined && this.hostname != null) { const res = yield this.discovery.register(o.constructor.name, 'tcp', this.hostname, this.port); if (res !== true) { setTimeout(() => { this.register(o); }, REGISTER_RETRY_INTERVAL); } } }); } /** * Hander the client message. */ handler(message) { const res = handler(message, this.map); return res; } }; let Http$1 = class Http { constructor(port, discovery) { /** * The method map. */ this.map = new Map(); /** * The server hostname. */ this.hostname = null; this.port = port; if (discovery != undefined) { this.discovery = discovery; this.hostname = getLocalIp(); } } /** * Set tcp options. */ setOptions(options) { } /** * Start the tcp server. */ start(callback) { var server = http__namespace.createServer((request, response) => { let requestData = ''; // Listen for data events request.on('data', (chunk) => { requestData += chunk; }); // Listen for end event (all data received) request.on('end', () => { const parsedData = JSON.parse(requestData); const res = this.handler(parsedData); response.writeHead(200, { 'Content-Type': 'application/json' }); response.end(res); }); }); server.listen(this.port, () => { console.info(`Listening http://0.0.0.0:${this.port}`); if (callback) callback(server); }); } /** * Register the method to the map. */ register(o, name) { return __awaiter(this, void 0, void 0, function* () { if (name) { this.map.set(name, o); } else { this.map.set(o.constructor.name, o); } if (this.discovery !== undefined && this.hostname != null) { const res = yield this.discovery.register(o.constructor.name, 'http', this.hostname, this.port); if (res !== true) { setTimeout(() => { this.register(o); }, REGISTER_RETRY_INTERVAL); } } }); } /** * Hander the client message. */ handler(message) { const res = handler(message, this.map); return res; } }; function generateTimestampUUID() { const timestamp = new Date().getTime().toString(16); const randomPart = Math.random().toString(16).substring(2, 6); // 12 characters of random data return `${timestamp}${randomPart}`; } class Pool { constructor(service, address, client, option) { /** * Active service address. */ this.activeAddresses = []; /** * The number of active service address. */ this.activeTotal = 0; /** * The service connections. */ this.conns = []; /** * The resolve queue that connection. */ this.resolves = []; this.service = service; this.address = address; this.client = client; this.option = option || { minIdle: 1, maxIdle: 10 }; } /** * Set the pool active addresses. * @returns */ setActiveAddresses() { return __awaiter(this, void 0, void 0, function* () { let address; if (typeof this.address == 'string') { address = this.address; } else { address = yield this.address.get(this.service); } const addresses = address.split(','); this.activeAddresses = addresses; return this.activeAddresses.length; }); } /** * Set the pool connections. */ setConns() { return __awaiter(this, void 0, void 0, function* () { yield this.setActiveAddresses(); for (let i = 0; i < this.option.minIdle; i++) { const conn = yield this.createConn(); this.conns.push(conn); } }); } /** * Create a tcp connection. * @returns */ createConn() { return __awaiter(this, void 0, void 0, function* () { let size = this.activeAddresses.length; if (size == 0) { yield this.setActiveAddresses(); } size = this.activeAddresses.length; if (size == 0) { return Promise.reject(new Error('No valid server available.')); } const key = this.activeTotal % size; const address = this.activeAddresses[key]; const socket = new net__namespace.Socket(); const addressObj = splitAddress(address); let isRemoved = false; return new Promise((resolve, reject) => { socket.on('ready', () => { this.activeTotal++; resolve(socket); }); socket.on('close', () => { }); socket.on('data', (data) => { this.client.handler(data.toString()); }); socket.on('error', (error) => { if (!isRemoved) { isRemoved = true; delete this.activeAddresses[key]; this.activeTotal--; } reject(error); }); socket.on('timeout', () => { if (!isRemoved) { isRemoved = true; delete this.activeAddresses[key]; this.activeTotal--; } reject(new Error('Connection timeout.')); }); socket.connect(addressObj.port, addressObj.host); }); }); } /** * Borrow a tcp connection from pool. * @returns */ borrow() { return __awaiter(this, void 0, void 0, function* () { if (this.activeTotal <= 0) { yield this.setConns(); } if (this.activeTotal <= 0) { return Promise.reject(new Error('Unable to connect to the server.')); } if (this.activeTotal >= this.option.maxIdle) { return new Promise((resolve) => { const conn = this.conns.shift(); if (conn !== undefined) { resolve(conn); } else { this.resolves.push(resolve); } }); } return this.createConn(); }); } /** * Release a tcp connection to the pool. * @param conn */ release(conn) { const resolve = this.resolves.shift(); if (resolve !== undefined) { resolve(conn); } else { this.conns.push(conn); } } } class Tcp { constructor(service, address, options) { /** * The callback map, the key is message id. */ this.map = new Map(); /** * The client options. */ this.options = { delimiter: '\r\n' }; this.service = service; this.pool = new Pool(service, address, this); if (options !== undefined) { this.options = Object.assign(this.options, options); } } call(method, ...args) { return __awaiter(this, void 0, void 0, function* () { const id = generateTimestampUUID(); const request = newRequest(id, this.service + '/' + method, args); const delimiter = this.options.delimiter; return this.pool.borrow().then((conn) => { const res = conn.write(JSON.stringify(request) + delimiter); if (res === true) { // Release the connection. this.pool.release(conn); } else { return Promise.reject(new Error('The connection was broken.')); } return new Promise((resolve, reject) => { this.map.set(id, (data) => { if (data.error != undefined) { reject(new Error(data.error.message)); } else if (data.result) { resolve(data.result); } else { reject(new Error('Unknown error')); } }); setTimeout(() => { this.map.delete(id); reject(); }, 10000); }); }); }); } handler(res) { const delimiter = this.options.delimiter; res = res.substring(0, res.length - delimiter.length); const data = JSON.parse(res); const callback = this.map.get(data.id); if (callback !== undefined) callback(data); } } class Http { constructor(service, address, options) { /** * Callback function map. */ this.map = new Map(); /** * Active service address. */ this.activeAddresses = []; this.service = service; this.address = address; } /** * Call service method to the server. * @param method * @param args * @returns */ call(method, ...args) { return __awaiter(this, void 0, void 0, function* () { const id = generateTimestampUUID(); const postData = JSON.stringify(newRequest(id, this.service + '/' + method, args)); const address = yield this.getUrl(); if (address == null) { return Promise.reject(new Error('Without invalid address.')); } const req = http__namespace.request({ host: address.host, port: address.port, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) } }, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { this.handler(data); }); }); req.on('error', (e) => { this.activeAddresses = this.activeAddresses.filter((item) => item.host != address.host || item.port != address.port); }); // Write data to request body req.write(postData); req.end(); return new Promise((resolve, reject) => { this.map.set(id, (data) => { if (data.error != undefined) { reject(new Error(data.error.message)); } else if (data.result) { resolve(data.result); } else { reject(new Error('Unknown error')); } }); setTimeout(() => { this.map.delete(id); reject(new Error('Timeout')); }, 10000); }); }); } getUrl() { return __awaiter(this, void 0, void 0, function* () { if (this.activeAddresses.length > 0) { return loadBalanceAddress(this.activeAddresses); } let address; if (typeof this.address == 'string') { address = this.address; } else { address = yield this.address.get(this.service); } this.activeAddresses = splitAddresses(address); return loadBalanceAddress(this.activeAddresses); }); } handler(res) { const data = JSON.parse(res); const callback = this.map.get(data.id); if (callback !== undefined) callback(data); } } function json(url, method, body) { return new Promise((resolve, reject) => { const req = http__namespace.request(url, { method: method, headers: { 'Content-Type': 'application/json', 'Content-Length': body ? Buffer.byteLength(body) : 0 } }, (res) => { if (res.statusCode !== 200) { const error = new Error(`Request failed with status code ${res.statusCode}`); req.emit('error', error); } else { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { resolve(data); }); } }); req.on('error', (error) => { reject(error); }); if (body) { req.write(body); } req.end(); }); } function get(url) { return new Promise((resolve) => { http__namespace.get(url, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { resolve(data); }); }); }); } class Consul { constructor(url) { this.url = url; } register(name, protocol, hostname, port) { return __awaiter(this, void 0, void 0, function* () { const parsedUrl = new URL(this.url); const queryParams = querystring.parse(parsedUrl.search.slice(1)); const instanceId = queryParams.instanceId; const token = queryParams.token; let id; if (instanceId == undefined) { id = `${name}:${port}`; } else { id = `${name}-${instanceId}:${port}`; } const rs = { ID: id, Name: name, Port: port, Address: hostname }; const registerData = JSON.stringify(rs); const registerUrl = this.getUrl(parsedUrl, '/v1/agent/service/register', token); let isCheck = queryParams.check; if (isCheck == 'true') { const res = yield json(registerUrl, 'PUT', registerData); let interval = queryParams.interval; if (!interval) { interval = '30s'; } let timeout = queryParams.timeout; if (!timeout) { timeout = '10s'; } let http, tcp; if (protocol == 'http' || protocol == 'https') { http = `${protocol}://${hostname}:${port}`; } else if (protocol == 'tcp') { tcp = `${hostname}:${port}`; } const check = { ID: id, Name: name, Status: 'passing', ServiceID: id, HTTP: http, Method: name, TCP: tcp, Interval: interval, Timeout: timeout }; const checkData = JSON.stringify(check); const checkUrl = this.getUrl(parsedUrl, '/v1/agent/check/register', token); yield json(checkUrl, 'PUT', checkData); return res === ''; } else { const res = yield json(registerUrl, 'PUT', registerData); return res === ''; } }); } get(name) { return __awaiter(this, void 0, void 0, function* () { const parsedUrl = new URL(this.url); const queryParams = querystring.parse(parsedUrl.search.slice(1)); const token = queryParams.token; const getUrl = this.getUrl(parsedUrl, `/v1/agent/health/service/name/${name}`, token); const res = yield json(getUrl, 'GET', null); const list = JSON.parse(res); return list .filter((item) => item.AggregatedStatus === 'passing') .map((item) => `${item.Service.Address}:${item.Service.Port}`) .join(','); }); } getUrl(parsedUrl, path, token) { if (token) { return `${parsedUrl.origin}${path}?token=${token}`; } return `${parsedUrl.origin}${path}`; } } const HEARTBEAT_INTERVAL = 5000; // ms const HEARTBEAT_RETRY_MAX = 3; // times class Nacos { constructor(url) { this.heartbeatList = new Array(); this.heartbeatRetry = new Map(); this.url = url; this.heatbeat(); } register(name, protocol, hostname, port) { return __awaiter(this, void 0, void 0, function* () { const parsedUrl = new URL(this.url); const queryParams = querystring.parse(parsedUrl.search.slice(1)); let ephemeral = queryParams.ephemeral; if (ephemeral === undefined) { ephemeral = 'true'; } let params = new Map(); params.set('serviceName', name); params.set('ip', hostname); params.set('port', port.toString()); params.set('ephemeral', ephemeral); const registerUrl = this.getUrl(parsedUrl, '/nacos/v1/ns/instance', params); const res = yield json(registerUrl, 'POST', null); if (ephemeral === 'true') { this.heartbeatList.push({ ip: hostname, port: port, healthy: true, instanceId: name }); } return res === 'ok'; }); } get(name) { return __awaiter(this, void 0, void 0, function* () { const parsedUrl = new URL(this.url); let params = new Map(); params.set('serviceName', name); const getUrl = this.getUrl(parsedUrl, `/nacos/v1/ns/instance/list`, params); const res = yield get(getUrl); const body = JSON.parse(res); if (!body || !body.hosts) { return ''; } return body.hosts.filter((item) => item.healthy).map((item) => `${item.ip}:${item.port}`).join(','); }); } beat(name, hostname, port) { const parsedUrl = new URL(this.url); const queryParams = querystring.parse(parsedUrl.search.slice(1)); let ephemeral = queryParams.ephemeral; if (ephemeral === undefined) { ephemeral = 'true'; } let params = new Map(); params.set('serviceName', name); params.set('ip', hostname); params.set('port', port.toString()); params.set('ephemeral', ephemeral); const beatUrl = this.getUrl(parsedUrl, '/nacos/v1/ns/instance/beat', params); return json(beatUrl, 'PUT', null); } heatbeat() { for (let i = 0; i < this.heartbeatList.length; i++) { const service = this.heartbeatList[i]; const instanceId = service.instanceId; const ip = service.ip; const port = service.port; this.beat(instanceId, ip, port).catch((error) => { const key = `${ip}:${port}`; const times = this.heartbeatRetry.get(key); if (times === undefined) { this.heartbeatRetry.set(key, 1); } else { if (times >= HEARTBEAT_RETRY_MAX) { this.heartbeatList.splice(i, 1); this.heartbeatRetry.delete(key); } else { this.heartbeatRetry.set(key, times + 1); } } }); } setTimeout(() => { this.heatbeat(); }, HEARTBEAT_INTERVAL); } getUrl(parsedUrl, path, params) { parsedUrl.pathname = path; for (let [key, value] of params) { parsedUrl.searchParams.append(key, value); } return parsedUrl.toString(); } } function NewServer(protocol, port, discovery, options) { let server; switch (protocol) { case 'tcp': server = new Tcp$1(port, discovery, options); break; case 'http': server = new Http$1(port, discovery); break; default: throw new Error('Unknown error'); } return server; } function NewClient(service, protocol, address, options) { let client; switch (protocol) { case 'tcp': client = new Tcp(service, address, options); break; case 'http': client = new Http(service, address); break; default: throw new Error('Unknown error'); } return client; } exports.Consul = Consul; exports.HttpClient = Http; exports.HttpServer = Http$1; exports.Nacos = Nacos; exports.NewClient = NewClient; exports.NewServer = NewServer; exports.TcpClient = Tcp; exports.TcpServer = Tcp$1;