Reference Source

js/servers/server.js

// NPM IMPORTS
import assert from 'assert'
import { fromJS } from 'immutable'

// COMMON IMPORTS
import T                   from 'devapt-core-common/dist/js/utils/types'
import DistributedInstance from 'devapt-core-common/dist/js/base/distributed_instance'

// SERVER IMPORTS
import runtime from '../base/runtime'
import AuthenticationWrapper from '../security/authentication_wrapper'


const context = 'server/servers/server'



/**
 * Type of servers.
 * @public
 */
export const ServerTypes = {
	SERVER_TYPE_EXPRESS : 'express',
	SERVER_TYPE_RESTIFY : 'restify',
	SERVER_TYPE_SOCKETIO : 'socketio',
	SERVER_TYPE_FEATHER : 'feather',
	SERVER_TYPE_CLUSTER : 'cluster',
	SERVER_TYPE_LOGS : 'logs',
	SERVER_TYPE_METRICS : 'metrics'
}


/**
 * @file Server base class.
 * @author Luc BORIES
 * @license Apache-2.0
 */
export default class Server extends DistributedInstance
{
	/**
	 * Create a server instance.
	 * @extends DistributedInstance
	 * 
	 * 	API:
	 * 		->constructor(arg_name, arg_class, arg_settings, arg_log_context=context)
	 * 
	 * 		->use_service_on_loading(arg_application, arg_service, arg_app_svc_cfg = {}):nothing - Add a service to use before or after security check.
	 * 		
	 * 		->get_server_security_settings():object - Get security settings object into the server.
	 * 		->get_security_settings():Immutable.Map - Get security settings from server or runtime.
	 * 
	 * 		->load():nothing - Load server settings.
	 * 
	 * 		->enable():nothing - Enable server (start it).
	 * 		->disable():nothing - Disable server (stop it).
	 * 
	 * 		->get_topology_info(arg_deep=true, arg_visited={}):object - Get topology item informations.
	 * 
	 * @param {string} arg_name - server name.
	 * @param {string} arg_class - server class name.
	 * @param {object} arg_settings - plugin settings map.
	 * @param {string} arg_log_context - trace context string.
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_name, arg_class, arg_settings, arg_log_context=context)
	{
		
		// DEBUG STORE
		// console.log(store, 'store')
		// console.log(config, 'config')
		
		if (! T.isObject(arg_settings))
		{
			console.error(arg_class, arg_name, arg_settings, arg_log_context, 'arg_class, arg_name, arg_settings, arg_context')
		}
		super('servers', arg_name, (arg_class ? arg_class.toString() : 'Server'), arg_settings, arg_log_context)
		
		this.is_server = true
		this.is_build = false
		
		this.server_host = undefined
		this.server_port = undefined
		this.server_protocole = undefined
		this.server_type = undefined
		
		this.server = undefined
		this.server_http = undefined
		this.serverio = undefined
		
		this.services_without_security = [] // STORE PLAIN OBJECTS WITH THIS FORMAT: { app:application instance, svc:service instance, cfg:application service config }
		this.services_with_security = [] // STORE PLAIN OBJECTS WITH THIS FORMAT: { app:application instance, svc:service instance, cfg:application service config }
		
		this.authentication = new AuthenticationWrapper(arg_log_context, this.get_logger_manager())
		// this.authorization = new AuthorizationWrapper(arg_log_context ? arg_log_context : context)
	}



	/**
	 * Add a service to use before or after security check.
	 * 
	 * @param {Application}	arg_application
	 * @param {Service}		arg_service
	 * @param {object}		arg_app_svc_cfg (default:{})
	 * 
	 * @returns {nothing}
	 */
	use_service_on_loading(arg_application, arg_service, arg_app_svc_cfg = {})
	{
		assert( T.isObject(arg_application) && arg_application.is_topology_define_application, context + ':bad application object')
		assert( T.isObject(arg_service) && arg_service.is_service, context + ':bad service object')
		assert( T.isObject(arg_app_svc_cfg) , context + ':bad application service config object')

		this.info('use_service_on_loading:service=' + arg_service.get_name())

		const security_enabled = arg_service.get_setting(['security', 'enabled'], true)
		const authentication_enabled = arg_service.get_setting(['security', 'authentication', 'enabled'], true)
		const authorization_enabled = arg_service.get_setting(['security', 'authorization', 'enabled'], true)
		const use_security_check = security_enabled ? (authentication_enabled || authorization_enabled) : false

		const record = {
			app:arg_application,
			svc:arg_service,
			cfg:arg_app_svc_cfg
		}

		if (use_security_check)
		{
			this.services_with_security.push(record)
			return
		}

		this.services_without_security.push(record)
	}

	
	
