<commit_before>var express = require('express');
var path = require('path');
var FluentInterface = require('./fluent');
var corsMiddleware = require('./cors');
var util = require('core-util-is');
var url = require('url');
var merge = require('merge');
var connectJson = require('connect-json');
var bodyParser = require('body-parser');

function createInvalidDataException(data) {
	return new Error('You have to provide a JSON object with the following structure: \n' + JSON.stringify({ request : { method : '[GET|PUT|POST|DELETE]', url : '(relative URL e.g. /hello)' }, response : { code : '(HTTP Response code e.g. 200/400/500)', body : '(a JSON object)' } }, null, 4) + ' but you provided: \n' + JSON.stringify(data, null, 4));
}

function Interfake(o) {
	o = o || { debug: false };
	var debug = require('./debug')('interfake-server', o.debug);
	var app = express();
	var server;
	var fluentInterface = new FluentInterface(this, o);
	var expectationsLookup = {};

	// app.use(app.router);
    app.use(connectJson());
    app.use(bodyParser.urlencoded());
    app.use(corsMiddleware);

	app.post('/_requests?', function(req, res){
		try {
			createRoute(req.body);
			res.send(201, { done : true });
		} catch (e) {
			debug('Error: ', e);
			res.send(400, e);
		}
	});

	function indexOfRequestRoute(request) {
		var i;
		if (!app.routes[request.method]) return -1;

		for (i = app.routes[request.method].length - 1; i >= 0; i--) {
			if (app.routes[request.method][i].path === request.url) {
				return i;
			}
		}
		return -1;
	}

	function clearRouteForRequest(request) {
		var i = indexOfRequestRoute(request);

		if (i === -1) {
			return;
		}

		app.routes[request.method].splice(i);
	}

	function determineDelay(delayInput) {
		var result = 0, range, upper, lower;
		if(util.isNumber(delayInput)) {
			result = delayInput;
		} else if(util.isString(delayInput)) {
			range = /([0-9]+)..([0-9]+)/.exec(delayInput);
			upper = +range[2];
			lower = +range[1];
			result = Math.floor( Math.random() * (upper - lower + 1) + lower );
		}

		return result;
	}

	function createRouteHash(requestDescriptor) {
		var finalRoute;

		var path = url.parse(requestDescriptor.url, true);
		requestDescriptor.query = merge(path.query || {}, requestDescriptor.query || {});

		requestDescriptor.url = path.pathname;

		var initialRoute = requestDescriptor.method.toUpperCase() + ' ' + requestDescriptor.url;
		if (requestDescriptor.query) {
			var queryKeys = Object.keys(requestDescriptor.query).filter(function (key) {
				return key !== 'callback';
			});
			if (queryKeys.length) {
				debug('Query keys are', queryKeys);
				finalRoute = initialRoute + '?' + queryKeys.sort().map(function (key) {
					return encodeURIComponent(key) + '=' + encodeURIComponent(requestDescriptor.query[key]);
				}).join(';');
			}
		}

		if (!finalRoute) {
			finalRoute = initialRoute;
		}

		debug('Lookup hash key will be: ' + finalRoute);
		return finalRoute;
	}

	function createRoute(data, existingLookupHash) {
		var specifiedRequest, specifiedResponse, afterSpecifiedResponse, lookupHash, existingExpectations;
		if (!data.request || !data.request.method || !data.request.url || !data.response || !data.response.code) {
			throw createInvalidDataException(data);
		}

		// debug('Setup time', JSON.stringify(data, null, 4));

		if (data.response.body) {
			debug('Setting up ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length);
		} else {
			debug('Setting up ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with no body');
		}

		specifiedRequest = data.request;

		// clearRouteForRequest(specifiedRequest);

		if (existingLookupHash) {
			existingExpectations = expectationsLookup[existingLookupHash].pop();
			debug('Looking for existing lookup hash ', existingLookupHash);
			// existingExpectations.request = merge(data.request, existingExpectations.request);
			lookupHash = createRouteHash(specifiedRequest);
			debug('New lookup hash is', lookupHash);
		} else {
			// Register query params/response in lookup hash
			lookupHash = createRouteHash(specifiedRequest);
		}

		if (expectationsLookup[lookupHash]) {
			debug('Lookup hash', lookupHash, 'already has a route associated with it - there must be more to come.');
			expectationsLookup[lookupHash].push({
				request: data.request,
				response: data.response,
				afterResponse: data.afterResponse
			});
		} else {
			expectationsLookup[lookupHash] = [{
				request: data.request,
				response: data.response,
				afterResponse: data.afterResponse
			}];
		}

		app[specifiedRequest.method](specifiedRequest.url, function (req, res) {
			var lookupHash = createRouteHash({
				method: req.method,
				url: req.path,
				query: req.query
			});
			var expectDataArray = expectationsLookup[lookupHash];

			if (!expectDataArray || expectDataArray.length === 0) {
				debug(expectationsLookup);
				debug('Found matching path route, but no matching data for given query params for lookup hash', lookupHash);
				return res.send(404);
			}

			var expectData = expectDataArray[expectDataArray.length - 1];

			var specifiedResponse = expectData.response; // req.route.responseData;
			var afterSpecifiedResponse = expectData.afterResponse; //req.route.afterResponseData;
			var responseDelay = determineDelay(specifiedResponse.delay);

			debug(req.method, 'request to', req.url, 'returning', specifiedResponse.code);
			debug(req.method, 'request to', req.url, 'will be delayed by', responseDelay, 'millis');
			// debug('After response is', afterSpecifiedResponse);

			var responseBody = specifiedResponse.body;

			res.setHeader('Content-Type', 'application/json');

			if (specifiedResponse.headers) {
				Object.keys(specifiedResponse.headers).forEach(function (k) {
					res.setHeader(k, specifiedResponse.headers[k]);
				});
			}

			if (req.query.callback) {
				debug('Request is asking for jsonp');
				if (typeof responseBody !== 'string') responseBody = JSON.stringify(responseBody);
				responseBody = req.query.callback.trim() + '(' + responseBody + ');';
			}
			setTimeout(function() {
				res.send(specifiedResponse.code, responseBody);

				if (afterSpecifiedResponse && afterSpecifiedResponse.endpoints) {
					debug('Response sent, setting up', afterSpecifiedResponse.endpoints.length, 'endpoints');
					afterSpecifiedResponse.endpoints.forEach(function (endpoint) {
						createRoute(endpoint);
					});
				}
			}, responseDelay);
		});

		var numAfterResponse = (data.afterResponse && data.afterResponse.endpoints) ? data.afterResponse.endpoints.length : 0;

		if (data.response.body) {
			debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length + ' and ' + numAfterResponse + ' after-responses');
		} else {
			debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with no body and ' + numAfterResponse + ' after-responses');
		}
		return lookupHash;
	}

	this.createRoute = createRoute;

	this.get = fluentInterface.forMethod('get');
	this.post = fluentInterface.forMethod('post');
	this.put = fluentInterface.forMethod('put');
	this.delete = fluentInterface.forMethod('delete');

	this.serveStatic = function (path, directory) {
		path = path || '/_static';
		app.use(path, express.static(directory));
	};

	this.listen = function (port) {
		port = port || 3000;
		server = app.listen(port, function () {
			debug('Interfake is listening for requests on port ' + port);
		});
	};

	this.stop = function () {
		if (server) {
			debug('Interfake is stopping');
			server.close(function () {
				debug('Interfake has stopped');
				server = undefined;
			});
		}
	};
}

Interfake.prototype.loadFile = function (filePath) {
	filePath = path.resolve(process.cwd(), filePath);

	file = require(filePath);

	file.forEach(function (endpoint) {
		this.createRoute(endpoint);
	}.bind(this));
};

module.exports = Interfake;
<commit_msg>Using the bodyparser not just for json<commit_after>var express = require('express');
var path = require('path');
var FluentInterface = require('./fluent');
var corsMiddleware = require('./cors');
var util = require('core-util-is');
var url = require('url');
var merge = require('merge');
var connectJson = require('connect-json');
var bodyParser = require('body-parser');

function createInvalidDataException(data) {
	return new Error('You have to provide a JSON object with the following structure: \n' + JSON.stringify({ request : { method : '[GET|PUT|POST|DELETE]', url : '(relative URL e.g. /hello)' }, response : { code : '(HTTP Response code e.g. 200/400/500)', body : '(a JSON object)' } }, null, 4) + ' but you provided: \n' + JSON.stringify(data, null, 4));
}

