UNPKG

scalra

Version:

node.js framework to prototype and scale rapidly

411 lines (326 loc) 12.7 kB
/* // frontier.js // // common app to init / stop a server with socket handling // // history: // 2012-04-09 initial version // 2012-04-22 aere_game_frontier also adopts this class // 2012-05-10 include connection handling mechanism (conn) // 2013-09-13 extract out socket server into 'listener' functions: */ // // main frontier // // ip_port contains ip & port // /* conn_handler: { onConnect: 'function', onDisconnect: 'function' } config: { path: 'string', // path where the frontier is located conn_handler: 'object', // optional connection handler handlers: 'array', components: 'array', modules: 'object' } a module should be specified in frontier's config in the following way: modules: { cloud_connector: {server: {IP: 'src.scalra.com', port: 37070}, name: 'LocalServer', serial: UTIL.createUUID()} } */ var l_name = 'SR.Frontier'; exports.icFrontier = function (config) { // check for port only config if (typeof config === 'number') { config = {lobbyPort: config, components: []}; } // default to empty config config = config || {components: []}; LOG.debug('path in SR: ' + __dirname, l_name); // reference to still access current object despite going into callbacks var that = this; // load project settings // NOTE: previously we assume that global.g_settings is defined inside the project 'setting.js' file // now all settings should belong to SR.Settings var setting_file = ((process.argv[2] === undefined || process.argv[2].indexOf('settings') === -1) ? 'settings.js' : process.argv[2]); // NOTE: we assume setting file is one level above the frontier directory var setting_path = SR.path.join(SR.FRONTIER_PATH, '..', setting_file); if (SR.fs.existsSync(setting_path)) { LOG.sys('requiring: ' + setting_path, l_name); // storing setting path so we may access later (for example, when updating DB config in settings.js) // TODO: this needs to be fixed, not a clean approach SR.Settings.PATH_SETTINGS = setting_path; SR.Settings.Project = require(setting_path).settings; // use previous assumption of a global setting variable // NOTE: this is obsolete usage and should be discouraged if (global.hasOwnProperty('g_settings')) { LOG.warn('global variable g_settings exists, use as project settings (NOTE: this is obsolete usage)', l_name); SR.Settings.Project = g_settings; } } // TODO: should provide better defaults if (typeof SR.Settings.Project !== 'object') { LOG.warn('project settings not found at \'' + setting_path + '\', use defaults...', l_name); SR.Settings.Project = {}; } // set lobby port SR.Settings.Project.lobbyPort = config.lobbyPort || SR.Settings.Project.lobbyPort || 37070; // override system settings if there are project-specific settings of same names for (var key in SR.Settings.Project) { if (SR.Settings.hasOwnProperty(key) === false) { continue; } if ((typeof SR.Settings.Project[key] === 'string' && SR.Settings.Project[key] === '') || (typeof SR.Settings.Project[key] === 'object' && Object.keys(SR.Settings.Project[key]).length === 0)) { continue; } // augmnet the setting if it's an object, replace directly for strings if (typeof SR.Settings.Project[key] === 'string') { SR.Settings[key] = SR.Settings.Project[key]; } else { for (var setting_key in SR.Settings.Project[key]) { SR.Settings[key][setting_key] = SR.Settings.Project[key][setting_key]; } } } // override system & project-specific settings with command line settings var argv = require('minimist')(process.argv.slice(2)); //console.dir(argv); for (var key in argv) { if (key === '_' || SR.Settings.hasOwnProperty(key) === false) continue; LOG.warn('[' + key + ']: ' + argv[key], l_name); SR.Settings[key] = argv[key]; } // set unique ID for this frontier this.id = UTIL.createUUID(); var l_frontierName = undefined; // build module path from a root path var l_buildModulePath = function (root_path, default_prefix) { var arr = SR.Settings.SR_PATH.split(SR.path.sep); var prefix = arr[arr.length-1] + '-'; var dirs = UTIL.getDirectoriesSync(root_path); //LOG.warn('root_path: ' + root_path, l_name); //LOG.warn(dirs, l_name); if (default_prefix) prefix = default_prefix; //LOG.warn('default_prefix: ' + default_prefix + ' prefix: ' + prefix + ' paths to check:', l_name); for (var i in dirs) { //LOG.warn(dirs[i]); if (dirs[i].startsWith(prefix)) { SR.Settings.MOD_PATHS.push(SR.path.join(root_path, dirs[i])); } } }; // determine proper server info // lobby_port_opened: true/false var l_createServerInfo = function () { // extract frontier name from path, avoid unusable characters var words = SR.Settings.FRONTIER_PATH.replace(':', ']').split(SR.path.sep); //LOG.debug('path split:', l_name); //LOG.debug(words, l_name); var owner = words[words.length-3]; var project = words[words.length-2]; var name = words[words.length-1]; // get one level deeper for scalra system servers (monitor, entry...) if (owner === 'scalra' && (name === 'monitor' || name === 'entry')) { owner = words[words.length-4]; project = words[words.length-3]; } LOG.warn('extracting server info from path... \n[owner]: ' + owner + '\n[project]: ' + project + '\n[name]: ' + name, l_name); // store as server_info SR.Settings.SERVER_INFO = { id: that.id, owner: owner, project: project, name: name }; // we use type as name // NOTE: this will be the default DB name used by this frontier as well l_frontierName = (owner + '-' + project + '-' + name); // determine server type if (project === 'scalra') { // for monitor or entry servers // NOTE: we take only the name before the first '.' as type SR.Settings.SERVER_INFO.type = name.split('.')[0]; } // for 'lobby' or 'app' else if (name === 'lobby' && process.argv[2] !== 'app') SR.Settings.SERVER_INFO.type = 'lobby'; else SR.Settings.SERVER_INFO.type = 'app'; LOG.warn('server: ' + l_frontierName + ' type: ' + SR.Settings.SERVER_INFO.type, l_name); }; // store frontier path SR.Settings.FRONTIER_PATH = SR.FRONTIER_PATH; // store path to project's base directory SR.Settings.PROJECT_PATH = SR.path.resolve(SR.FRONTIER_PATH, '..'); l_createServerInfo(); // TODO: remove this if possible/ // right now is used by SR.Component SR.Settings.FRONTIER = this; SR.Settings.SR_PATH = SR.path.resolve(__dirname, '..'); LOG.warn('set SR.Settings.FRONTIER_PATH to: ' + SR.Settings.FRONTIER_PATH, l_name); LOG.warn('set SR.Settings.PROJECT_PATH to: ' + SR.Settings.PROJECT_PATH, l_name); LOG.warn('set SR.Settings.SR_PATH to: ' + SR.Settings.SR_PATH, l_name); // store paths to modules SR.Settings.MOD_PATHS = []; var root_path = SR.path.join(SR.Settings.SR_PATH, '..'); var project_module_path = SR.path.join(SR.Settings.PROJECT_PATH, 'node_modules'); // NOTE: we give priority for project modules before others l_buildModulePath(project_module_path, 'scalra'); l_buildModulePath(root_path); // we search for modules one-level deeper // NOTE: appears to be not necessary now //l_buildModulePath(SR.path.join(root_path, '..'), 'scalra'); // add project's 'modules' directory to it SR.Settings.MOD_PATHS.push(SR.path.join(SR.Settings.FRONTIER_PATH, '..')); LOG.debug('module paths:', l_name); LOG.debug(SR.Settings.MOD_PATHS, l_name); // // local variables // // flag to indicate shutdown var l_shutting_down = false; // flag to indicate if server is ready var l_ready = false; // // public functions // // get connection handler this.getConnectionHandler = function () { LOG.error('getConnectionHandler: this method is obsolete, please remove its usage..', l_name); LOG.stack(); var server = SR.Call('socketserver.get'); return (server ? server.getConnectionHandler() : undefined); }; // get frontier's name this.getName = function () { return l_frontierName; }; // enable logging //var stepLog = SR.Component.Log(SR.Settings.FRONTIER_PATH, SR.Settings.SERVER_INFO.name); // init necessary data for server execution this.init = function (onDone) { // actually execute the steps SR.Module.start(function () { // call other registered callbacks upon server start LOG.warn('calling all onStart functions...', l_name); SR.Callback.notify('onStart'); UTIL.safeCall(onDone); }); }; //----------------------------------------- // shutdown all connected sockets (clients) // NOTE: this can also be called from outside via 'dispose' method var l_dispose = this.dispose = function (onDone) { LOG.warn('trigger dispose (close all sockets and shutdown)...', l_name); // if we're already shutting down (e.g., Ctrl-C is pressed again) // then perform force shutdown if (l_shutting_down === true) { LOG.warn('already shutting down, force close-down in 3 seconds', l_name); setTimeout(function () { process.exit(); }, 3000); return; } l_shutting_down = true; // let app-level stop steps be executed SR.Callback.notify('onStop'); SR.Module.stop(function () { UTIL.safeCall(onDone); process.exit(); }); }; // check whether server is successfully created this.isServerReady = function () { return l_ready; }; // get ip & port info for this frontier this.getHostAddress = function () { return SR.Settings.SERVER_INFO; }; // get ip & port info for this frontier this.setHostAddress = function (addr) { LOG.error('setHostAddress: this method is obsolete, please remove its usage..', l_name); LOG.stack(); //ip_port = addr; }; // // steps to init/dispose // SR.Module.load('log', config); SR.Module.load('key_loader', config); SR.Module.load('socketserver', config); //SR.Module.addStep(stepLog); // load application handlers if (config.handlers) { LOG.warn('load handlers specified in config, size: ' + config.handlers.length, l_name); // add handlers, if available for (var i=0; i < config.handlers.length; i++) SR.Handler.addByFile(config.handlers[i], SR.Settings.FRONTIER_PATH); } // NOTE: this has to be loaded here (cannot load in initServer, because Log needs to be init before initServer) // TODO: change this... // load default components SR.Module.addStep(SR.Component.REST()); SR.Module.addStep(SR.Component.SockJS()); // determine if we should load AppManager or AppConnector components depending on server type if (SR.Settings.ENABLE_CLUSTER_MODE) { if (SR.Settings.SERVER_INFO.type === 'lobby') SR.Module.addStep(SR.Component.AppManager()); else if (SR.Settings.SERVER_INFO.type === 'app') SR.Module.addStep(SR.Component.AppConnector()); } else { // force this server as a standalone 'lobby' (for single-file servers) SR.Settings.SERVER_INFO.type = 'lobby'; } // add components, if available if (config.components && config.components instanceof Array) { LOG.warn('loading components from Frontier config...', l_name); for (var i=0; i < config.components.length; i++) { // usage of user-specified AppManager or AppConnector is obsolete // ignore & provide warning if (config.components[i].name === 'AppManager') { LOG.warn('AppManager is now auto-loaded, please remove it from config.components', l_name); continue; } if (config.components[i].name === 'AppConnector') { LOG.warn('AppConnector is now auto-loaded, please remove it from config.components', l_name); continue; } if (config.components[i].name === 'REST-HTTP') { LOG.warn('REST (HTTP) is now auto-loaded, please remove it from config.components', l_name); continue; } if (config.components[i].name === 'SockJS-HTTP') { LOG.warn('SockJS (HTTP) is now auto-loaded, please remove it from config.components', l_name); continue; } SR.Module.addStep(config.components[i]); } } // add modules, if available // NOTE: config.modules contains module 'name' and 'config' ONLY if (config.modules) { for (var name in config.modules) { SR.Module.load(name, config.modules[name]); } } // load default modules SR.Module.load('system', config); SR.Module.load('account', config); //SR.Module.load('gmail', config); // load monitor-related functions if specified if (SR.Settings.CONNECT_MONITOR_ONSTART) { SR.Module.load('reporting', config); } // change process / group id in the end (if another identity is desired for server execution) SR.Module.load('owner_switcher', config); }; // end icFrontier