	/**
	 * Get security settings object into the server.
	 * 
	 * @returns {object} - security settings.
	 */
	get_server_security_settings()
	{
		this.enter_group('get_server_security_settings')
		
		const security_settings = this.get_setting('security', null)
		
		this.leave_group('get_server_security_settings')
		return security_settings
	}
	
	
	
	/**
	 * Get security settings from server or runtime.
	 * 
	 * @returns {Immutable.Map} - security settings.
	 */
	get_security_settings()
	{
		const self = this
		self.enter_group('get_security_settings')
		
		// DEFAULT SECURITY SETTINGS
		const default_security = {
			'authentication': {
				'enabled': true,
				'plugins': []
			},
			'authorization': {
				'enabled': true,
				'plugins': []
			}
		}
		
		// TARGET SECURITY SETTINGS
		let security_settings = fromJS(default_security)
		
		// GET SERVER SECURITY SETTINGS
		// console.log(context + '.get_security_settings', this.get_name(), this.$settings)
		let srv_security_settings = self.get_setting('security', null)
		// console.log(context + '.get_security_settings:srv_security_settings', srv_security_settings)
		// assert( T.isObject(srv_security_settings), context + ':bad srv_security_settings object')
		
		if ( srv_security_settings && ! T.isFunction(srv_security_settings.has) )
		{
			srv_security_settings = fromJS(srv_security_settings)
		}
		
		if ( srv_security_settings && srv_security_settings.has('authentication') )
		{
			// AUTHENTICATION IS ENABLED ?
			const auth_is_enabled = srv_security_settings.getIn(['authentication', 'enabled'], false)
			if (! auth_is_enabled)
			{
				// self.error('get_security_settings:bad server.settings.security.authentication.enabled')
				// self.leave_group('get_security_settings:from server with error')
				self.leave_group('get_security_settings:from server with disabled authentication')
				return security_settings
			}
			security_settings = security_settings.setIn(['authentication', 'enabled'], auth_is_enabled)
			
			/*if ( srv_security_settings.hasIn(['authentication', 'enabled']) )
			{
				const enabled = srv_security_settings.getIn(['authentication', 'enabled'])
				security_settings = security_settings.setIn(['authentication', 'enabled'], enabled)
			}
			else
			{
				// self.error('get_security_settings:bad server.settings.security.authentication.enabled')
				// self.leave_group('get_security_settings:from server with error')
				self.leave_group('get_security_settings:from server with disabled authentication')
				return security_settings
			}*/
			
			// AUTHENTICATION PLUGINS
			if ( srv_security_settings.hasIn(['authentication', 'plugins']) )
			{
				const plugins = srv_security_settings.getIn(['authentication', 'plugins'])
				security_settings = security_settings.setIn(['authentication', 'plugins'], plugins.toArray())
				if (plugins.size == 0)
				{
					self.error('bad server.settings.security.authentication.plugins.size')
					self.leave_group('get_security_settings:from server with error')
					return security_settings
				}
			}
			else
			{
				self.error('get_security_settings:bad server.settings.security.authentication.plugins')
				self.leave_group('get_security_settings:from server with error')
				return security_settings
			}
			
			// console.log(context + '.get_security_settings:from server:security_settings', security_settings)
			self.leave_group('get_security_settings:from server')
			return security_settings
		}
		
		
		
		// GET RUNTIME SECURITY SETTINGS
		const rt_security_settings = runtime.get_registry().root.get('security')
		assert( T.isObject(rt_security_settings) && rt_security_settings.has, context + ':bad rt_security_settings object')
		
		if ( rt_security_settings.has('enabled') )
		{
			const enabled = rt_security_settings.get('enabled')
			security_settings = security_settings.set('enabled', enabled)
		} else {
			security_settings = security_settings.setIn(['authentication', 'enabled'], false)
			self.warn('get_security_settings:security is disabled')
			self.leave_group('get_security_settings:security is disabled')
			return security_settings
		}
		// else
		// {
		// 	self.error('get_security_settings:bad runtime.settings.security.enabled')
		// 	self.leave_group('get_security_settings:from runtime with error')
		// 	return security_settings
		// }
		
		// GET RUNTIME AUTHENTICATION SECURITY SETTINGS
		if ( rt_security_settings && rt_security_settings.has('authentication') )
		{
			// AUTHENTICATION IS ENABLED ?
			if ( rt_security_settings.hasIn(['authentication', 'enabled']) )
			{
				const enabled = rt_security_settings.getIn(['authentication', 'enabled'])
				security_settings = security_settings.setIn(['authentication', 'enabled'], enabled)
			}
			else
			{
				self.error('get_security_settings:bad runtime.settings.security.authentication.enabled')
				self.leave_group('get_security_settings:from runtime with error')
				return security_settings
			}
			
			// AUTHENTICATION PLUGINS
			if ( rt_security_settings.hasIn(['authentication', 'default_plugins']) )
			{
				const plugins = rt_security_settings.getIn(['authentication', 'default_plugins'])
				security_settings = security_settings.setIn(['authentication', 'plugins'], plugins.toArray())
				if (plugins.size == 0)
				{
					self.error('bad runtime.settings.security.authentication.plugins.size')
					self.leave_group('get_security_settings:from runtime with error')
					return security_settings
				}
			}
			else
			{
				self.error('get_security_settings:bad runtime.settings.security.authentication.default_plugins')
				self.leave_group('get_security_settings:from runtime with error')
				return security_settings
			}
			
			self.leave_group('get_security_settings:from runtime')
			return security_settings
		}
		
		
		// console.error(context + ':no security settings found')
		self.error(context + ':no security settings found')
		self.leave_group('get_security_settings:not found')
		return null
	}
	
	
	