function Interfake(o) {
	o = o || { debug: false };
	var debug = require('./debug')('interfake-server', o.debug);
	var app = express();
	var server;
	var fluentInterface = new FluentInterface(this, o);
	var expectationsLookup = {};

	// app.use(app.router);
    app.use(connectJson());
    app.use(bodyParser());
    app.use(corsMiddleware);

	app.post('/_requests?', function(req, res){
		try {
			createRoute(req.body);
			res.send(201, { done : true });
		} catch (e) {
			debug('Error: ', e);
			res.send(400, e);
		}
	});

	function indexOfRequestRoute(request) {
		var i;
		if (!app.routes[request.method]) return -1;

		for (i = app.routes[request.method].length - 1; i >= 0; i--) {
			if (app.routes[request.method][i].path === request.url) {
				return i;
			}
		}
		return -1;
	}

	function clearRouteForRequest(request) {
		var i = indexOfRequestRoute(request);

		if (i === -1) {
			return;
		}

		app.routes[request.method].splice(i);
	}

	function determineDelay(delayInput) {
		var result = 0, range, upper, lower;
		if(util.isNumber(delayInput)) {
			result = delayInput;
		} else if(util.isString(delayInput)) {
			range = /([0-9]+)..([0-9]+)/.exec(delayInput);
			upper = +range[2];
			lower = +range[1];
			result = Math.floor( Math.random() * (upper - lower + 1) + lower );
		}

		return result;
	}

	function createRouteHash(requestDescriptor) {
		var finalRoute;

		var path = url.parse(requestDescriptor.url, true);
		requestDescriptor.query = merge(path.query || {}, requestDescriptor.query || {});

		requestDescriptor.url = path.pathname;

		var initialRoute = requestDescriptor.method.toUpperCase() + ' ' + requestDescriptor.url;
		if (requestDescriptor.query) {
			var queryKeys = Object.keys(requestDescriptor.query).filter(function (key) {
				return key !== 'callback';
			});
			if (queryKeys.length) {
				debug('Query keys are', queryKeys);
				finalRoute = initialRoute + '?' + queryKeys.sort().map(function (key) {
					return encodeURIComponent(key) + '=' + encodeURIComponent(requestDescriptor.query[key]);
				}).join(';');
			}
		}

		if (!finalRoute) {
			finalRoute = initialRoute;
		}

		debug('Lookup hash key will be: ' + finalRoute);
		return finalRoute;
	}

	function createRoute(data, existingLookupHash) {
		var specifiedRequest, specifiedResponse, afterSpecifiedResponse, lookupHash, existingExpectations;
		if (!data.request || !data.request.method || !data.request.url || !data.response || !data.response.code) {
			throw createInvalidDataException(data);
		}

		// debug('Setup time', JSON.stringify(data, null, 4));

		if (data.response.body) {
			debug('Setting up ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length);
		} else {
			debug('Setting up ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with no body');
		}

		specifiedRequest = data.request;

		// clearRouteForRequest(specifiedRequest);

		if (existingLookupHash) {
			existingExpectations = expectationsLookup[existingLookupHash].pop();
			debug('Looking for existing lookup hash ', existingLookupHash);
			// existingExpectations.request = merge(data.request, existingExpectations.request);
			lookupHash = createRouteHash(specifiedRequest);
			debug('New lookup hash is', lookupHash);
		} else {
			// Register query params/response in lookup hash
			lookupHash = createRouteHash(specifiedRequest);
		}

		if (expectationsLookup[lookupHash]) {
			debug('Lookup hash', lookupHash, 'already has a route associated with it - there must be more to come.');
			expectationsLookup[lookupHash].push({
				request: data.request,
				response: data.response,
				afterResponse: data.afterResponse
			});
		} else {
			expectationsLookup[lookupHash] = [{
				request: data.request,
				response: data.response,
				afterResponse: data.afterResponse
			}];
		}

		app[specifiedRequest.method](specifiedRequest.url, function (req, res) {
			var lookupHash = createRouteHash({
				method: req.method,
				url: req.path,
				query: req.query
			});
			var expectDataArray = expectationsLookup[lookupHash];

			if (!expectDataArray || expectDataArray.length === 0) {
				debug(expectationsLookup);
				debug('Found matching path route, but no matching data for given query params for lookup hash', lookupHash);
				return res.send(404);
			}

			var expectData = expectDataArray[expectDataArray.length - 1];

			var specifiedResponse = expectData.response; // req.route.responseData;
			var afterSpecifiedResponse = expectData.afterResponse; //req.route.afterResponseData;
			var responseDelay = determineDelay(specifiedResponse.delay);

			debug(req.method, 'request to', req.url, 'returning', specifiedResponse.code);
			debug(req.method, 'request to', req.url, 'will be delayed by', responseDelay, 'millis');
			// debug('After response is', afterSpecifiedResponse);

			var responseBody = specifiedResponse.body;

			res.setHeader('Content-Type', 'application/json');

			if (specifiedResponse.headers) {
				Object.keys(specifiedResponse.headers).forEach(function (k) {
					res.setHeader(k, specifiedResponse.headers[k]);
				});
			}

			if (req.query.callback) {
				debug('Request is asking for jsonp');
				if (typeof responseBody !== 'string') responseBody = JSON.stringify(responseBody);
				responseBody = req.query.callback.trim() + '(' + responseBody + ');';
			}
			setTimeout(function() {
				res.send(specifiedResponse.code, responseBody);

				if (afterSpecifiedResponse && afterSpecifiedResponse.endpoints) {
					debug('Response sent, setting up', afterSpecifiedResponse.endpoints.length, 'endpoints');
					afterSpecifiedResponse.endpoints.forEach(function (endpoint) {
						createRoute(endpoint);
					});
				}
			}, responseDelay);
		});

		var numAfterResponse = (data.afterResponse && data.afterResponse.endpoints) ? data.afterResponse.endpoints.length : 0;

		if (data.response.body) {
			debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with a body of length ' + JSON.stringify(data.response.body).length + ' and ' + numAfterResponse + ' after-responses');
		} else {
			debug('Setup complete: ' + data.request.method + ' ' + data.request.url + ' to return ' + data.response.code + ' with no body and ' + numAfterResponse + ' after-responses');
		}
		return lookupHash;
	}

	this.createRoute = createRoute;

	this.get = fluentInterface.forMethod('get');
	this.post = fluentInterface.forMethod('post');
	this.put = fluentInterface.forMethod('put');
	this.delete = fluentInterface.forMethod('delete');

	this.serveStatic = function (path, directory) {
		path = path || '/_static';
		app.use(path, express.static(directory));
	};

	this.listen = function (port) {
		port = port || 3000;
		server = app.listen(port, function () {
			debug('Interfake is listening for requests on port ' + port);
		});
	};

	this.stop = function () {
		if (server) {
			debug('Interfake is stopping');
			server.close(function () {
				debug('Interfake has stopped');
				server = undefined;
			});
		}
	};
}

Interfake.prototype.loadFile = function (filePath) {
	filePath = path.resolve(process.cwd(), filePath);

	file = require(filePath);

	file.forEach(function (endpoint) {
		this.createRoute(endpoint);
	}.bind(this));
};

module.exports = Interfake;
<|endoftext|>


<commit_before>"use strict";

var EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , CServer = require('mongodb-core').Server
  , Cursor = require('./cursor')
  , AggregationCursor = require('./aggregation_cursor')
  , CommandCursor = require('./command_cursor')
  , f = require('util').format
  , ServerCapabilities = require('./topology_base').ServerCapabilities
  , Store = require('./topology_base').Store
  , Define = require('./metadata')
  , MongoError = require('mongodb-core').MongoError
  , shallowClone = require('./utils').shallowClone
  , MAX_JS_INT = require('./utils').MAX_JS_INT
  , translateOptions = require('./utils').translateOptions
  , filterOptions = require('./utils').filterOptions
  , mergeOptions = require('./utils').mergeOptions;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 * @example
 * var Db = require('mongodb').Db,
 *   Server = require('mongodb').Server,
 *   test = require('assert');
 * // Connect using single Server
 * var db = new Db('test', new Server('localhost', 27017););
 * db.open(function(err, db) {
 *   // Get an additional db
 *   db.close();
 * });
 */

// Allowed parameters
var legalOptionNames = ['poolSize', 'ssl', 'sslValidate', 'checkServerIdentity', 'sslCA'
  , 'sslCert', 'sslKey', 'sslPass', 'socketOptions', 'reconnectTries', 'reconnectInterval'
  , 'store', 'auto_reconnect', 'autoReconnect', 'emitError', 'bufferMaxEntries'
  , 'keepAlive', 'noDelay', 'connectTimeoutMS', 'socketTimeoutMS', 'loggerLevel', 'logger'];

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options=null] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {object} [options.socketOptions=null] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=true] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @return {Server} a Server instance.
 */
var Server = function(host, port, options) {
  options = options || {};
  if(!(this instanceof Server)) return new Server(host, port, options);
  EventEmitter.call(this);
  var self = this;

  // Filter the options
  options = filterOptions(options, legalOptionNames);

  // Stored options
  var storeOptions = {
      force: false
    , bufferMaxEntries: typeof options.bufferMaxEntries == 'number' ? options.bufferMaxEntries : MAX_JS_INT
  }

  // Shared global store
  var store = options.store || new Store(self, storeOptions);

  // Detect if we have a socket connection
  if(host.indexOf('\/') != -1) {
    if(port != null && typeof port == 'object') {
      options = port;
      port = null;
    }
  } else if(port == null) {
    throw MongoError.create({message: 'port must be specified', driver:true});
  }

  // Get the reconnect option
  var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
  reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;

  // Clone options
  var clonedOptions = mergeOptions({}, {
    host: host, port: port, disconnectHandler: store,
    cursorFactory: Cursor,
    reconnect: reconnect,
    emitError: typeof options.emitError == 'boolean' ? options.emitError : true,
    size: typeof options.poolSize == 'number' ? options.poolSize : 5
  });

  // Translate any SSL options and other connectivity options
  clonedOptions = translateOptions(clonedOptions, options);

  // Socket options
  var socketOptions = options.socketOptions && Object.keys(options.socketOptions).length > 0
    ? options.socketOptions : options;

  // Translate all the options to the mongodb-core ones
  clonedOptions = translateOptions(clonedOptions, socketOptions);
  if(typeof clonedOptions.keepAlive == 'number') {
    clonedOptions.keepAliveInitialDelay = clonedOptions.keepAlive;
    clonedOptions.keepAlive = clonedOptions.keepAlive > 0;
  }

  // Create an instance of a server instance from mongodb-core
  var server = new CServer(clonedOptions);
  // Server capabilities
  var sCapabilities = null;

  // Define the internal properties
  this.s = {
    // Create an instance of a server instance from mongodb-core
      server: server
    // Server capabilities
    , sCapabilities: null
    // Cloned options
    , clonedOptions: clonedOptions
    // Reconnect
    , reconnect: clonedOptions.reconnect
    // Emit error
    , emitError: clonedOptions.emitError
    // Pool size
    , poolSize: clonedOptions.size
    // Store Options
    , storeOptions: storeOptions
    // Store
    , store: store
    // Host
    , host: host
    // Port
    , port: port
    // Options
    , options: options
  }

  // BSON property
  Object.defineProperty(this, 'bson', {
    enumerable: true, get: function() {
      return self.s.server.bson;
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'isMasterDoc', {
    enumerable:true, get: function() {
      return self.s.server.lastIsMaster();
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'poolSize', {
    enumerable:true, get: function() { return self.s.server.connections().length; }
  });

  Object.defineProperty(this, 'autoReconnect', {
    enumerable:true, get: function() { return self.s.reconnect; }
  });

  Object.defineProperty(this, 'host', {
    enumerable:true, get: function() { return self.s.host; }
  });

  Object.defineProperty(this, 'port', {
    enumerable:true, get: function() { return self.s.port; }
  });
}

inherits(Server, EventEmitter);

var define = Server.define = new Define('Server', Server, false);

Server.prototype.parserType = function() {
  return this.s.server.parserType();
}

define.classMethod('parserType', {callback: false, promise:false, returns: [String]});

// Connect
Server.prototype.connect = function(db, _options, callback) {
  var self = this;
  if('function' === typeof _options) callback = _options, _options = {};
  if(_options == null) _options = {};
  if(!('function' === typeof callback)) callback = null;
  self.s.options = _options;

  // Update bufferMaxEntries
  self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;

  // Error handler
  var connectErrorHandler = function(event) {
    return function(err) {
      // Remove all event handlers
      var events = ['timeout', 'error', 'close'];
      events.forEach(function(e) {
        self.s.server.removeListener(e, connectHandlers[e]);
      });

      self.s.server.removeListener('connect', connectErrorHandler);

      // Try to callback
      try {
        callback(err);
      } catch(err) {
        process.nextTick(function() { throw err; })
      }
    }
  }

  // Actual handler
  var errorHandler = function(event) {
    return function(err) {
      if(event != 'error') {
        self.emit(event, err);
      }
    }
  }

  // Error handler
  var reconnectHandler = function(err) {
    self.emit('reconnect', self);
    self.s.store.execute();
  }

  // Destroy called on topology, perform cleanup
  var destroyHandler = function() {
    self.s.store.flush();
  }

  // Connect handler
  var connectHandler = function() {
    // Clear out all the current handlers left over
    ["timeout", "error", "close", 'serverOpening', 'serverDescriptionChanged', 'serverHeartbeatStarted',
      'serverHeartbeatSucceeded', 'serverHearbeatFailed', 'serverClosed', 'topologyOpening',
      'topologyClosed', 'topologyDescriptionChanged'].forEach(function(e) {
      self.s.server.removeAllListeners(e);
    });

    // Set up listeners
    self.s.server.once('timeout', errorHandler('timeout'));
    self.s.server.once('error', errorHandler('error'));
    self.s.server.on('close', errorHandler('close'));
    // Only called on destroy
    self.s.server.once('destroy', destroyHandler);

    // relay the event
    var relay = function(event) {
      return function(t, server) {
        self.emit(event, t, server);
      }
    }

    // Set up SDAM listeners
    self.s.server.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
    self.s.server.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
    self.s.server.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
    self.s.server.on('serverHearbeatFailed', relay('serverHearbeatFailed'));
    self.s.server.on('serverOpening', relay('serverOpening'));
    self.s.server.on('serverClosed', relay('serverClosed'));
    self.s.server.on('topologyOpening', relay('topologyOpening'));
    self.s.server.on('topologyClosed', relay('topologyClosed'));
    self.s.server.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));

    // Emit open event
    self.emit('open', null, self);

    // Return correctly
    try {
      callback(null, self);
    } catch(err) {
      console.log(err.stack)
      process.nextTick(function() { throw err; })
    }
  }

  // Set up listeners
  var connectHandlers = {
    timeout: connectErrorHandler('timeout'),
    error: connectErrorHandler('error'),
    close: connectErrorHandler('close')
  };

  // Add the event handlers
  self.s.server.once('timeout', connectHandlers.timeout);
  self.s.server.once('error', connectHandlers.error);
  self.s.server.once('close', connectHandlers.close);
  self.s.server.once('connect', connectHandler);
  // Reconnect server
  self.s.server.on('reconnect', reconnectHandler);

  // Start connection
  self.s.server.connect(_options);
}

// Server capabilities
Server.prototype.capabilities = function() {
  if(this.s.sCapabilities) return this.s.sCapabilities;
  if(this.s.server.lastIsMaster() == null) return null;
  this.s.sCapabilities = new ServerCapabilities(this.s.server.lastIsMaster());
  return this.s.sCapabilities;
}

define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});

