occaecatidicta
Version:
243 lines (214 loc) • 7.16 kB
text/typescript
import { getLogger, Logger } from 'omelox-logger';
let logger = getLogger('omelox-rpc', 'mqtt-acceptor');
import { EventEmitter } from 'events';
import { Tracer } from '../../util/tracer';
import * as utils from '../../util/utils';
let MqttCon: any = require('mqtt-connection');
import * as util from 'util';
import * as net from 'net';
import { Socket } from 'net';
import {AcceptorOpts, IAcceptor, AcceptorCallback} from '../acceptor';
import { MsgPkg } from '../dispatcher';
export interface AcceptorPkg {
source: string;
remote: string;
id: string & number;
seq: number;
msg: MsgPkg;
}
let curId = 1;
export class MQTTAcceptor extends EventEmitter implements IAcceptor {
interval: number; // flush interval in ms
bufferMsg: any;
rpcLogger: any;
rpcDebugLog: any;
_interval: any; // interval object
sockets: any;
msgQueues: any;
cb: AcceptorCallback;
inited: boolean;
server: net.Server;
closed: boolean;
constructor(opts: AcceptorOpts, cb: AcceptorCallback) {
super();
this.interval = opts.interval; // flush interval in ms
this.bufferMsg = opts.bufferMsg;
this.rpcLogger = opts.rpcLogger;
this.rpcDebugLog = opts.rpcDebugLog;
this._interval = null; // interval object
this.sockets = {};
this.msgQueues = {};
this.cb = cb;
}
listen(port: number|string) {
// check status
if (this.inited) {
// this.cb(new Error('already inited.'));
// return;
throw new Error('already inited.');
}
this.inited = true;
let self = this;
this.server = new net.Server();
this.server.listen(port);
this.server.on('error', (err) => {
logger.error('rpc server is error: %j', err.stack);
self.emit('error', err, this);
});
this.server.on('connection', function (stream) {
stream.setNoDelay(true)
let socket = MqttCon(stream);
socket['id'] = curId++;
socket.on('connect', function (pkg: any) {
console.log('connected');
});
socket.on('publish', function (pkg: any) {
pkg = pkg.payload.toString();
let isArray = false;
try {
pkg = JSON.parse(pkg);
if (pkg instanceof Array) {
self.processMsgs(socket, pkg);
isArray = true;
} else {
self.processMsg(socket, pkg);
}
} catch (err) {
if (!isArray) {
self.doSend(socket, {
id: pkg.id,
resp: [self.cloneError(err)]
});
}
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();
}, 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: {[key: string]: 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, pkg: AcceptorPkg) {
let tracer: Tracer = null;
if (this.rpcDebugLog) {
tracer = new Tracer(this.rpcLogger, this.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');
}
this.cb(tracer, pkg.msg, (... args: any[]) => {
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 = {
id: pkg.id,
resp: args
};
}
if (this.bufferMsg) {
this.enqueue(socket, resp);
} else {
this.doSend(socket, resp);
}
});
}
processMsgs(socket: any, pkgs: Array<AcceptorPkg>) {
for (let i = 0, l = pkgs.length; i < l; i++) {
this.processMsg(socket, pkgs[i]);
}
}
enqueue(socket: any, msg: {[key: string]: any}) {
let id = socket.id;
let queue = this.msgQueues[id];
if (!queue) {
queue = this.msgQueues[id] = [];
}
queue.push(msg);
}
flush() {
let sockets = this.sockets,
queues = this.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: {[key: string]: any}) {
socket.publish({
topic: 'rpc',
payload: JSON.stringify(msg)
});
}
}
/**
* 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 MQTTAcceptor(opts || <any>{}, cb);
}