	/**
	 * Load server settings.
	 * 
	 * @returns {nothing}
	 */
	load()
	{
		this.enter_group('load')
		
		// DEBUG
		// debugger

		assert( T.isObject(this.$settings) && T.isFunction(this.$settings.has), context + ':bad settings object')
		
		const cfg = runtime.get_registry().root
		
		// SET SERVER HOST
		this.server_host = this.$settings.has('host') ? this.$settings.get('host') : null
		if ( ! this.server_host && cfg.hasIn(['servers', 'default', 'host']) )
		{
			this.server_host = cfg.getIn(['servers', 'default', 'host'])
		}
		assert( T.isString(this.server_host), context + ':bad server host string:[' + this.server_host + ']')
		
		// SET SERVER PORT
		this.server_port = this.$settings.has('port') ? this.$settings.get('port') : null
		if ( ! this.server_port && cfg.hasIn(['servers', 'default', 'port']) )
		{
			this.server_port = cfg.getIn(['servers', 'default', 'port'])
		}
		assert( T.isString(this.server_port) || T.isNumber(this.server_port), context + ':bad server port string|number:[' + this.server_port + ']')
		
		// SET SERVER PROTOCOLE
		this.server_protocole = this.$settings.has('protocole') ? this.$settings.get('protocole') : null
		if ( ! this.server_protocole && cfg.hasIn(['servers', 'default', 'protocole']) )
		{
			this.server_protocole = cfg.getIn(['servers', 'default', 'protocole'])
		}
		assert( T.isString(this.server_protocole), context + ':bad server protocole string:[' + this.server_protocole + ']')
		
		// SET SERVER TYPE
		this.server_type = this.$settings.has('type') ? this.$settings.get('type') : null
		if ( ! this.server_type && cfg.hasIn(['servers', 'default', 'type']) )
		{
			this.server_type = cfg.getIn(['servers', 'default', 'type'])
		}
		assert( T.isString(this.server_type), context + ':bad server type string:[' + this.server_type + ']')
		
		// SECURITY (could be null for Bus Server...)
		const security_settings = this.get_security_settings()
		// console.log(context + '.load:security_settings', security_settings)
		
		// DEBUG
		// debugger

		// AUTHENTICATION
		if ( security_settings && security_settings.hasIn(['authentication', 'enabled']) && security_settings.hasIn(['authentication', 'plugins']) )
		{
			let auth_settings = security_settings.get('authentication')
			auth_settings = auth_settings.set('runtime', this.get_runtime())
			auth_settings = auth_settings.set('logger_manager', this.get_logger_manager())
			this.authentication.load(auth_settings)
		}
		
		// AUTHORIZATION
		// TODO
		
		// PARENT CLASS LOADING
		super.load()
		
		// SUBSCRIBE TO MESSAGES BUS
		this.enable_msg()

		this.leave_group('load')
	}