// Command
Server.prototype.command = function(ns, cmd, options, callback) {
  this.s.server.command(ns, cmd, options, callback);
}

define.classMethod('command', {callback: true, promise:false});

// Insert
Server.prototype.insert = function(ns, ops, options, callback) {
  this.s.server.insert(ns, ops, options, callback);
}

define.classMethod('insert', {callback: true, promise:false});

// Update
Server.prototype.update = function(ns, ops, options, callback) {
  this.s.server.update(ns, ops, options, callback);
}

define.classMethod('update', {callback: true, promise:false});

// Remove
Server.prototype.remove = function(ns, ops, options, callback) {
  this.s.server.remove(ns, ops, options, callback);
}

define.classMethod('remove', {callback: true, promise:false});

// IsConnected
Server.prototype.isConnected = function() {
  return this.s.server.isConnected();
}

Server.prototype.isDestroyed = function() {
  return this.s.server.isDestroyed();
}

define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});

// Insert
Server.prototype.cursor = function(ns, cmd, options) {
  options.disconnectHandler = this.s.store;
  return this.s.server.cursor(ns, cmd, options);
}

define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});

Server.prototype.setBSONParserType = function(type) {
  return this.s.server.setBSONParserType(type);
}

Server.prototype.lastIsMaster = function() {
  return this.s.server.lastIsMaster();
}

/**
 * Unref all sockets
 * @method
 */
Server.prototype.unref = function() {
  this.s.server.unref();
}

Server.prototype.close = function(forceClosed) {
  this.s.server.destroy();
  // We need to wash out all stored processes
  if(forceClosed == true) {
    this.s.storeOptions.force = forceClosed;
    this.s.store.flush();
  }
}

define.classMethod('close', {callback: false, promise:false});

Server.prototype.auth = function() {
  var args = Array.prototype.slice.call(arguments, 0);
  this.s.server.auth.apply(this.s.server, args);
}

define.classMethod('auth', {callback: true, promise:false});

/**
 * All raw connections
 * @method
 * @return {array}
 */
Server.prototype.connections = function() {
  return this.s.server.connections();
}

define.classMethod('connections', {callback: false, promise:false, returns:[Array]});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

module.exports = Server;
<commit_msg>Allow monitoring and haInterval options to reach mongodb-core server<commit_after>"use strict";

var EventEmitter = require('events').EventEmitter
  , inherits = require('util').inherits
  , CServer = require('mongodb-core').Server
  , Cursor = require('./cursor')
  , AggregationCursor = require('./aggregation_cursor')
  , CommandCursor = require('./command_cursor')
  , f = require('util').format
  , ServerCapabilities = require('./topology_base').ServerCapabilities
  , Store = require('./topology_base').Store
  , Define = require('./metadata')
  , MongoError = require('mongodb-core').MongoError
  , shallowClone = require('./utils').shallowClone
  , MAX_JS_INT = require('./utils').MAX_JS_INT
  , translateOptions = require('./utils').translateOptions
  , filterOptions = require('./utils').filterOptions
  , mergeOptions = require('./utils').mergeOptions;

/**
 * @fileOverview The **Server** class is a class that represents a single server topology and is
 * used to construct connections.
 *
 * **Server Should not be used, use MongoClient.connect**
 * @example
 * var Db = require('mongodb').Db,
 *   Server = require('mongodb').Server,
 *   test = require('assert');
 * // Connect using single Server
 * var db = new Db('test', new Server('localhost', 27017););
 * db.open(function(err, db) {
 *   // Get an additional db
 *   db.close();
 * });
 */

// Allowed parameters
var legalOptionNames = ['poolSize', 'ssl', 'sslValidate', 'checkServerIdentity', 'sslCA'
  , 'sslCert', 'sslKey', 'sslPass', 'socketOptions', 'reconnectTries', 'reconnectInterval'
  , 'store', 'auto_reconnect', 'autoReconnect', 'emitError', 'bufferMaxEntries'
  , 'keepAlive', 'noDelay', 'connectTimeoutMS', 'socketTimeoutMS', 'loggerLevel', 'logger'
  , 'monitoring', 'haInterval'];

/**
 * Creates a new Server instance
 * @class
 * @deprecated
 * @param {string} host The host for the server, can be either an IP4, IP6 or domain socket style host.
 * @param {number} [port] The server port if IP4.
 * @param {object} [options=null] Optional settings.
 * @param {number} [options.poolSize=5] Number of connections in the connection pool for each server instance, set to 5 as default for legacy reasons.
 * @param {boolean} [options.ssl=false] Use ssl connection (needs to have a mongod server with ssl support)
 * @param {object} [options.sslValidate=true] Validate mongod server certificate against ca (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {boolean|function} [options.checkServerIdentity=true] Ensure we check server identify during SSL, set to false to disable checking. Only works for Node 0.12.x or higher. You can pass in a boolean or your own checkServerIdentity override function.
 * @param {array} [options.sslCA=null] Array of valid certificates either as Buffers or Strings (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslCert=null] String or buffer containing the certificate we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslKey=null] String or buffer containing the certificate private key we wish to present (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {(Buffer|string)} [options.sslPass=null] String or buffer containing the certificate password (needs to have a mongod server with ssl support, 2.4 or higher)
 * @param {object} [options.socketOptions=null] Socket options
 * @param {boolean} [options.socketOptions.autoReconnect=true] Reconnect on error.
 * @param {boolean} [options.socketOptions.noDelay=true] TCP Socket NoDelay option.
 * @param {number} [options.socketOptions.keepAlive=0] TCP KeepAlive on the socket with a X ms delay before start.
 * @param {number} [options.socketOptions.connectTimeoutMS=0] TCP Connection timeout setting
 * @param {number} [options.socketOptions.socketTimeoutMS=0] TCP Socket timeout setting
 * @param {number} [options.reconnectTries=30] Server attempt to reconnect #times
 * @param {number} [options.reconnectInterval=1000] Server will wait # milliseconds between retries
 * @param {number} [options.monitoring=false] Triggers the server instance to call ismaster
 * @fires Server#connect
 * @fires Server#close
 * @fires Server#error
 * @fires Server#timeout
 * @fires Server#parseError
 * @fires Server#reconnect
 * @return {Server} a Server instance.
 */
