occaecatidicta
Version:
267 lines (236 loc) • 7.11 kB
text/typescript
import { getLogger } from 'omelox-logger';
let logger = getLogger('omelox-rpc', 'mqtt2-acceptor');
import { EventEmitter } from 'events';
import {constants} from '../../util/constants';
import { Tracer } from '../../util/tracer';
import * as utils from '../../util/utils';
import * as Coder from '../../util/coder';
let MqttCon: any = require('mqtt-connection');
import * as util from 'util';
import * as net from 'net';
let curId = 1;
export interface AcceptorPkg {
source: string;
remote: string;
id: string & number;
seq: number;
msg: string;
payload: any;
}
export interface AcceptorOpts {
interval: number;
bufferMsg: any;
rpcLogger: any;
rpcDebugLog: any;
services: any;
}
export class MQTT2Acceptor extends EventEmitter {
interval: number; // flush interval in ms
bufferMsg: any;
rpcLogger: any;
rpcDebugLog: any;
services: any;
_interval: any; // interval object
sockets: any;
msgQueues: any;
servicesMap: any;
cb: any;
inited: boolean;
server: net.Server;
closed: boolean;
constructor(opts: AcceptorOpts, cb: (tracer: Tracer, msg?: any, cb?: Function) => void) {
super();
this.interval = opts.interval; // flush interval in ms
this.bufferMsg = opts.bufferMsg;
this.rpcLogger = opts.rpcLogger;
this.rpcDebugLog = opts.rpcDebugLog;
this.services = opts.services;
this._interval = null; // interval object
this.sockets = {};
this.msgQueues = {};
this.servicesMap = {};
this.cb = cb;
}
listen(port: string | number) {
// check status
if (!!this.inited) {
this.cb(new Error('already inited.'));
return;
}
this.inited = true;
let self = this;
this.server = new net.Server();
this.server.listen(port);
this.server.on('error', function (err) {
logger.error('rpc server is error: %j', err.stack);
self.emit('error', err);
});
this.server.on('connection', function (stream) {
stream.setNoDelay(true)
let socket = MqttCon(stream);
socket['id'] = curId++;
socket.on('connect', function (pkg: AcceptorPkg) {
console.log('connected');
self.sendHandshake(socket, self);
});
socket.on('publish', function (pkg: AcceptorPkg) {
let newPkg: any = Coder.decodeServer(pkg.payload, self.servicesMap);
try {
self.processMsg(socket, self, newPkg);
} catch (err) {
let resp = Coder.encodeServer(newPkg.id, [self.cloneError(err)]);
// doSend(socket, resp);
logger.error('process rpc message error %s', err.stack);
}
});
socket.on('pingreq', function () {
socket.pingresp();
});
socket.on('error', function () {
self.onSocketClose(socket);
});
socket.on('close', function () {
self.onSocketClose(socket);
});
self.sockets[socket.id] = socket;
socket.on('disconnect', function (reason: Error) {
self.onSocketClose(socket);
});
});
if (this.bufferMsg) {
this._interval = setInterval(function () {
self.flush(self);
}, this.interval);
}
}
close() {
if (this.closed) {
return;
}
this.closed = true;
if (this._interval) {
clearInterval(this._interval);
this._interval = null;
}
this.server.close();
this.emit('closed');
}
onSocketClose(socket: any) {
if (!socket['closed']) {
let id = socket.id;
socket['closed'] = true;
delete this.sockets[id];
delete this.msgQueues[id];
}
}
cloneError(origin: { msg: string, stack: object }) {
// copy the stack infos for Error instance json result is empty
let res = {
msg: origin.msg,
stack: origin.stack
};
return res;
}
processMsg(socket: object, acceptor: MQTT2Acceptor, pkg: AcceptorPkg) {
let tracer: Tracer = null;
if (this.rpcDebugLog) {
tracer = new Tracer(acceptor.rpcLogger, acceptor.rpcDebugLog, pkg.remote, pkg.source, pkg.msg, pkg.id, pkg.seq);
tracer.info('server', __filename, 'processMsg', 'mqtt-acceptor receive message and try to process message');
}
acceptor.cb(tracer, pkg.msg, (... args: any[]) => {
// let args = Array.prototype.slice.call(arguments, 0);
let len = arguments.length;
for (let i = 0; i < len; i++) {
args[i] = arguments[i];
}
let errorArg = args[0]; // first callback argument can be error object, the others are message
if (errorArg && errorArg instanceof Error) {
args[0] = this.cloneError(<any>errorArg);
}
let resp;
if (tracer && tracer.isEnabled) {
resp = {
traceId: tracer.id,
seqId: tracer.seq,
source: tracer.source,
id: pkg.id,
resp: args
};
} else {
resp = <any>Coder.encodeServer(pkg.id, args);
// resp = {
// id: pkg.id,
// resp: args
// };
}
if (acceptor.bufferMsg) {
this.enqueue(socket, acceptor, resp);
} else {
this.doSend(socket, resp);
}
});
}
processMsgs(socket: any, acceptor: MQTT2Acceptor, pkgs: Array<AcceptorPkg>) {
for (let i = 0, l = pkgs.length; i < l; i++) {
this.processMsg(socket, acceptor, pkgs[i]);
}
}
enqueue(socket: any, acceptor: MQTT2Acceptor, msg: {[key: string]: any}) {
let id = socket.id;
let queue = acceptor.msgQueues[id];
if (!queue) {
queue = acceptor.msgQueues[id] = [];
}
queue.push(msg);
}
flush(acceptor: MQTT2Acceptor) {
let sockets = acceptor.sockets,
queues = acceptor.msgQueues,
queue, socket;
for (let socketId in queues) {
socket = sockets[socketId];
if (!socket) {
// clear pending messages if the socket not exist any more
delete queues[socketId];
continue;
}
queue = queues[socketId];
if (!queue.length) {
continue;
}
this.doSend(socket, queue);
queues[socketId] = [];
}
}
doSend(socket: any, msg: Coder.Msg) {
socket.publish({
topic: constants['TOPIC_RPC'],
payload: msg
// payload: JSON.stringify(msg)
});
}
doSendHandshake(socket: any, msg: string) {
socket.publish({
topic: constants['TOPIC_HANDSHAKE'],
payload: msg
// payload: JSON.stringify(msg)
});
}
sendHandshake(socket: any, acceptor: MQTT2Acceptor) {
// let servicesMap = utils.genServicesMap(acceptor.services);
// acceptor.servicesMap = servicesMap;
// this.doSendHandshake(socket, JSON.stringify(servicesMap));
let servicesMap = JSON.parse(acceptor.services);
acceptor.servicesMap = servicesMap;
this.doSendHandshake(socket, JSON.stringify(servicesMap));
}
}
/**
* create acceptor
*
* @param opts init params
* @param cb(tracer, msg, cb) callback function that would be invoked when new message arrives
*/
export function create(opts: AcceptorOpts, cb: (tracer: Tracer, msg?: any, cb?: Function) => void) {
return new MQTT2Acceptor(opts || <any>{}, cb);
}