	/**
	 * Build private server instance.
	 * 
	 * @returns {nothing}
	 */
	build_server()
	{
		// NOTHING TO DO HERE
	}

	
	
	/**
	 * Enable server (start it).
	 * 
	 * @returns {nothing}
	 */
	enable()
	{
		// BUILD SERVER
		if ( ! this.is_build)
		{
			assert( T.isFunction(this.build_server), context + ':bad build_server function')
			this.build_server()
			this.is_build = true
		}

		// ENABLE LISTENER
		const name = this.$name
		const host = this.server_host
		const port = this.server_port
		
		let should_listen = true
		
		// ENABLE LAST LOADING/ACTIONS: ERROR HANDLING...
		if ( T.isFunction(this.finaly) )
		{
			this.finaly()
		}
		
		// ENABLE LISTENER TRACE
		if (should_listen)
		{
			const srv = this.server_http ? this.server_http : this.server
			if ( srv && T.isFunction(srv.listen) )
			{
				srv.listen(this.server_port,
					function()
					{
						console.info('%s listening at %s : %s', name, host, port)
					}
				)
			}
		}
	}
	
	
	
	/**
	 * Disable server (stop it).
	 * 
	 * @returns {nothing}
	 */
	disable()
	{
		// NOTHING TO DO HERE
	}



	/**
	 * Get topology item informations.
	 * 
	 * @param {boolean} arg_deep - get deep sub items information on true (default:false).
	 * @param {object} arg_visited - visited items plain object map.
	 * 
	 * @returns {object} - topology informations (plain object).
	 */
	get_topology_info(arg_deep=true, arg_visited={})
	{
		const info = {
			name:this.get_name(),
			uid_desc:'N/A',
			uid:'N/A',

			tenant:'N/A',
			package:'N/A',
			version:'N/A',
			
			type:'server',
			security:'N/A',
			
			children:['N/A']
		}

		if ( arg_visited && (this.topology_uid in arg_visited) )
		{
			return Object.assign(info, { note:'already dumped' } )
		}

		arg_visited[this.topology_uid] = info

		return info
	}
}

/*
https://engineering.gosquared.com/making-dashboard-faster
function parallel(middlewares) {
  return function (req, res, next) {
    async.each(middlewares, function (mw, cb) {
      mw(req, res, cb);
    }, next);
  };
}

app.use(parallel([
  getUser,
  getSiteList,
  getCurrentSite,
  getSubscription
]));


		const has_cluster = this.$settings.has('workers')
		
		// CLUSTER
		if (has_cluster)
		{
			var cluster = require('cluster');
			var numCPUs = require('os').cpus().length;
			
			// const workers = this.$settings.get('workers')
			// const min_workers = this.workers.min || 1;
			// const max_workers = this.workers.max || 3;
			// const method_workers = this.workers.method || 'roundrobin';
			
			should_listen = ! cluster.isMaster
			
			if (cluster.isMaster)
			{
				// Fork workers.
				for(var i = 0; i < numCPUs; i++)
				{
					cluster.fork()
				}
				
				cluster.on('exit',
					function(worker, code, signal)
					{
						console.log('worker ' + worker.process.pid + ' died')
					}
				)
			}
		}
*/