var Server = function(host, port, options) {
  options = options || {};
  if(!(this instanceof Server)) return new Server(host, port, options);
  EventEmitter.call(this);
  var self = this;

  // Filter the options
  options = filterOptions(options, legalOptionNames);

  // Stored options
  var storeOptions = {
      force: false
    , bufferMaxEntries: typeof options.bufferMaxEntries == 'number' ? options.bufferMaxEntries : MAX_JS_INT
  }

  // Shared global store
  var store = options.store || new Store(self, storeOptions);

  // Detect if we have a socket connection
  if(host.indexOf('\/') != -1) {
    if(port != null && typeof port == 'object') {
      options = port;
      port = null;
    }
  } else if(port == null) {
    throw MongoError.create({message: 'port must be specified', driver:true});
  }

  // Get the reconnect option
  var reconnect = typeof options.auto_reconnect == 'boolean' ? options.auto_reconnect : true;
  reconnect = typeof options.autoReconnect == 'boolean' ? options.autoReconnect : reconnect;

  // Clone options
  var clonedOptions = mergeOptions({}, {
    host: host, port: port, disconnectHandler: store,
    cursorFactory: Cursor,
    reconnect: reconnect,
    emitError: typeof options.emitError == 'boolean' ? options.emitError : true,
    size: typeof options.poolSize == 'number' ? options.poolSize : 5
  });

  // Translate any SSL options and other connectivity options
  clonedOptions = translateOptions(clonedOptions, options);

  // Socket options
  var socketOptions = options.socketOptions && Object.keys(options.socketOptions).length > 0
    ? options.socketOptions : options;

  // Translate all the options to the mongodb-core ones
  clonedOptions = translateOptions(clonedOptions, socketOptions);
  if(typeof clonedOptions.keepAlive == 'number') {
    clonedOptions.keepAliveInitialDelay = clonedOptions.keepAlive;
    clonedOptions.keepAlive = clonedOptions.keepAlive > 0;
  }

  // Create an instance of a server instance from mongodb-core
  var server = new CServer(clonedOptions);
  // Server capabilities
  var sCapabilities = null;

  // Define the internal properties
  this.s = {
    // Create an instance of a server instance from mongodb-core
      server: server
    // Server capabilities
    , sCapabilities: null
    // Cloned options
    , clonedOptions: clonedOptions
    // Reconnect
    , reconnect: clonedOptions.reconnect
    // Emit error
    , emitError: clonedOptions.emitError
    // Pool size
    , poolSize: clonedOptions.size
    // Store Options
    , storeOptions: storeOptions
    // Store
    , store: store
    // Host
    , host: host
    // Port
    , port: port
    // Options
    , options: options
  }

  // BSON property
  Object.defineProperty(this, 'bson', {
    enumerable: true, get: function() {
      return self.s.server.bson;
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'isMasterDoc', {
    enumerable:true, get: function() {
      return self.s.server.lastIsMaster();
    }
  });

  // Last ismaster
  Object.defineProperty(this, 'poolSize', {
    enumerable:true, get: function() { return self.s.server.connections().length; }
  });

  Object.defineProperty(this, 'autoReconnect', {
    enumerable:true, get: function() { return self.s.reconnect; }
  });

  Object.defineProperty(this, 'host', {
    enumerable:true, get: function() { return self.s.host; }
  });

  Object.defineProperty(this, 'port', {
    enumerable:true, get: function() { return self.s.port; }
  });
}

inherits(Server, EventEmitter);

var define = Server.define = new Define('Server', Server, false);

Server.prototype.parserType = function() {
  return this.s.server.parserType();
}

define.classMethod('parserType', {callback: false, promise:false, returns: [String]});

// Connect
Server.prototype.connect = function(db, _options, callback) {
  var self = this;
  if('function' === typeof _options) callback = _options, _options = {};
  if(_options == null) _options = {};
  if(!('function' === typeof callback)) callback = null;
  self.s.options = _options;

  // Update bufferMaxEntries
  self.s.storeOptions.bufferMaxEntries = db.bufferMaxEntries;

  // Error handler
  var connectErrorHandler = function(event) {
    return function(err) {
      // Remove all event handlers
      var events = ['timeout', 'error', 'close'];
      events.forEach(function(e) {
        self.s.server.removeListener(e, connectHandlers[e]);
      });

      self.s.server.removeListener('connect', connectErrorHandler);

      // Try to callback
      try {
        callback(err);
      } catch(err) {
        process.nextTick(function() { throw err; })
      }
    }
  }

  // Actual handler
  var errorHandler = function(event) {
    return function(err) {
      if(event != 'error') {
        self.emit(event, err);
      }
    }
  }

  // Error handler
  var reconnectHandler = function(err) {
    self.emit('reconnect', self);
    self.s.store.execute();
  }

  // Destroy called on topology, perform cleanup
  var destroyHandler = function() {
    self.s.store.flush();
  }

  // Connect handler
  var connectHandler = function() {
    // Clear out all the current handlers left over
    ["timeout", "error", "close", 'serverOpening', 'serverDescriptionChanged', 'serverHeartbeatStarted',
      'serverHeartbeatSucceeded', 'serverHearbeatFailed', 'serverClosed', 'topologyOpening',
      'topologyClosed', 'topologyDescriptionChanged'].forEach(function(e) {
      self.s.server.removeAllListeners(e);
    });

    // Set up listeners
    self.s.server.once('timeout', errorHandler('timeout'));
    self.s.server.once('error', errorHandler('error'));
    self.s.server.on('close', errorHandler('close'));
    // Only called on destroy
    self.s.server.once('destroy', destroyHandler);

    // relay the event
    var relay = function(event) {
      return function(t, server) {
        self.emit(event, t, server);
      }
    }

    // Set up SDAM listeners
    self.s.server.on('serverDescriptionChanged', relay('serverDescriptionChanged'));
    self.s.server.on('serverHeartbeatStarted', relay('serverHeartbeatStarted'));
    self.s.server.on('serverHeartbeatSucceeded', relay('serverHeartbeatSucceeded'));
    self.s.server.on('serverHearbeatFailed', relay('serverHearbeatFailed'));
    self.s.server.on('serverOpening', relay('serverOpening'));
    self.s.server.on('serverClosed', relay('serverClosed'));
    self.s.server.on('topologyOpening', relay('topologyOpening'));
    self.s.server.on('topologyClosed', relay('topologyClosed'));
    self.s.server.on('topologyDescriptionChanged', relay('topologyDescriptionChanged'));

    // Emit open event
    self.emit('open', null, self);

    // Return correctly
    try {
      callback(null, self);
    } catch(err) {
      console.log(err.stack)
      process.nextTick(function() { throw err; })
    }
  }

  // Set up listeners
  var connectHandlers = {
    timeout: connectErrorHandler('timeout'),
    error: connectErrorHandler('error'),
    close: connectErrorHandler('close')
  };

  // Add the event handlers
  self.s.server.once('timeout', connectHandlers.timeout);
  self.s.server.once('error', connectHandlers.error);
  self.s.server.once('close', connectHandlers.close);
  self.s.server.once('connect', connectHandler);
  // Reconnect server
  self.s.server.on('reconnect', reconnectHandler);

  // Start connection
  self.s.server.connect(_options);
}

// Server capabilities
Server.prototype.capabilities = function() {
  if(this.s.sCapabilities) return this.s.sCapabilities;
  if(this.s.server.lastIsMaster() == null) return null;
  this.s.sCapabilities = new ServerCapabilities(this.s.server.lastIsMaster());
  return this.s.sCapabilities;
}

define.classMethod('capabilities', {callback: false, promise:false, returns: [ServerCapabilities]});

// Command
Server.prototype.command = function(ns, cmd, options, callback) {
  this.s.server.command(ns, cmd, options, callback);
}

define.classMethod('command', {callback: true, promise:false});

// Insert
Server.prototype.insert = function(ns, ops, options, callback) {
  this.s.server.insert(ns, ops, options, callback);
}

define.classMethod('insert', {callback: true, promise:false});

// Update
Server.prototype.update = function(ns, ops, options, callback) {
  this.s.server.update(ns, ops, options, callback);
}

define.classMethod('update', {callback: true, promise:false});

// Remove
Server.prototype.remove = function(ns, ops, options, callback) {
  this.s.server.remove(ns, ops, options, callback);
}

define.classMethod('remove', {callback: true, promise:false});

// IsConnected
Server.prototype.isConnected = function() {
  return this.s.server.isConnected();
}

Server.prototype.isDestroyed = function() {
  return this.s.server.isDestroyed();
}

define.classMethod('isConnected', {callback: false, promise:false, returns: [Boolean]});

// Insert
Server.prototype.cursor = function(ns, cmd, options) {
  options.disconnectHandler = this.s.store;
  return this.s.server.cursor(ns, cmd, options);
}

define.classMethod('cursor', {callback: false, promise:false, returns: [Cursor, AggregationCursor, CommandCursor]});

Server.prototype.setBSONParserType = function(type) {
  return this.s.server.setBSONParserType(type);
}

Server.prototype.lastIsMaster = function() {
  return this.s.server.lastIsMaster();
}

/**
 * Unref all sockets
 * @method
 */
Server.prototype.unref = function() {
  this.s.server.unref();
}

Server.prototype.close = function(forceClosed) {
  this.s.server.destroy();
  // We need to wash out all stored processes
  if(forceClosed == true) {
    this.s.storeOptions.force = forceClosed;
    this.s.store.flush();
  }
}

define.classMethod('close', {callback: false, promise:false});

Server.prototype.auth = function() {
  var args = Array.prototype.slice.call(arguments, 0);
  this.s.server.auth.apply(this.s.server, args);
}

define.classMethod('auth', {callback: true, promise:false});

/**
 * All raw connections
 * @method
 * @return {array}
 */
Server.prototype.connections = function() {
  return this.s.server.connections();
}

define.classMethod('connections', {callback: false, promise:false, returns:[Array]});

/**
 * Server connect event
 *
 * @event Server#connect
 * @type {object}
 */

/**
 * Server close event
 *
 * @event Server#close
 * @type {object}
 */

/**
 * Server reconnect event
 *
 * @event Server#reconnect
 * @type {object}
 */

/**
 * Server error event
 *
 * @event Server#error
 * @type {MongoError}
 */

/**
 * Server timeout event
 *
 * @event Server#timeout
 * @type {object}
 */

/**
 * Server parseError event
 *
 * @event Server#parseError
 * @type {object}
 */

module.exports = Server;
<|endoftext|>


<commit_before>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

"use strict";

var bodyParser = require('body-parser');
var express = require('express');
var gameCache = new (require('./gamecache'));
var http = require('http');
var path = require('path');
var log = require('./log');

var app = express();

var getIpAddress = function(req) {
  var ip = req.headers['x-forwarded-for'] ||
       req.connection.remoteAddress ||
       req.socket.remoteAddress ||
       req.connection.socket.remoteAddress;
  if (ip && ip.indexOf(',')) {
    ip = ip.split(",")[0];
  }
  return ip;
};

var validIpAddress = (function() {
  var ipAddressRE = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;

  return function(address) {
    return ipAddressRE.test(address);
  };
}());

var validPort = (function() {
  var portRE = /^\d{1,5}$/;

  return function(port) {
    return portRE.test(port);
  };
}());

var handleInform = function(req, res) {
  var ip = getIpAddress(req);
  if (!ip) {
    log.info("inform: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.json(400, {msg: "missing public ip address"});
    return;
  }
  //req
  var hftIp = req.query.hftip;
  if (!hftIp) {
    log.info("inform: no hft ip address", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing hft ip address"});
    return;
  }

  if (!validIpAddress(hftIp)) {
    log.info("inform: bad hft ip address", { ip: hftIp });
    res.status(400).json({msg: "invalid hft ip address"});
    return;
  }

  var hftPort = req.query.hftport;
  if (!hftPort) {
    log.info("inform: no hft port", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing hft port"});
    return;
  }

  if (!validPort(hftPort)) {
    log.info("inform: bad port", { port: hftPort });
    res.status(400).json({msg: "invalid hft ip address"});
    return;
  }

  log.info("added game: ", {ip: ip, hftIp: hftIp, hftPort: hftPort});
  gameCache.addExternalIpToInternalIpGame(ip, hftIp + ":" + hftPort);
  res.status(200).json({ip: ip});
};

var handleInform2 = function(req, res) {

  var ip = getIpAddress(req);
  if (!ip) {
    log.info("inform: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.json(400, {msg: "missing public ip address"});
    return;
  }

  var addresses = req.body.addresses;
  if (!addresses) {
    log.info("inform: no addresss", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing addresses"});
    return;
  }

  if (!addresses.length) {
    log.info("inform: zero addresss", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "zero addresses"});
    return;
  }

  for (var ii = 0; ii < addresses.length; ++ii) {
    var address = addresses[ii];
    if (!validIpAddress(address)) {
      log.info("inform: bad ip address", { ip: address });
      res.status(400).json({msg: "invalid ip address"});
      return;
    }
  }

  var port = req.body.port;
  if (!port) {
    log.info("inform: no port", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing port"});
    return;
  }

  if (!validPort(port)) {
    log.info("inform: bad port", { port: port });
    res.status(400).json({msg: "invalid port"});
    return;
  }

  addresses.forEach(function(address) {
    gameCache.addExternalIpToInternalIpGame(ip, address + ":" + port);
  });
  log.info("added game: ", {ip: ip, addresses: addresses, port: port});
  res.status(200).json({ip: ip});
};

var handleGetGames = function(req, res) {
  var ip = getIpAddress(req);
  if (!ip) {
    log.info("getGames: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing public ip address"});
    return;
  }
  var gameIps = gameCache.getInternalIpsForExternalIp(ip);
  log.info("got games for: ", {ip: ip});
  res.status(200).json(gameIps);
};

var handleCheck = function(req, res) {
    res.header('Content-Type', 'application/xml');
    return res.status(200).send([
      "<pingdom_http_custom_check>",
      "<status>OK</status>",
      "<response_time>0.1</response_time>",
      "</pingdom_http_custom_check>",
    ].join("\n"));
};

var allowCrossDomain = function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, X-HTTP-Method-Override');
  // res.header('Access-Control-Allow-Credentials', false);
  // res.header('Access-Control-Max-Age', 86400);

  if (req.method === "OPTIONS") {
    res.status(200).end("{}");
  } else {
    next();
  }
};

var handleOPTIONS = function(req, res) {
  res.status(200).end("{}");
};

app.use(allowCrossDomain);
app.use(bodyParser.json());
app.use('/', express.static(path.normalize(path.join(__dirname, "..", "public"))));
app.post('/api/inform', handleInform);
app.post('/api/inform2', handleInform2);
app.post('/api/getgames', handleGetGames);
app.get('/check', handleCheck);
app.options(/.*/, handleOPTIONS);

var server = http.createServer(app);

// For testing
server.clearGameCache = function() {
  gameCache.clear();
};

server.monitor = function() {
  var interval = 1000 * 60 * 30;
  setInterval(function() {
    log.info('gameCache state', gameCache.getInfo());
  }, interval);
};

module.exports = server;

<commit_msg>formatting<commit_after>/*
 * Copyright 2014, Gregg Tavares.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Gregg Tavares. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

"use strict";

var bodyParser = require('body-parser');
var express = require('express');
var gameCache = new (require('./gamecache'));
var http = require('http');
var path = require('path');
var log = require('./log');

var app = express();

var getIpAddress = function(req) {
  var ip = req.headers['x-forwarded-for'] ||
       req.connection.remoteAddress ||
       req.socket.remoteAddress ||
       req.connection.socket.remoteAddress;
  if (ip && ip.indexOf(',')) {
    ip = ip.split(",")[0];
  }
  return ip;
};

var validIpAddress = (function() {
  var ipAddressRE = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;

  return function(address) {
    return ipAddressRE.test(address);
  };
}());

var validPort = (function() {
  var portRE = /^\d{1,5}$/;

  return function(port) {
    return portRE.test(port);
  };
}());

var handleInform = function(req, res) {
  var ip = getIpAddress(req);
  if (!ip) {
    log.info("inform: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.json(400, {msg: "missing public ip address"});
    return;
  }
  //req
  var hftIp = req.query.hftip;
  if (!hftIp) {
    log.info("inform: no hft ip address", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing hft ip address"});
    return;
  }

  if (!validIpAddress(hftIp)) {
    log.info("inform: bad hft ip address", { ip: hftIp });
    res.status(400).json({msg: "invalid hft ip address"});
    return;
  }

  var hftPort = req.query.hftport;
  if (!hftPort) {
    log.info("inform: no hft port", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing hft port"});
    return;
  }

  if (!validPort(hftPort)) {
    log.info("inform: bad port", { port: hftPort });
    res.status(400).json({msg: "invalid hft ip address"});
    return;
  }

  log.info("added game: ", {ip: ip, hftIp: hftIp, hftPort: hftPort});
  gameCache.addExternalIpToInternalIpGame(ip, hftIp + ":" + hftPort);
  res.status(200).json({ip: ip});
};

var handleInform2 = function(req, res) {

  var ip = getIpAddress(req);
  if (!ip) {
    log.info("inform: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.json(400, {msg: "missing public ip address"});
    return;
  }

  var addresses = req.body.addresses;
  if (!addresses) {
    log.info("inform: no addresss", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing addresses"});
    return;
  }

  if (!addresses.length) {
    log.info("inform: zero addresss", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "zero addresses"});
    return;
  }

  for (var ii = 0; ii < addresses.length; ++ii) {
    var address = addresses[ii];
    if (!validIpAddress(address)) {
      log.info("inform: bad ip address", { ip: address });
      res.status(400).json({msg: "invalid ip address"});
      return;
    }
  }

  var port = req.body.port;
  if (!port) {
    log.info("inform: no port", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing port"});
    return;
  }

  if (!validPort(port)) {
    log.info("inform: bad port", { port: port });
    res.status(400).json({msg: "invalid port"});
    return;
  }

  addresses.forEach(function(address) {
    gameCache.addExternalIpToInternalIpGame(ip, address + ":" + port);
  });
  log.info("added game: ", {ip: ip, addresses: addresses, port: port});
  res.status(200).json({ip: ip});
};

var handleGetGames = function(req, res) {
  var ip = getIpAddress(req);
  if (!ip) {
    log.info("getGames: no public ip address", { "usage-agent": req.headers['user-agent']});
    res.status(400).json({msg: "missing public ip address"});
    return;
  }
  var gameIps = gameCache.getInternalIpsForExternalIp(ip);
  log.info("got games for: ", {ip: ip});
  res.status(200).json(gameIps);
};

var handleCheck = function(req, res) {
  res.header('Content-Type', 'application/xml');
  return res.status(200).send([
    "<pingdom_http_custom_check>",
    "<status>OK</status>",
    "<response_time>0.1</response_time>",
    "</pingdom_http_custom_check>",
  ].join("\n"));
};

var allowCrossDomain = function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With, X-HTTP-Method-Override');
  // res.header('Access-Control-Allow-Credentials', false);
  // res.header('Access-Control-Max-Age', 86400);

  if (req.method === "OPTIONS") {
    res.status(200).end("{}");
  } else {
    next();
  }
};

var handleOPTIONS = function(req, res) {
  res.status(200).end("{}");
};

app.use(allowCrossDomain);
app.use(bodyParser.json());
app.use('/', express.static(path.normalize(path.join(__dirname, "..", "public"))));
app.post('/api/inform', handleInform);
app.post('/api/inform2', handleInform2);
app.post('/api/getgames', handleGetGames);
app.get('/check', handleCheck);
app.options(/.*/, handleOPTIONS);

var server = http.createServer(app);

// For testing
server.clearGameCache = function() {
  gameCache.clear();
};

server.monitor = function() {
  var interval = 1000 * 60 * 30;
  setInterval(function() {
    log.info('gameCache state', gameCache.getInfo());
  }, interval);
};

module.exports = server;

<|endoftext|>


<commit_before>var fs          = require('fs');
var qs          = require('qs');
var path        = require('path');
var util        = require('util');
var http        = require('http');
var https       = require('https');
var events      = require('events');
var parse       = require('url').parse;
var debug       = require('debug')('tinylr:server');
var Client      = require('./client');
var constants   = require('constants');

// Middleware fallbacks
var bodyParser  = require('body-parser').json()
var queryParser = require('./middleware/query')();

var config = require('../package.json');

function Server(options) {
  options = this.options = options || {};
  events.EventEmitter.call(this);

  options.livereload = options.livereload || path.join(__dirname, '../node_modules/livereload-js/dist/livereload.js');
  options.port = parseInt(options.port || 35729, 10);

  this.on('GET /', this.index.bind(this));
  this.on('GET /changed', this.changed.bind(this));
  this.on('POST /changed', this.changed.bind(this));
  this.on('GET /livereload.js', this.livereload.bind(this));
  this.on('GET /kill', this.close.bind(this));

  this.clients = {};
  this.configure(options.app);
}

module.exports = Server;

util.inherits(Server, events.EventEmitter);

Server.prototype.configure = function configure(app) {
  var self = this;
  debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');

  if (!app) {
    if ((this.options.key && this.options.cert) || this.options.pfx) {
      this.server = https.createServer(this.options, this.handler.bind(this));
    } else {
      this.server = http.createServer(this.handler.bind(this));
    }
    this.server.on('upgrade', this.websocketify.bind(this));
    this.server.on('error', function() {
      self.error.apply(self, arguments);
    });
    return this;
  }

  this.app = app;

  this.app.listen = function(port, done) {
    done = done || function() {};
    if (port !== self.options.port) {
      debug('Warn: LiveReload port is not standard (%d). You are listening on %d', self.options.port, port);
      debug('You\'ll need to rely on the LiveReload snippet');
      debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
    }

    var srv = self.server = http.createServer(app);
    srv.on('upgrade', self.websocketify.bind(self));
    srv.on('error', function() {
      self.error.apply(self, arguments);
    });
    srv.on('close', self.close.bind(self));
    return srv.listen(port, done);
  };

  return this;
};

Server.prototype.handler = function handler(req, res, next) {
  var self = this;
  var middleware = typeof next === 'function';
  debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');

  this.parse(req, res, function(err) {
    debug('query parsed', req.body, err);
    if (err) return next(err);
    self.handle(req, res, next);
  });

  // req
  //   .on('end', this.handle.bind(this, req, res))
  //   .on('data', function(chunk) {
  //     req.data = req.data || '';
  //     req.data += chunk;
  //   });

  return this;
};

// Ensure body / query are defined, useful as a fallback when the
// Server is used without express / connect, and shouldn't hurt
// otherwise
Server.prototype.parse = function(req, res, next) {
  debug('Parse', req.body, req.query);
  bodyParser(req, res, function(err) {
    debug('Body parsed', req.body);
    if (err) return next(err);

    queryParser(req, res, next);
  });
};

Server.prototype.handle = function handle(req, res, next) {
  var url = parse(req.url);
  debug('Request:', req.method, url.href);
  var middleware = typeof next === 'function';

  res.setHeader('Content-Type', 'application/json');

  // do the routing
  var route = req.method + ' '  + url.pathname;
  var respond = this.emit(route, req, res);
  if (respond) return;
  if (middleware) return next();

  res.writeHead(404);
  res.write(JSON.stringify({
    error: 'not_found',
    reason: 'no such route'
  }));
  res.end();
};

Server.prototype.websocketify = function websocketify(req, socket, head) {
  var self = this;
  var client = new Client(req, socket, head, this.options);
  this.clients[client.id] = client;

  debug('New LiveReload connection (id: %s)', client.id);
  client.on('end', function() {
    debug('Destroy client %s (url: %s)', client.id, client.url);
    delete self.clients[client.id];
  });
};

Server.prototype.listen = function listen(port, host, fn) {
  this.port = port;

  if (typeof host === 'function') {
    fn = host;
    host = undefined;
  }

  this.server.listen(port, host, fn);
};

Server.prototype.close = function close(req, res) {
  Object.keys(this.clients).forEach(function(id) {
    this.clients[id].close();
  }, this);

  if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));

  if (res) res.end();
};

Server.prototype.error = function error(e) {
  console.error();
  console.error('... Uhoh. Got error %s ...', e.message);
  console.error(e.stack);

  if (e.code !== constants.EADDRINUSE) return;
  console.error();
  console.error('You already have a server listening on %s', this.port);
  console.error('You should stop it and try again.');
  console.error();
};

// Routes

Server.prototype.livereload = function livereload(req, res) {
  fs.createReadStream(this.options.livereload).pipe(res);
};

Server.prototype.changed = function changed(req, res) {
  var files = this.param('files', req);

  debug('Changed event (Files: %s)', files.join(' '));
  var clients = this.notifyClients(files);

  if (!res) return;

  res.write(JSON.stringify({
    clients: clients,
    files: files
  }));

  res.end();
};

Server.prototype.notifyClients = function notifyClients(files) {
  var clients = Object.keys(this.clients).map(function(id) {
    var client = this.clients[id];
    debug('Reloading client %s (url: %s)', client.id, client.url);
    client.reload(files);
    return {
      id: client.id,
      url: client.url
    };
  }, this);

  return clients;
};

// Lookup param from body / params / query.
Server.prototype.param = function _param(name, req) {
  var param;
  if (req.body && req.body[name]) param = req.body.files;
  else if (req.params && req.params[name]) param = req.params.files;
  else if (req.query && req.query[name]) param= req.query.files;

  // normalize files array
  param = Array.isArray(param) ? param :
    typeof param === 'string' ? param.split(/[\s,]/) :
    [];

  debug('param %s', name, req.body, req.params, req.query, param);
  return param;
};

Server.prototype.index = function index(req, res) {
  res.write(JSON.stringify({
    tinylr: 'Welcome',
    version: config.version
  }));

  res.end();
};
<commit_msg>Adding in an optional errorListener method that can be used when the live reload listen process breaks down, allowing the user to respond to the error if they so wish.<commit_after>var fs          = require('fs');
var qs          = require('qs');
var path        = require('path');
var util        = require('util');
var http        = require('http');
var https       = require('https');
var events      = require('events');
var parse       = require('url').parse;
var debug       = require('debug')('tinylr:server');
var Client      = require('./client');
var constants   = require('constants');

// Middleware fallbacks
var bodyParser  = require('body-parser').json()
var queryParser = require('./middleware/query')();

var config = require('../package.json');

function Server(options) {
  options = this.options = options || {};
  events.EventEmitter.call(this);

  options.livereload = options.livereload || path.join(__dirname, '../node_modules/livereload-js/dist/livereload.js');
  options.port = parseInt(options.port || 35729, 10);

  this.on('GET /', this.index.bind(this));
  this.on('GET /changed', this.changed.bind(this));
  this.on('POST /changed', this.changed.bind(this));
  this.on('GET /livereload.js', this.livereload.bind(this));
  this.on('GET /kill', this.close.bind(this));

  if(options.errorListener) {
    this.errorListener = options.errorListener;
  }

  this.clients = {};
  this.configure(options.app);
}

module.exports = Server;

util.inherits(Server, events.EventEmitter);

Server.prototype.configure = function configure(app) {
  var self = this;
  debug('Configuring %s', app ? 'connect / express application' : 'HTTP server');

  if (!app) {
    if ((this.options.key && this.options.cert) || this.options.pfx) {
      this.server = https.createServer(this.options, this.handler.bind(this));
    } else {
      this.server = http.createServer(this.handler.bind(this));
    }
    this.server.on('upgrade', this.websocketify.bind(this));
    this.server.on('error', function() {
      self.error.apply(self, arguments);
    });
    return this;
  }

  this.app = app;

  this.app.listen = function(port, done) {
    done = done || function() {};
    if (port !== self.options.port) {
      debug('Warn: LiveReload port is not standard (%d). You are listening on %d', self.options.port, port);
      debug('You\'ll need to rely on the LiveReload snippet');
      debug('> http://feedback.livereload.com/knowledgebase/articles/86180-how-do-i-add-the-script-tag-manually-');
    }

    var srv = self.server = http.createServer(app);
    srv.on('upgrade', self.websocketify.bind(self));
    srv.on('error', function() {
      self.error.apply(self, arguments);
    });
    srv.on('close', self.close.bind(self));
    return srv.listen(port, done);
  };

  return this;
};

Server.prototype.handler = function handler(req, res, next) {
  var self = this;
  var middleware = typeof next === 'function';
  debug('LiveReload handler %s (middleware: %s)', req.url, middleware ? 'on' : 'off');

  this.parse(req, res, function(err) {
    debug('query parsed', req.body, err);
    if (err) return next(err);
    self.handle(req, res, next);
  });

  // req
  //   .on('end', this.handle.bind(this, req, res))
  //   .on('data', function(chunk) {
  //     req.data = req.data || '';
  //     req.data += chunk;
  //   });

  return this;
};

// Ensure body / query are defined, useful as a fallback when the
// Server is used without express / connect, and shouldn't hurt
// otherwise
Server.prototype.parse = function(req, res, next) {
  debug('Parse', req.body, req.query);
  bodyParser(req, res, function(err) {
    debug('Body parsed', req.body);
    if (err) return next(err);

    queryParser(req, res, next);
  });
};

Server.prototype.handle = function handle(req, res, next) {
  var url = parse(req.url);
  debug('Request:', req.method, url.href);
  var middleware = typeof next === 'function';

  res.setHeader('Content-Type', 'application/json');

  // do the routing
  var route = req.method + ' '  + url.pathname;
  var respond = this.emit(route, req, res);
  if (respond) return;
  if (middleware) return next();

  res.writeHead(404);
  res.write(JSON.stringify({
    error: 'not_found',
    reason: 'no such route'
  }));
  res.end();
};

Server.prototype.websocketify = function websocketify(req, socket, head) {
  var self = this;
  var client = new Client(req, socket, head, this.options);
  this.clients[client.id] = client;

  debug('New LiveReload connection (id: %s)', client.id);
  client.on('end', function() {
    debug('Destroy client %s (url: %s)', client.id, client.url);
    delete self.clients[client.id];
  });
};

Server.prototype.listen = function listen(port, host, fn) {
  this.port = port;

  if (typeof host === 'function') {
    fn = host;
    host = undefined;
  }

  this.server.listen(port, host, fn);
};

Server.prototype.close = function close(req, res) {
  Object.keys(this.clients).forEach(function(id) {
    this.clients[id].close();
  }, this);

  if (this.server._handle) this.server.close(this.emit.bind(this, 'close'));

  if (res) res.end();
};

Server.prototype.error = function error(e) {
  if(this.errorListener) {
    this.errorListener(e);
    return
  }

  console.error();
  console.error('... Uhoh. Got error %s ...', e.message);
  console.error(e.stack);

  if (e.code !== constants.EADDRINUSE) return;
  console.error();
  console.error('You already have a server listening on %s', this.port);
  console.error('You should stop it and try again.');
  console.error();
};

// Routes

Server.prototype.livereload = function livereload(req, res) {
  fs.createReadStream(this.options.livereload).pipe(res);
};

Server.prototype.changed = function changed(req, res) {
  var files = this.param('files', req);

  debug('Changed event (Files: %s)', files.join(' '));
  var clients = this.notifyClients(files);

  if (!res) return;

  res.write(JSON.stringify({
    clients: clients,
    files: files
  }));

  res.end();
};

Server.prototype.notifyClients = function notifyClients(files) {
  var clients = Object.keys(this.clients).map(function(id) {
    var client = this.clients[id];
    debug('Reloading client %s (url: %s)', client.id, client.url);
    client.reload(files);
    return {
      id: client.id,
      url: client.url
    };
  }, this);

  return clients;
};

// Lookup param from body / params / query.
Server.prototype.param = function _param(name, req) {
  var param;
  if (req.body && req.body[name]) param = req.body.files;
  else if (req.params && req.params[name]) param = req.params.files;
  else if (req.query && req.query[name]) param= req.query.files;

  // normalize files array
  param = Array.isArray(param) ? param :
    typeof param === 'string' ? param.split(/[\s,]/) :
    [];

  debug('param %s', name, req.body, req.params, req.query, param);
  return param;
};

Server.prototype.index = function index(req, res) {
  res.write(JSON.stringify({
    tinylr: 'Welcome',
    version: config.version
  }));

  res.end();
};
<|endoftext|>


<commit_before>//built-in modules
var http = require("http");
var url = require("url");
var qs = require("querystring");

//external modules
var async = require("async");

//custom modules
var router = require("./router");
var sandbox = require("./sandbox");
var db = require("./db");
var eq = require("./evqueue");
var api = require("./api");
var util = require("./util");
var time = require("./time")
var CONST = require("./constants");

/*
 * built-in event emitter for own events.
 */
var EventEmitter = require("events").EventEmitter,
	emitter = new EventEmitter();

var srv = exports;

util.add_console_log_printing_format();

/*
 * Server namespace
 */
srv.events = {};
srv.config = { 	system:{},
				events:{},
				db:{
					system_catalogs:["events", "timers"],
					default_catalog : "docs"
				}
			};
srv.init = {};
srv.api = { config:{procedures:{}}, events:{} };
srv.eq = { events:{} };
srv.db = {select: db.select, save:db.save, criteria:db.criteria};

/*
 * Make api functions public to front module, so can call them from front module server.js:
 *
 * var jsa = require("jsa");
 * jsa.api.docs.create({uid:32342,doc:{test:1}},function(err,ret_val){ ... })
 */

for( key in api.remote ){

	(function(){

		var scopekey = key;
		srv.api.config.procedures[scopekey] = CONST.ENABLE();
		srv.api[scopekey] = function(params, ret_handler){

			sandbox.execute( scopekey, params, ret_handler?ret_handler:function(err,val){

				if(err)
					throw new Error(err.message);
				else if(srv.config.app.debug)
					console.log(val);
			});
		}
	})();
};

for( key in api.config ){

	(function(){
		var scopekey = key;
		srv.api.config[scopekey] = function(params, ret_handler){

			api.config[scopekey](params, ret_handler);
		}
	})();
};

srv.api.config.rcpts = function( rcpt_handler ){

	api.rcpts = rcpt_handler;
}

srv.api.config.enable_procedures = function(){

	for(key in srv.api.config.procedures)
		srv.api.config.procedures[key] = CONST.ENABLE();
}

srv.api.config.disable_procedures = function(){

	for(key in srv.api.config.procedures)
		srv.api.config.procedures[key] = CONST.DISABLE();
}

/*
 * This function is where all new operations will be added or init stuff will be coded.
 * When all init stuff is done must call ret_handler to signal the end of the init process.
 */
srv.init.execute = [

	function( end_handler ){
		/*
		srv.config.events.catalog = {};
		srv.config.events.catalog["docs"].mode = 1; //all event_types have the same event_mode for docs belonging to docs catalog.

		srv.config.events.catalog["docs"].operation = {};
		//catalog op
		srv.config.events.catalog["docs"].operation["create"].mode = 1;
		//doc ops
		srv.config.events.catalog["docs"].operation["join"].mode = 1;
		srv.config.events.catalog["docs"].operation["unjoin"].mode = 1;
		srv.config.events.catalog["docs"].operation["add"].mode = 0;
		srv.config.events.catalog["docs"].operation["remove"].mode = 0;

		//field ops
		srv.config.events.catalog["docs"].operation["set"].mode = 0;
		srv.config.events.catalog["docs"].operation["set"].field = {};
		srv.config.events.catalog["docs"].operation["set"].field["x"].mode = 1;
		srv.config.events.catalog["docs"].operation["set"].field["y"].mode = 1;
		srv.config.events.catalog["docs"].operation["set"].field["z"].mode = 1;

		srv.config.events.catalog["docs"].operation["add"].field["a"].mode = 1;
		srv.config.events.catalog["docs"].operation["add"].field["a.b"].mode = 1;
		srv.config.events.catalog["docs"].operation["add"].field["a.b.c"].mode = 1;

		srv.config.events.catalog["docs"].operation["push"].mode = 1;
		srv.config.events.catalog["docs"].operation["pop"].mode = 1;
		srv.config.events.catalog["docs"].operation["shift"].mode = 1;
		srv.config.events.catalog["docs"].operation["unshift"].mode = 1;

		srv.config.events["create"] = {docs:0};
		*/

		end_handler();
}];

/*
 * Add external handler for api module events. evqueue.js module adds a default handler for
 * all primitive events related to primitive operations: ev_create, ev_join, ev_unjoin,
 * ev_add, ev_rem, ev_set, ev_incr, ev_decr, ev_push, ev_pop, ev_pull.
 */
srv.api.events.on = function( ev_type, ret_handler ){

	api.on(ev_type, ret_handler);
	return srv.api.events;
}

/*
 * Emit event as an api event.
 */
srv.api.events.emit = function( ev_type, ctx, rcpts ){

	api.emit( ev_type, ctx, rcpts );
}

/*
 * Register listeners for eventqueue events. Eventqueue event list:
 *
 * ev_eq_push
 */
srv.eq.events.on = function( ev_type, ret_handler ){

	eq.on(ev_type, ret_handler);
	return srv.eq.events;
}

/*
 * Emit  event as a evqueue event.
 */
srv.eq.events.emit = function( ev_type, ctx, rcpts ){

	eq.emit( ev_type, ctx, rcpts );
}

/*
 * Register listeners to the server events using a delegation pattern for EventEmitter.
 *
 */
srv.events.on = function( ev_type, ret_handler ){

	emitter.on(ev_type, function( data, rcpts ){

		ret_handler( {
						ev_type:ev_type,
						ev_tstamp:time.now(),
						ev_data:data
					 }, rcpts );
	});
}

srv.events.on("ev_srv_req",function(msg){

	if( srv.config.app.debug )
		console.log("-->POST %s\n-->\t%s", msg.ev_data.pathname, msg.ev_data.request);

});

/*
 * emit a server-event. Server events list:
 *
 *   -ev_srv_start
 *   -ev_srv_stop
 *   -ev_srv_req
 *
 */
var emit = srv.events.emit =  function( ev_type, msg, rcpts ){

	emitter.emit(ev_type, msg, rcpts );
}

/*
 * add a new operation to the service
 */
srv.api.config.newop = function( fname, f_handler ){

	var ev_name = "ev_api_" + fname;

	api.remote[fname] = function( ctx, ret_handler){//register function to api module.

		f_handler(ctx, ret_handler);

	}

	srv.api[fname] = function( params, ret_handler ){//prepare call operation through sandbox.

		sandbox.execute(fname, params, ret_handler?ret_handler:function(err,val){

			if(err)
				throw new Error(err.message);
			else if(srv.config.app.debug)
					console.log(val);
		});
	}

	srv.api.config.procedures[fname] = CONST.ENABLE();

	eq.api.listen( ev_name );

}

/*
 *  termination handler.
 */
function termination_handler(sig) {

   if (typeof sig === "string") {
      console.log("Received %s - terminating Node process.", sig);
      process.exit(1);
   }
   console.log("Node process stopped.");
}

if( process.listeners("exit").length  == 1 ){

	//Process on exit and signals.
	process.on('exit', function() { termination_handler(); });

	//Uncaught exceptions handler to make the server stay running forever.
	process.on("uncaughtException", function(err){

		console.warn( err );

	});

	[
	 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGBUS',
	 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGPIPE', 'SIGTERM'
	].forEach(function(element, index, array) {

	    process.on(element, function() { termination_handler(element); });
	});

}

/*
 * Plots active timers.
 */
exports.timers = function(){

	util.show_timers();
}

/*
 * If srv.config.app is undefined then settings are loaded from file settings.json, otherwise the function
 * prints and/or overwrites these default settings (srv.config.app) with program settings (econfig).
 *
 */
var settings = exports.settings = function( econfig ){

	if( srv.config.app == undefined ){

		try{

			srv.config.app = require("../settings.json");
			srv.config.app.version = require("../package.json").version;
		}catch(err){

			throw new Error("Cannot read config file 'settings.json'");
		}
	}else if( typeof econfig == "object" && econfig.reload == 1){

		try{
			console.log("**Reloading config file 'settings.json'");
			delete require.cache[ process.cwd() + "/settings.json" ];
			delete require.cache[ process.cwd() + "/package.json" ];
			srv.config.app = require("../settings.json");
			srv.config.app.version = require("../package.json").version;
			delete econfig.reload;
		}catch(err){
			throw new Error("Cannot read config file 'settings.json' or 'package.json'");
		}

	}

	if( typeof econfig == "object" ){
		for(key in econfig)
			if(econfig[key])
				srv.config.app[key] = econfig[key];
	}

}

/*
 * private function to process incoming request: parse url and route request  based on pathname.
 */
function request_handler(request, response){

	if( request.method == "POST" ){

	    if( request.headers["content-type"] == "application/json" ){

			var data = "";
			var pathname = url.parse(request.url).pathname;
			request.setEncoding("utf8");
			request.on("data", function( chunk ){

			    data += chunk;
			});
			request.on("end", function(){

			    emit("ev_srv_req", {pathname:pathname, request:data} );
			    router.route( pathname, response, data );
			});

	    }else{

			response.writeHead(400, {"Content-Type":"text/html"});
			response.write("400 Bad request.");
			response.end();
	    }
	}else{
	    response.writeHead(405,{"Content-Type":"text/html"});
	    response.write("405 Method not allowed");
	    response.end();
	}
}

srv.config.system = {pid:process.pid, uid:process.getuid(), cwd:process.cwd(), node_version:process.version };

/*
 * start http server and connect driver to db.
 *
 * srv.config.app properties:
 *
 * .port : http port to listen
 * .ipaddr : addr to bind the service
 * .dbhost: db host ip address
 * .dbport : db  port
 * .dbname: database name
 * .dbuser : db connection user.
 * .dbpass : db connection password
 */
var httpsrv = http.createServer(request_handler);
exports.start = function( econfig, ret_handler ){

	//set parameters conveniently if only one parameter is passed.
	if( typeof econfig == "function"){

		ret_handler = econfig;
		econfig = undefined;

	}

	if(httpsrv._handle)
		stop();

	console.log("\n\n");
	console.log("**Starting JSA server...");
	console.log("**System settings:\n\t%s", JSON.stringify(srv.config.system) );
	console.log("**Reading config file 'settings.json'");
	settings(econfig);
	console.log("**App settings:\n\t%s" , JSON.stringify(srv.config.app) );

	db.connect( srv.config.app, function(err,val){

		if( !err ){

			httpsrv.listen( srv.config.app.httpport, srv.config.app.httpaddr, function(){

				console.log("**Httpd listening on %s:%d", srv.config.app.httpaddr, srv.config.app.httpport);

				async.series(
				[
					function(next){

						async.series( srv.init.execute, function(err, results){

							console.log("**Executed jsa.api.init.execute");
							console.log("**Remote operations: {%s}", util.get_remote_operations().join());
							next();
						});
					},
					function(next){

						db.prepare(srv.config.db,function(err, result){

							console.log("**Prepared db catalogs: {%s}", result.join() );
							next();
						});
					},
					function(next){

						time.load_remove_timers(function(n){

							console.log("**Loaded %d remove timers.",n);
							next();
						});
					}
				],function(){

					console.tlog("Server started");
					emit("ev_srv_start");

					if(ret_handler)
						ret_handler();
				});
			});

		}else
			console.tlog(err);
	});

}

/*
 * Stop http server.
 */
var stop = function(){

	if( httpsrv )
		httpsrv.close();
	console.log("Httpd stopped.");
	emit("ev_srv_stop");
}
exports.stop = stop;

<commit_msg>minor changes<commit_after>//built-in modules
var http = require("http");
var url = require("url");
var qs = require("querystring");

//external modules
var async = require("async");

//custom modules
var plugin = require("./plugin");
var router = require("./router");
var sandbox = require("./sandbox");
var db = require("./db");
var eq = require("./evqueue");
var api = require("./api");
var util = require("./util");
var time = require("./time")
var CONST = require("./constants");

/*
 * built-in event emitter for own events.
 */
var EventEmitter = require("events").EventEmitter,
	emitter = new EventEmitter();

var srv = exports;

util.add_console_log_printing_format();

/*
 * Server namespace
 */
srv.events = {};
srv.config = { 	system:{},
				events:{},
				db:{
					system_catalogs:["events", "timers"],
					default_catalog : "docs"
				}
			};
srv.init = {};
srv.api = { config:{procedures:{}}, events:{} };
srv.eq = { events:{} };
srv.db = {select: db.select, save:db.save, criteria:db.criteria};

/*
 * Make api functions public to front module, so can call them from front module server.js:
 *
 * var jsa = require("jsa");
 * jsa.api.docs.create({uid:32342,doc:{test:1}},function(err,ret_val){ ... })
 */

for( key in api.remote ){

	(function(){

		var scopekey = key;
		srv.api.config.procedures[scopekey] = CONST.ENABLE();
		srv.api[scopekey] = function(params, ret_handler){

			sandbox.execute( scopekey, params, ret_handler?ret_handler:function(err,val){

				if(err)
					throw new Error(err.message);
				else if(srv.config.app.debug)
					console.log(val);
			});
		}
	})();
};

for( key in api.config ){

	(function(){
		var scopekey = key;
		srv.api.config[scopekey] = function(params, ret_handler){

			api.config[scopekey](params, ret_handler);
		}
	})();
};

srv.api.config.rcpts = function( rcpt_handler ){

	api.rcpts = rcpt_handler;
}

srv.api.config.enable_procedures = function(){

	for(key in srv.api.config.procedures)
		srv.api.config.procedures[key] = CONST.ENABLE();
}

srv.api.config.disable_procedures = function(){

	for(key in srv.api.config.procedures)
		srv.api.config.procedures[key] = CONST.DISABLE();
}

/*
 * This function is where all new operations will be added or init stuff will be coded.
 * When all init stuff is done must call ret_handler to signal the end of the init process.
 */
srv.init.execute = [

	function( end_handler ){

		end_handler();
}];

/*
 * Add external handler for api module events. evqueue.js module adds a default handler for
 * all primitive events related to primitive operations: ev_create, ev_join, ev_unjoin,
 * ev_add, ev_rem, ev_set, ev_incr, ev_decr, ev_push, ev_pop, ev_pull.
 */
srv.api.events.on = function( ev_type, ret_handler ){

	api.on(ev_type, ret_handler);
	return srv.api.events;
}

/*
 * Emit event as an api event.
 */
srv.api.events.emit = function( ev_type, ctx, rcpts ){

	api.emit( ev_type, ctx, rcpts );
}

/*
 * Register listeners for eventqueue events. Eventqueue event list: ev_eq_push
 */
srv.eq.events.on = function( ev_type, ret_handler ){

	eq.on(ev_type, ret_handler);
	return srv.eq.events;
}

/*
 * Emit  event as a evqueue event.
 */
srv.eq.events.emit = function( ev_type, ctx, rcpts ){

	eq.emit( ev_type, ctx, rcpts );
}

/*
 * Register listeners to the server events using a delegation pattern for EventEmitter.
 *
 */
srv.events.on = function( ev_type, ret_handler ){

	emitter.on(ev_type, function( data, rcpts ){

		ret_handler( {
						ev_type:ev_type,
						ev_tstamp:time.now(),
						ev_data:data
					 }, rcpts );
	});
}

srv.events.on("ev_srv_req",function(msg){

	if( srv.config.app.debug )
		console.log("-->POST %s\n-->\t%s", msg.ev_data.pathname, msg.ev_data.request);

});

/*
 * emit a server-event. Server events list:
 *
 *   -ev_srv_start
 *   -ev_srv_stop
 *   -ev_srv_req
 *
 */
var emit = srv.events.emit =  function( ev_type, msg, rcpts ){

	emitter.emit(ev_type, msg, rcpts );
}

/*
 * add a new operation to the service
 */
srv.api.config.newop = function( fname, f_handler ){

	api.remote[fname] = function( ctx, ret_handler){//register function to api module.

		f_handler(ctx, ret_handler);

	}

	srv.api[fname] = function( params, ret_handler ){//prepare call operation through server via sandbox.

		sandbox.execute(fname, params, ret_handler?ret_handler:function(err,val){

			if(err)
				throw new Error(err.message);
			else if(srv.config.app.debug)
					console.log(val);
		});
	}

	srv.api.config.procedures[fname] = CONST.ENABLE();

	eq.api.listen( "ev_api_" + fname );

}

/*
 *  termination handler.
 */
function termination_handler(sig) {

   if (typeof sig === "string") {
      console.log("Received %s - terminating Node process.", sig);
      process.exit(1);
   }
   console.log("Node process stopped.");
}

if( process.listeners("exit").length  == 1 ){

	//Process on exit and signals.
	process.on('exit', function() { termination_handler(); });

	//Uncaught exceptions handler to make the server stay running forever.
	process.on("uncaughtException", function(err){

		console.warn( err );

	});

	[
	 'SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGBUS',
	 'SIGFPE', 'SIGUSR1', 'SIGSEGV', 'SIGUSR2', 'SIGPIPE', 'SIGTERM'
	].forEach(function(element, index, array) {

	    process.on(element, function() { termination_handler(element); });
	});

}

/*
 * Plots active timers.
 */
exports.timers = function(){

	util.show_timers();
}

/*
 * If srv.config.app is undefined then settings are loaded from file settings.json, otherwise the function
 * prints and/or overwrites these default settings (srv.config.app) with program settings (econfig).
 *
 */
var settings = exports.settings = function( econfig ){

	if( srv.config.app == undefined ){

		try{

			srv.config.app = require("../settings.json");
			srv.config.app.version = require("../package.json").version;
		}catch(err){

			throw new Error("Cannot read config file 'settings.json'");
		}
	}else if( typeof econfig == "object" && econfig.reload == 1){

		try{
			console.log("**Reloading config file 'settings.json'");
			delete require.cache[ process.cwd() + "/settings.json" ];
			delete require.cache[ process.cwd() + "/package.json" ];
			srv.config.app = require("../settings.json");
			srv.config.app.version = require("../package.json").version;
			delete econfig.reload;
		}catch(err){
			throw new Error("Cannot read config file 'settings.json' or 'package.json'");
		}

	}

	if( typeof econfig == "object" ){
		for(key in econfig)
			if(econfig[key])
				srv.config.app[key] = econfig[key];
	}

}

/*
 * private function to process incoming request: parse url and route request  based on pathname.
 */
function request_handler(request, response){

	if( request.method == "POST" ){

	    if( request.headers["content-type"] == "application/json" ){

			var data = "";
			var pathname = url.parse(request.url).pathname;
			request.setEncoding("utf8");
			request.on("data", function( chunk ){

			    data += chunk;
			});
			request.on("end", function(){

			    emit("ev_srv_req", {pathname:pathname, request:data} );
			    router.route( pathname, response, data );
			});

	    }else{

			response.writeHead(400, {"Content-Type":"text/html"});
			response.write("400 Bad request.");
			response.end();
	    }
	}else{
	    response.writeHead(405,{"Content-Type":"text/html"});
	    response.write("405 Method not allowed");
	    response.end();
	}
}

srv.config.system = {pid:process.pid, uid:process.getuid(), cwd:process.cwd(), node_version:process.version };

/*
 * start http server and connect driver to db.
 *
 * srv.config.app properties:
 *
 * .port : http port to listen
 * .ipaddr : addr to bind the service
 * .dbhost: db host ip address
 * .dbport : db  port
 * .dbname: database name
 * .dbuser : db connection user.
 * .dbpass : db connection password
 */
var httpsrv = http.createServer(request_handler);
exports.start = function( econfig, ret_handler ){

	//set parameters conveniently if only one parameter is passed.
	if( typeof econfig == "function"){

		ret_handler = econfig;
		econfig = undefined;

	}

	if(httpsrv._handle)
		stop();

	console.log("\n\n");
	console.log("**Starting JSA server...");
	console.log("**System settings:\n\t%s", JSON.stringify(srv.config.system) );
	console.log("**Reading config file 'settings.json'");
	settings(econfig);
	console.log("**App settings:\n\t%s" , JSON.stringify(srv.config.app) );

	db.connect( srv.config.app, function(err,val){

		if( !err ){

			httpsrv.listen( srv.config.app.httpport, srv.config.app.httpaddr, function(){

				console.log("**Httpd listening on %s:%d", srv.config.app.httpaddr, srv.config.app.httpport);

				async.series(
				[
					function(next){

						async.series( srv.init.execute, function(err, results){

							console.log("**Executed jsa.api.init.execute");
							console.log("**Remote operations: {%s}", util.get_remote_operations().join());
							next();
						});
					},
					function(next){

						db.prepare(srv.config.db,function(err, result){

							console.log("**Prepared db catalogs: {%s}", result.join() );
							next();
						});
					},
					function(next){

						time.load_remove_timers(function(n){

							console.log("**Loaded %d remove timers.",n);
							next();
						});
					}
				],function(){

					console.tlog("Server started");
					emit("ev_srv_start");

					if(ret_handler)
						ret_handler();
				});
			});

		}else
			console.tlog(err);
	});

}

/*
 * Stop http server.
 */
var stop = function(){

	if( httpsrv )
		httpsrv.close();
	console.log("Httpd stopped.");
	emit("ev_srv_stop");
}
exports.stop = stop;

<|endoftext|>