occaecatidicta
Version:
249 lines (225 loc) • 6.76 kB
text/typescript
import { getLogger } from 'omelox-logger';
let logger = getLogger('omelox-rpc', 'ws-acceptor');
import { EventEmitter } from 'events';
import { Tracer } from '../../util/tracer';
import * as utils from '../../util/utils';
import * as sio from 'socket.io';
import * as util from 'util';
import * as Coder from '../../util/coder';
export interface AcceptorPkg {
source: string;
remote: string;
id: string & number;
seq: number;
msg: string;
}
export interface AcceptorOpts {
interval: number;
bufferMsg: any;
rpcLogger: any;
rpcDebugLog: any;
whitelist: any;
}
export class WSAcceptor extends EventEmitter {
interval: number; // flush interval in ms
bufferMsg: any;
rpcLogger: any;
rpcDebugLog: any;
whitelist: any;
_interval: any; // interval object
sockets: { [key: string]: any };
msgQueues: { [key: string]: any };
cb: any;
inited: boolean;
server: any;
closed: boolean;
constructor(opts: AcceptorOpts, cb: (tracer: Tracer, msg?: any, cb?: Function) => void) {
super();
this.bufferMsg = opts.bufferMsg;
this.interval = opts.interval; // flush interval in ms
this.rpcDebugLog = opts.rpcDebugLog;
this.rpcLogger = opts.rpcLogger;
this.whitelist = opts.whitelist;
this._interval = null; // interval object
this.sockets = {};
this.msgQueues = {};
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 = sio.listen(port);
this.server.set('log level', 0);
this.server.server.on('error', function (err: Error) {
logger.error('rpc server is error: %j', err.stack);
self.emit('error', err);
});
this.server.sockets.on('connection', function (socket: any) {
self.sockets[socket.id] = socket;
self.emit('connection', {
id: socket.id,
ip: socket.handshake.address.address
});
socket.on('message', function (pkg: AcceptorPkg) {
try {
if (pkg instanceof Array) {
self.processMsgs(socket, self, pkg);
} else {
self.processMsg(socket, self, pkg);
}
} catch (e) {
// socke.io would broken if uncaugth the exception
logger.error('rpc server process message error: %j', e.stack);
}
});
socket.on('disconnect', function (reason: string) {
delete self.sockets[socket.id];
delete self.msgQueues[socket.id];
});
});
this.on('connection', self.ipFilter.bind(this));
if (this.bufferMsg) {
this._interval = setInterval(function () {
self.flush(self);
}, this.interval);
}
}
ipFilter(obj: any) {
if (typeof this.whitelist === 'function') {
let self = this;
self.whitelist(function (err: Error, tmpList: Array<any>) {
if (err) {
logger.error('%j.(RPC whitelist).', err);
return;
}
if (!Array.isArray(tmpList)) {
logger.error('%j is not an array.(RPC whitelist).', tmpList);
return;
}
if (!!obj && !!obj.ip && !!obj.id) {
for (let i in tmpList) {
let exp = new RegExp(tmpList[i]);
if (exp.test(obj.ip)) {
return;
}
}
let sock = self.sockets[obj.id];
if (sock) {
sock.disconnect('unauthorized');
logger.warn('%s is rejected(RPC whitelist).', obj.ip);
}
}
});
}
}
close() {
if (!!this.closed) {
return;
}
this.closed = true;
if (this._interval) {
clearInterval(this._interval);
this._interval = null;
}
try {
this.server.server.close();
} catch (err) {
logger.error('rpc server close error: %j', err.stack);
}
this.emit('closed');
}
cloneError(origin: { msg: any, stack: Error }) {
// copy the stack infos for Error instance json result is empty
let res = {
msg: origin.msg,
stack: origin.stack
};
return res;
}
processMsg(socket: any, acceptor: WSAcceptor, 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', 'ws-acceptor receive message and try to process message');
}
acceptor.cb(tracer, pkg.msg, () => {
// let args = arguments;
let args = Array.prototype.slice.call(arguments, 0);
let errorArg: { msg: any, stack: Error } = args[0]; // first callback argument can be error object, the others are message
if (errorArg instanceof Error) {
args[0] = this.cloneError(errorArg);
}
// for(let i=0, l=args.length; i<l; i++) {
// if(args[i] instanceof Error) {
// args[i] = cloneError(args[i]);
// }
// }
let resp: any;
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
};
// resp = {id: pkg.id, resp: Array.prototype.slice.call(args, 0)};
}
if (acceptor.bufferMsg) {
this.enqueue(socket, acceptor, resp);
} else {
socket.emit('message', resp);
}
});
}
processMsgs(socket: any, acceptor: WSAcceptor, pkgs: Array<AcceptorPkg>) {
for (let i = 0, l = pkgs.length; i < l; i++) {
this.processMsg(socket, acceptor, pkgs[i]);
}
}
enqueue(socket: any, acceptor: WSAcceptor, msg: Coder.Msg) {
let queue = acceptor.msgQueues[socket.id];
if (!queue) {
queue = acceptor.msgQueues[socket.id] = [];
}
queue.push(msg);
}
flush(acceptor: WSAcceptor) {
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;
}
socket.emit('message', queue);
queues[socketId] = [];
}
}
}
/**
* 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 WSAcceptor(opts || <any>{}, cb);
}