UNPKG

microwizard

Version:

A fast and stable microservice framework, mostly compatible with senecas user API

258 lines (218 loc) 5.48 kB
/** * Parts of this code are from the original module and under the following * copyright. * * /* Copyright (c) 2015-2017 Richard Rodger, MIT License * the rest of the code follows the following copyright * /* Copyright (c) 2024 WizardTales GmbH, MIT License */ import { LRUCache } from 'lru-cache'; import * as Tcp from './tcp.js'; import { uid } from 'uid'; import Promise from 'bluebird'; const METHODS = { a: 'act', aE: 'actE' }; class TP { #context; #closeAction = []; constructor (context, mc) { this.#context = context; this.mc = mc; } async handleRequest (data, listenOptions, reduceActive) { if (!['a', 'aE'].includes(data.k)) { return { input: data, error: 'unknown method' }; } const out = { id: data.id, k: 'res', sync: data.sync }; // we send overload message when the service stopped // responding if (this.mc.offline) { const errobj = {}; errobj.message = 'retry_later_err_overload'; errobj.name = 'Error'; out.error = errobj; return out; } try { const response = await this.mc[METHODS[data.k]]( data.p || data.args, data.d, { n: true }, // signal this came from the network { n: true } // signal this came from the network ); out.res = response; } catch (err) { const errobj = Object.assign({}, err); errobj.message = err.message; errobj.name = err.name || 'Error'; errobj.stack = err.stack; out.error = errobj; } return out; } handleResponse (data, clientOptions) { data.sync = undefined === data.sync ? true : data.sync; if (data.k !== 'res') { if (this._context.options.warn.invalid_kind) { console.log('client', 'invalid_kind_res', clientOptions, data); } return false; } if (data.id === null) { if (this._context.options.warn.no_message_id) { console.log('client', 'no_message_id', clientOptions, data); } return false; } let result = null; let err = null; if (!data.error) { result = data.res; } else { err = new Error(data.error.message); for (const [key, value] of Object.entries(data.error)) { err[key] = value; } if (!data.sync) { console.log( 'client', 'unexcepted_async_error', clientOptions, data, err ); return true; } } if (!data.sync) { return true; } const callmeta = this.#context.callmap.get(data.id); if (callmeta) { setTimeout(() => { this.#context.callmap.delete(data.id); }, 100); } else { // this can result when there was a slow request still answering after // timeout if (this.#context.options.warn.unknown_message_id) { console.log('client', 'unknown_message_id', clientOptions, data); } return false; } const actinfo = { id: data.id }; this.callmeta({ callmeta: callmeta, err: err, result: result, actinfo: actinfo, clientOptions: clientOptions, data: data }); return true; } callmeta (options) { try { options.callmeta.done(options.err, options.result, options.actinfo); } catch (e) { console.error( 'client', 'callback_error', options.clientOptions, options.data, e.stack || e ); } } prepareRequest (args, done, meta) { const meta$ = meta || {}; meta$.sync = undefined === meta$.sync ? true : meta$.sync; const callmeta = { args: args, done, when: Date.now() }; if (meta$.sync) { this.#context.callmap.set(meta$.id, callmeta); } const output = { id: meta$.id, k: meta$.k || 'a', sync: meta$.sync }; if (output.k === 'aE') { output.p = meta$.p; output.d = args; } else { output.args = args; } return output; } makeClient (makeSend, clientOptions, cb) { makeSend({}, null, function (err, send) { if (err) { return cb(err); } const client = { id: clientOptions.id || uid(), toString: function () { return `any-${this.id}`; }, send: async function (args, meta) { return Promise.fromCallback((done) => send.call(this, args, done, meta) ).catch((x) => { throw x; }); } }; cb(null, client); }); } // graceful shutdown onClose (action) { this.#closeAction.push(action); } async close () { return Promise.allSettled(this.#closeAction.map((x) => x())); } } export default function transport (opts, mc) { const callmap = new LRUCache({ max: 10000 }); const settings = { warn: { unknown_message_id: true, invalid_kind: true, invalid_origin: true, no_message_id: true, message_loop: true, own_message: true }, host: '0.0.0.0', port: 10201, timeout: 5555, // fail fast, this connect timeout only really comes to play during // initial and re-connects connectTimeout: 355, ...opts }; const tp = new TP( { callmap, opts: settings }, mc ); const listen = Tcp.listen(settings, tp); const client = Tcp.client(settings, tp); return { listen, client, tp }; }