nats-micro
Version:
NATS micro compatible extra-lightweight microservice library
236 lines • 11.7 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (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());
});
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
import EventEmitter from 'events';
import { debug } from './debug.js';
import { MicroserviceRegistrationSubject, } from './types/index.js';
import { wrapMethod, attachThreadContext } from './utils/index.js';
export class Monitor {
constructor(broker, systemBroker, options = {}) {
this.broker = broker;
this.systemBroker = systemBroker;
this.services = [];
this.connections = {};
this.ee = new EventEmitter();
this.options = Object.assign({ discoveryTimeout: 5000 }, options);
const handleServiceRegistration = wrapMethod(this.broker, attachThreadContext('monitor', this.handleServiceRegistration.bind(this)), {
method: 'handleServiceStatus',
});
broker.on(MicroserviceRegistrationSubject, handleServiceRegistration);
if (systemBroker) {
systemBroker.on('$SYS.ACCOUNT.*.CONNECT', this.handleAccountConnect.bind(this));
systemBroker.on('$SYS.ACCOUNT.*.DISCONNECT', this.handleAccountDisconnect.bind(this));
}
else {
debug.monitor.error('Connection established/dropped monitoring disabled: no system broker');
}
this.discoverConnections();
this.discover(this.options.discoveryTimeout);
}
discoverConnections() {
var _a, e_1, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
if (!this.systemBroker) {
debug.monitor.error('Failed to discover current connections: no system broker');
return;
}
try {
for (var _d = true, _e = __asyncValues(this.systemBroker.requestMany('$SYS.REQ.SERVER.PING', {}, {
timeout: 3000,
})), _f; _f = yield _e.next(), _a = _f.done, !_a;) {
_c = _f.value;
_d = false;
try {
const { data: server } = _c;
debug.monitor.info(`Found server ${server.server.id}`);
const { data: connz } = yield this.systemBroker.request(`$SYS.REQ.SERVER.${server.server.id}.CONNZ`, { auth: true });
if (!connz) {
debug.monitor.error(`Server ${server.server.id} did not response CONNZ request`);
return;
}
debug.monitor.debug(`Server ${server.server.id} connections: ${connz.data.connections.map((c) => c.cid)}`);
for (const connection of connz.data.connections) {
this.connections[connection.cid] = {
client: {
id: connection.cid,
host: connection.ip,
start: connection.start,
acc: connection.account,
user: connection.authorized_user,
},
server: {
name: server.server.name,
id: server.server.id,
host: server.server.host,
ver: server.server.ver,
},
};
}
for (const service of this.services) {
const clientId = this.getServiceClientId(service);
if (clientId) {
const conn = this.connections[clientId];
if (conn) {
service.connection = conn;
debug.monitor.debug(`Updated microservice ${service.name}.${service.id} to client ${clientId}'s connection`);
this.emit('added', service);
}
}
}
}
finally {
_d = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
}
finally { if (e_1) throw e_1.error; }
}
});
}
handleServiceRegistration(req, res) {
if (req.data.state === 'down')
this.removeService(req.data.info);
else
this.saveService(req.data.info);
res.sendNoResponse();
}
handleAccountConnect(msg) {
return __awaiter(this, void 0, void 0, function* () {
const connection = msg.data;
this.connections[connection.client.id] = msg.data;
});
}
handleAccountDisconnect(msg) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const connection = msg.data;
const clientId = connection.client.id;
let count = 0;
let idx = 0;
while (idx < this.services.length) {
const service = this.services[idx];
if (((_a = this.getServiceConnectionInfo(service)) === null || _a === void 0 ? void 0 : _a.client.id) === clientId) {
const removed = this.services.splice(idx, 1);
this.emit('removed', removed[0]);
count++;
}
else
idx++;
}
debug.monitor.debug(`Client ${clientId} disconnected, removing ${count} microservices`);
delete (this.connections[connection.client.id]);
this.emit('change', this.services);
});
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
emit(event, ...args) {
this.ee.emit(event, ...args);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
on(event, listener) {
this.ee.on(event, listener);
}
getServiceClientId(service) {
const clientId = Number(service.metadata['_nats.client.id']);
return Number.isNaN(clientId) ? undefined : clientId;
}
getServiceConnectionInfo(service) {
const clientId = this.getServiceClientId(service);
return clientId ? this.connections[clientId] : undefined;
}
saveService(service) {
const idx = this.services.findIndex((svc) => svc.id === service.id);
const clientId = this.getServiceClientId(service);
const connection = this.getServiceConnectionInfo(service);
debug.monitor.debug(`${idx >= 0 ? 'Updated' : 'New'} microservice ${service.name}.${service.id} on client ${clientId}${connection ? '' : ' (unknown connection)'}`);
if (idx >= 0) {
this.services[idx] = Object.assign(Object.assign(Object.assign({}, this.services[idx]), service), { lastFoundAt: new Date() });
}
else
this.services.push(Object.assign({ firstFoundAt: new Date(), lastFoundAt: new Date(), connection: connection }, service));
this.emit('added', service);
this.emit('change', this.services);
}
removeService(service) {
const idx = this.services.findIndex((svc) => svc.id === service.id);
if (idx >= 0) {
debug.monitor.debug(`Removing microservice ${service.name}.${service.id}`);
this.services.splice(idx, 1);
this.emit('removed', service);
this.emit('change', this.services);
}
}
discover(timeout, options) {
var _a, e_2, _b, _c;
return __awaiter(this, void 0, void 0, function* () {
const servicesSnapshot = [...this.services];
const servicesIterator = this.broker.requestMany('$SRV.INFO', '', {
limit: -1,
timeout: timeout !== null && timeout !== void 0 ? timeout : this.options.discoveryTimeout,
});
const services = [];
try {
for (var _d = true, servicesIterator_1 = __asyncValues(servicesIterator), servicesIterator_1_1; servicesIterator_1_1 = yield servicesIterator_1.next(), _a = servicesIterator_1_1.done, !_a;) {
_c = servicesIterator_1_1.value;
_d = false;
try {
const service = _c;
services.push(service.data);
this.saveService(service.data);
}
finally {
_d = true;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_d && !_a && (_b = servicesIterator_1.return)) yield _b.call(servicesIterator_1);
}
finally { if (e_2) throw e_2.error; }
}
if (!(options === null || options === void 0 ? void 0 : options.doNotClear)) {
// compare to this.services BEFORE requestMany was made
// as there can be new additions after $SRV.INFO sent and
// before we get to this point
const servicesToForget = servicesSnapshot
.filter((oldSvc) => !services.some((newSvc) => newSvc.id === oldSvc.id));
if (servicesToForget.length > 0) {
debug.monitor.info(`Removing microservices ${servicesToForget.map((svc) => `${svc.name}.${svc.id}`)} that have not responded during discovery`);
for (const serviceToForger of servicesToForget)
this.removeService(serviceToForger);
}
}
});
}
startPeriodicDiscovery(interval, discoveryTimeout) {
this.stopPeriodicDiscovery();
this.discoveryInterval = setInterval(() => this.discover(discoveryTimeout), interval);
}
stopPeriodicDiscovery() {
if (this.discoveryInterval) {
clearInterval(this.discoveryInterval);
this.discoveryInterval = undefined;
}
}
}
//# sourceMappingURL=monitor.js.map