UNPKG

scalra

Version:

node.js framework to prototype and scale rapidly

447 lines (358 loc) 13.1 kB
// // RPC.js // // Remote Procedural Call (RPC) service between servers // // history: // 2014-03-02 extracted from app_connector (remoteAction) and // app_manager_handler.js (APP_RPC event handling, renamed as SR_SYS_RPC) // 2014-03-11 put event handler for SR_SYS_RPC here in this file (first usage in scalra core functions) // // functions: // // addServer(name, info, conn) // add a server info with its connection object // removeServer(conn) // remove a server by its connection object // queryServer() // query currently registered servers // registerConnector(connector) // record default connector to use // call(svc_name, func_name, parameters, connector) // make an RPC call to a remote server via a connector (app -> lobby) // remoteEvent(server_name, event_type, para, onDone) // send an event to remote server for processing // relayEvent(server_name, event_type, event) // relay an event (without changing event object) to a remote server for processing // var l_connector = undefined; var l_localinfo = undefined; // list of connected servers /* example info in l_servers { "id": "0EB091F3-388D-463E-A3F5-D9F86D004C58", "owner": "test", "project": "Cluster_Test", "name": "lobby", "type": "lobby", "IP": "211.78.245.176", "port": 37220, conn: 'object' } */ var l_servers = SR.State.get('SR.RPC.servers'); // callbacks to call once a event is processed remotely and returned var l_pendingEvents = SR.State.get('SR.RPC.events'); // record default connector exports.registerConnector = function (connector) { l_connector = connector; }; // store my own server info for RPC event handling // local_info format: // {name: 'string', info: 'object'} exports.registerLocal = function (local_info) { l_localinfo = local_info; }; /* example info { "id": "0EB091F3-388D-463E-A3F5-D9F86D004C58", "owner": "test", "project": "Cluster_Test", "name": "lobby", "type": "lobby", "IP": "211.78.245.176", "port": 37220, } */ // add a server info with its connection object var l_addServer = exports.addServer = function (name, info, conn) { // if already exists then fail if (l_servers.hasOwnProperty(name)) { LOG.error('server [' + name + '] already registered...', 'SR.RPC'); return false; } LOG.warn('remote server [' + name + '] connected, add to pool...', 'SR.RPC'); // NOTE: we clone a copy of the object to avoid attaching conn object to it will complicate original info l_servers[name] = UTIL.clone(info); l_servers[name].conn = conn; return true; }; // remove a server by its connection object var l_removeServer = exports.removeServer = function (conn) { for (var name in l_servers) { LOG.warn('checking [' + name + ']...', 'SR.RPC'); //console.log(l_servers[name].conn); if (l_servers[name].conn.connID === conn.connID) { LOG.warn('remote server [' + name + '] disconnected, remove from pool...', 'SR.RPC'); delete l_servers[name]; return true; } } return false; }; // query currently registered servers (without conn objects) // TODO: better approach than copy everything? var l_queryServer = exports.queryServer = function () { var list = {}; // strip off conn object for (var id in l_servers) { LOG.warn(l_servers[id], 'SR.RPC'); list[id] = {}; for (var name in l_servers[id]) if (name !== 'conn') list[id][name] = l_servers[id][name]; } return list; }; // make an RPC call to a remote server via a connector // NOTE: last argument in parameters is a onDone callback exports.call = function (svc_name, func_name, parameters, connector) { connector = connector || l_connector; // convert all arguments to array var args = Array.prototype.slice.call(parameters); // check if last argument is callback var onDone = args[args.length-1]; var has_callback = (typeof onDone === 'function'); // remove last argument if it's a callback function if (has_callback) args.pop(); else onDone = undefined; LOG.warn('func_name: ' + func_name + ' return by callback: ' + has_callback, 'SR.RPC'); if (!connector || typeof connector.send !== 'function') { LOG.error('cannot call remote service, connector missing', 'SR.RPC'); return false; } // send an event with an expected response type connector.send('SR_SYS_RPC', {grp: svc_name, func: func_name, cb: has_callback, args: args}, 'SRR_SYS_RPC', function (event) { LOG.sys('SRR_SYS_RPC received', 'SR.RPC'); // if no callback provided, simply print out return values if (onDone) UTIL.safeCall(onDone, event.data.res); else LOG.warn('RPC call to [' + svc_name + '.' + func_name + '] returns: ' + event.data.res, 'SR.RPC'); } ); return true; }; // send an event to remote server for processing // NOTE: this works similarly as notifyLobby in SR.AppConnector // onDone = function (type, para) exports.remoteEvent = function (server_name, event_type, para, onDone) { // build custom event // create a new remote event with a specific done function to perform further processing // NOTE: we need to prepare the event object as a 'raw' event, // different from a 'dispatched' event processed by handlers, // as 'E' parameter is extracted as 'msg_type' para = para || {}; LOG.warn('server_name: ' + server_name + ' build new event: ' + event_type); LOG.warn(para); var event = SR.EventManager.createEvent(event_type, para, function (res_obj) { LOG.sys('get remote response for event [' + event_type + ']: ', 'SR.RPC'); LOG.sys(res_obj, 'SR.RPC'); UTIL.safeCall(onDone, res_obj[SR.Tags.PARA]); }, { type: 'relay' }); event.msg_type = event.data[SR.Tags.EVENT]; event.data = event.data[SR.Tags.PARA]; // TODO: relayEvent can perhaps accept more direct input? (than having to create an event object first) // NOTE: if server is not registered, will return false, and undefined is returned if (l_relayEvent(server_name, event_type, event) === false) { UTIL.safeCall(onDone); } }; // relay an event (without changing event object) to a remote server for processing var l_relayEvent = exports.relayEvent = function (server_name, event_type, event) { // first check if the event should be processed by myself (local server) if (l_localinfo && l_localinfo.name === server_name) { LOG.warn('[' + event_type + '] is a local event, process locally', 'SR.RPC'); return false; } LOG.warn('checking: ' + server_name, 'SR.RPC'); // if server name cannot be found immeidately, assuming it's one of the app server and try to find its ID if (l_servers.hasOwnProperty(server_name) === false) { // check if server name is an available app name for (var id in l_servers) { LOG.warn('checking appID: ' + id, 'SR.RPC'); LOG.warn(l_servers[id].name, 'SR.RPC'); if (l_servers[id].name === server_name) { server_name = id; break; } } } // if the server is not registered, also do not relay if (l_servers.hasOwnProperty(server_name) === false) { LOG.warn('target server [' + server_name + '] not registered, process locally', 'SR.RPC'); return false; } LOG.sys('relay event [' + event_type + '] to server [' + server_name + ']', 'SR.RPC'); var info = l_servers[server_name]; // build packet to send var packet = { type: event_type, data: event.data, id: UTIL.createID(), host: event.conn.host, port: event.conn.port, cookie: event.conn.cookie }; // store callback when event processing result is returned l_pendingEvents[packet.id] = event; // send away the event to remote server for processing SR.EventManager.send('SR_SYS_RPC_EVENT', packet, [info.conn]); return true; }; // // event handlers // // a pool for all message handlers var l_handlers = exports.handlers = {}; // a pool for all message handlers var l_checkers = exports.checkers = {}; // RPC handling from other servers l_checkers.SR_SYS_RPC = {}; l_handlers.SR_SYS_RPC = function (event) { var group = event.data.grp; var func = event.data.func; var args = event.data.args; var has_callback = event.data.cb; // define what to return var onDone = function (result) { var return_obj = {func: func, res: result}; event.done('SRR_SYS_RPC', return_obj); }; // check if we need to add onDone to arguments if (has_callback) args.push(onDone); // execute function // check if it's SR functions if (group.startsWith('SR.')) { group = group.substring(3); LOG.warn('group: ' + group, 'SR.RPC'); SR[group][func].apply(this, args); } else { if (global.hasOwnProperty(group) === false) { var msg = 'no handler by name [' + group + '] found'; LOG.warn(msg, 'SR.RPC'); return onDone(msg); } // NOTE: this could be a dangerous operation (potential security issues) try { // make calls to the specified function var result = global[group][func].apply(this, args); // see if need to actively return if (has_callback === false) onDone(result); } catch(e) { LOG.error(e, 'SR.RPC'); onDone(e); } } }; var l_serial_checker = undefined; // TODO: cleaner appraoch for serial checks? exports.registerSerialChecker = function (checker) { if (typeof checker === 'function') l_serial_checker = checker; else LOG.error('checker is not a valid function', 'SR.RPC'); }; // process a registeration request of the local server l_checkers.SR_REGISTER_SERVER = { name: 'string', info: 'object' }; l_handlers.SR_REGISTER_SERVER = function (event) { LOG.warn('SR_REGISTER_SERVER called', 'SR.RPC'); LOG.warn(event.data, 'SR.RPC'); // check reg code var result = true; if (l_serial_checker) { if (l_serial_checker(event.data.serial)) { LOG.warn('serial registeration success: ' + event.data.serial, 'SR.RPC'); l_addServer(event.data.name, event.data, event.conn); } else { result = false; LOG.warn('serial registeration failed: ' + event.data.serial, 'SR.RPC'); } } event.done('SR_REGISTER_SERVER_R', {name: event.data.name, result: result}); }; // process relayEvent requests l_checkers.SR_SYS_RPC_EVENT = { type: 'string', data: 'object', id: 'number', host: 'string', port: 'number' }; l_handlers.SR_SYS_RPC_EVENT = function (event) { var event_type = event.data.type; var para = event.data.data; LOG.sys('SR_SYS_RPC_EVENT called, event_type: ' + event_type + ' para: ', 'SR.RPC'); LOG.sys(para, 'SR.RPC'); // create a new event with a specific done function to return results var relayed_event = SR.EventManager.createEvent(event_type, para, function (res_obj) { event.done('SRR_SYS_RPC_EVENT', {type: res_obj.U, data: res_obj.P, id: event.data.id}); }, { // NOTE: we pass in the host/port/cookie of the original client host: event.data.host, port: event.data.port, type: 'relay', cookie: event.data.cookie }); // process event using dispatcher, and return results SR.Handler.dispatch(relayed_event); }; // process relayEvent responses l_checkers.SRR_SYS_RPC_EVENT = { type: 'string', data: 'object', id: 'number' }; l_handlers.SRR_SYS_RPC_EVENT = function (event) { LOG.warn('SRR_SYS_RPC_EVENT called', 'SR.RPC'); // no need to return anything event.done(); var result = event.data; if (l_pendingEvents.hasOwnProperty(result.id) === false) { LOG.error('received response to an event with unknown id: ' + result.id, 'SR.RPC'); return; } LOG.warn('got response for relayed event, packet id: ' + result.id, 'SR.RPC'); var pending_event = l_pendingEvents[result.id]; // deliver the response to the event to the original event handler pending_event.done(result.type, result.data); delete l_pendingEvents[result.id]; // TODO: record how long it took for the remote event to finish? // TODO: if event takes too long to return, can we indicate there's a timeout error? }; // // init / dispose actions during server start/stop or connect/disconnect // // register handlers when server starts SR.Callback.onStart(function () { var handler_set = { handlers: l_handlers, checkers: l_checkers }; // register RPC handlers for both lobby server and AppManager (if this is a lobby server) // NOTE: better approach than install twice? LOG.sys('load RPC handlers for main server...', 'SR.RPC'); SR.Handler.add(handler_set); if (SR.Settings.SERVER_INFO.type === 'lobby') { LOG.sys('load RPC handlers for AppManager...', 'SR.RPC'); SR.Handler.add(handler_set, 'manager'); } }); // remove stored local server info when socket is disconnected SR.Callback.onDisconnect(function (conn) { l_removeServer(conn); }); // when app server joins SR.Callback.onAppServerStart(function (info, conn) { LOG.warn('adding new AppServer (id: ' + info.id + '):', 'SR.RPC'); LOG.warn(info, 'SR.RPC'); l_addServer(info.id, info, conn); }); // when app server leaves SR.Callback.onAppServerStop(function (info, conn) { LOG.warn('AppServer stops: ' + info.id, 'SR.RPC'); l_removeServer(conn); });