UNPKG

occaecatidicta

Version:
315 lines (274 loc) 10.3 kB
import { getLogger } from 'omelox-logger'; let logger = getLogger('omelox-rpc', 'mqtt-mailbox'); import { EventEmitter } from 'events'; import { constants } from '../../util/constants'; import { Tracer } from '../../util/tracer'; let MqttCon: any = require('mqtt-connection'); import * as utils from '../../util/utils'; import * as util from 'util'; import * as net from 'net'; import {IMailBox, MailBoxTimeoutCallback, MailBoxMessage, MailBoxOpts, MailBoxPkg} from '../mailbox'; let CONNECT_TIMEOUT = 2000; export class MailBox extends EventEmitter implements IMailBox { constructor(serverInfo: {id: string, host: string, port: number}, opts: MailBoxOpts) { super(); this.id = serverInfo.id; this.host = serverInfo.host; this.port = serverInfo.port; this.bufferMsg = opts.bufferMsg; this.keepalive = opts.keepalive || constants.DEFAULT_PARAM.KEEPALIVE; this.interval = opts.interval || constants.DEFAULT_PARAM.INTERVAL; this.timeoutValue = opts.timeout || constants.DEFAULT_PARAM.CALLBACK_TIMEOUT; this.opts = opts; this.serverId = opts.context.serverId; } curId = 0; id: string; host: string; port: number; requests: {[id: number]: MailBoxTimeoutCallback} = {}; timeout: {[id: number]: NodeJS.Timer} = {}; queue: MailBoxMessage[] = []; bufferMsg: boolean; keepalive: number; interval: number; timeoutValue: any; keepaliveTimer: NodeJS.Timer; lastPing = -1; lastPong = -1; connected = false; closed = false; opts: any; serverId: string; socket: any; _interval: NodeJS.Timer; _errored = false; connect(tracer: Tracer, cb: (err?: Error) => void) { tracer && tracer.info('client', __filename, 'connect', 'mqtt-mailbox try to connect'); if (this.connected) { tracer && tracer.error('client', __filename, 'connect', 'mailbox has already connected'); return cb(new Error('mailbox has already connected.')); } let self = this; let stream = net.createConnection(this.port, this.host); stream.setNoDelay(true) this.socket = MqttCon(stream); let connectTimeout = setTimeout( () => { logger.error('rpc client %s connect to remote server %s timeout', self.serverId, self.id); self.emit('close', self.id); }, CONNECT_TIMEOUT); this.socket.connect({ clientId: 'MQTT_RPC_' + Date.now() }, () => { if (self.connected) { return; } clearTimeout(connectTimeout); if(self._errored) { cb(new Error('mqtt-mailbox errored')); return; } self.connected = true; if (self.bufferMsg) { self._interval = setInterval( () => { self.flush(); }, self.interval); } self.setupKeepAlive(); cb(); }); this.socket.on('publish', (pkg: any) => { pkg = pkg.payload.toString(); try { pkg = JSON.parse(pkg); if (pkg instanceof Array) { this.processMsgs( pkg); } else { this.processMsg(pkg); } } catch (err) { logger.error('rpc client %s process remote server %s message with error: %s', self.serverId, self.id, err.stack); } }); this.socket.on('error', function (err: any) { self._errored = true; logger.error('rpc socket %s is error, remote server %s host: %s, port: %s', self.serverId, self.id, self.host, self.port); if(err.code && err.code === 'ECONNREFUSED') { // self.emit('error', constants.RPC_ERROR.FAIL_CONNECT_SERVER, tracer, self.id, null, self.opts); return; } self.emit('close', self.id); }); this.socket.on('pingresp', function () { self.lastPong = Date.now(); }); this.socket.on('disconnect', function (reason: Error) { logger.error('rpc socket %s is disconnect from remote server %s, reason: %s', self.serverId, self.id, reason); let reqs: {[id: number]: any} = self.requests; for (let id in reqs) { let ReqCb = reqs[id]; ReqCb(tracer, new Error(self.serverId + ' disconnect with remote server ' + self.id)); } self.emit('close', self.id); }); } /** * close mailbox */ close() { if (this.closed) { return; } this.closed = true; this.connected = false; if (this._interval) { clearInterval(this._interval); this._interval = null; } this.socket.destroy(); } /** * send message to remote server * * @param msg {service:"", method:"", args:[]} * @param opts {} attach info to send method * @param cb declaration decided by remote interface */ send(tracer: Tracer, msg: MailBoxMessage, opts: any, cb: MailBoxTimeoutCallback) { tracer && tracer.info('client', __filename, 'send', 'mqtt-mailbox try to send'); if (!this.connected) { tracer && tracer.error('client', __filename, 'send', 'mqtt-mailbox not init'); cb(tracer, new Error(this.serverId + ' mqtt-mailbox is not init ' + this.id)); return; } if (this.closed) { tracer && tracer.error('client', __filename, 'send', 'mailbox has already closed'); cb(tracer, new Error(this.serverId + ' mqtt-mailbox has already closed ' + this.id)); return; } let id = this.curId++; this.requests[id] = cb; this.setCbTimeout( id, tracer, cb); let pkg: any; if (tracer && tracer.isEnabled) { pkg = { traceId: tracer.id, seqId: tracer.seq, source: tracer.source, remote: tracer.remote, id: id, msg: msg }; } else { pkg = { id: id, msg: msg }; } if (this.bufferMsg) { this.enqueue( pkg); } else { this.doSend(this.socket, pkg); } } setupKeepAlive() { let self = this; this.keepaliveTimer = setInterval(function () { self.checkKeepAlive(); }, this.keepalive); } checkKeepAlive() { if (this.closed) { return; } // console.log('checkKeepAlive lastPing %d lastPong %d ~~~', this.lastPing, this.lastPong); let now = Date.now(); let KEEP_ALIVE_TIMEOUT = this.keepalive * 2; if (this.lastPing > 0) { if (this.lastPong < this.lastPing) { if (now - this.lastPing > KEEP_ALIVE_TIMEOUT) { logger.error('mqtt rpc client %s checkKeepAlive timeout from remote server %s for %d lastPing: %s lastPong: %s', this.serverId, this.id, KEEP_ALIVE_TIMEOUT, this.lastPing, this.lastPong); this.emit('close', this.id); this.lastPing = -1; // this.close(); } } else { this.socket.pingreq(); this.lastPing = Date.now(); } } else { this.socket.pingreq(); this.lastPing = Date.now(); } } enqueue(msg: MailBoxMessage) { this.queue.push(msg); } flush() { if (this.closed || !this.queue.length) { return; } this.doSend(this.socket, this.queue); this.queue = []; } doSend(socket: any, msg: object) { socket.publish({ topic: 'rpc', payload: JSON.stringify(msg) }); } processMsgs(pkgs: Array<MailBoxPkg>) { for (let i = 0, l = pkgs.length; i < l; i++) { this.processMsg(pkgs[i]); } } processMsg(pkg: MailBoxPkg) { let pkgId = pkg.id; this.clearCbTimeout(pkgId); let cb = this.requests[pkgId]; if (!cb) { return; } delete this.requests[pkgId]; let rpcDebugLog = this.opts.rpcDebugLog; let tracer = null; let sendErr = null; if (rpcDebugLog) { tracer = new Tracer(this.opts.rpcLogger, this.opts.rpcDebugLog, this.opts.clientId, pkg.source, pkg.resp, pkg.id, pkg.seq); } let pkgResp = pkg.resp; cb(tracer, sendErr, pkgResp); } setCbTimeout(id: number, tracer: any, cb: Function) { let timer = setTimeout( () => { // logger.warn('rpc request is timeout, id: %s, host: %s, port: %s', id, this.host, this.port); this.clearCbTimeout(id); if (this.requests[id]) { delete this.requests[id]; } let eMsg = util.format('rpc %s callback timeout %d, remote server %s host: %s, port: %s', this.serverId, this.timeoutValue, id, this.host, this.port); logger.error(eMsg); cb(tracer, new Error(eMsg)); }, this.timeoutValue); this.timeout[id] = timer as any; } clearCbTimeout(id: number) { if (!this.timeout[id]) { logger.warn('timer is not exsits, serverId: %s remote: %s, host: %s, port: %s', this.serverId, id, this.host, this.port); return; } clearTimeout(this.timeout[id]); delete this.timeout[id]; } } /** * Factory method to create mailbox * * @param {Object} server remote server info {id:"", host:"", port:""} * @param {Object} opts construct parameters * opts.bufferMsg {Boolean} msg should be buffered or send immediately. * opts.interval {Boolean} msg queue flush interval if bufferMsg is true. default is 50 ms */ export function create(server: {id: string, host: string, port: number}, opts: MailBoxOpts): IMailBox { return new MailBox(server, opts || <any>{}); }