<commit_before>/**
 * Module dependencies.
 */
var SessionStore = require('./txn/session')
  , UnorderedList = require('./unorderedlist')
  , authorization = require('./middleware/authorization')
  , resume = require('./middleware/resume')
  , decision = require('./middleware/decision')
  , transactionLoader = require('./middleware/transactionLoader')
  , token = require('./middleware/token')
  , authorizationErrorHandler = require('./middleware/authorizationErrorHandler')
  , errorHandler = require('./middleware/errorHandler')
  , utils = require('./utils')
  , debug = require('debug')('oauth2orize');

/**
 * `Server` constructor.
 *
 * @api public
 */
function Server(options) {
  options = options || {};
  this._reqParsers = [];
  this._resHandlers = [];
  this._errHandlers = [];
  this._exchanges = [];

  this._serializers = [];
  this._deserializers = [];
  this._txnStore = options.store || new SessionStore();
}

/**
 * Register authorization grant middleware.
 *
 * OAuth 2.0 defines an authorization framework, in which authorization grants
 * can be of a variety of types.  Initiating and responding to an OAuth 2.0
 * authorization transaction is implemented by grant middleware, and the server
 * registers the middleware it wishes to support.
 *
 * Examples:
 *
 *     server.grant(oauth2orize.grant.code());
 *
 *     server.grant('*', function(req) {
 *       return { host: req.headers['host'] }
 *     });
 *
 *     server.grant('foo', function(req) {
 *       return { foo: req.query['foo'] }
 *     });
 *
 * @param {String|Object} type
 * @param {String} phase
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */
Server.prototype.grant = function(type, phase, fn) {
  if (typeof type == 'object') {
    // sig: grant(mod)
    var mod = type;
    if (mod.request) { this.grant(mod.name, 'request', mod.request); }
    if (mod.response) { this.grant(mod.name, 'response', mod.response); }
    if (mod.error) { this.grant(mod.name, 'error', mod.error); }
    return this;
  }
  if (typeof phase == 'object') {
    // sig: grant(type, mod)
    var mod = phase;
    if (mod.request) { this.grant(type, 'request', mod.request); }
    if (mod.response) { this.grant(type, 'response', mod.response); }
    if (mod.error) { this.grant(type, 'error', mod.error); }
    return this;
  }

  if (typeof phase == 'function') {
    // sig: grant(type, fn)
    fn = phase;
    phase = 'request';
  }
  if (type === '*') { type = null; }
  if (type) { type = new UnorderedList(type); }

  if (phase == 'request') {
    debug('register request parser %s %s', type || '*', fn.name || 'anonymous');
    this._reqParsers.push({ type: type, handle: fn });
  } else if (phase == 'response') {
    debug('register response handler %s %s', type || '*', fn.name || 'anonymous');
    this._resHandlers.push({ type: type, handle: fn });
  } else if (phase == 'error') {
    debug('register error handler %s %s', type || '*', fn.name || 'anonymous');
    this._errHandlers.push({ type: type, handle: fn });
  }
  return this;
};

/**
 * Register token exchange middleware.
 *
 * OAuth 2.0 defines an authorization framework, in which authorization grants
 * can be of a variety of types.  Exchanging of these types for access tokens is
 * implemented by exchange middleware, and the server registers the middleware
 * it wishes to support.
 *
 * Examples:
 *
 *     server.exchange(oauth2orize.exchange.authorizationCode(function() {
 *       ...
 *     }));
 *
 * @param {String|Function} type
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */
Server.prototype.exchange = function(type, fn) {
  if (typeof type == 'function') {
    fn = type;
    type = fn.name;
  }
  if (type === '*') { type = null; }

  debug('register exchanger %s %s', type || '*', fn.name || 'anonymous');
  this._exchanges.push({ type: type, handle: fn });
  return this;
};

/**
 * Parses requests to obtain authorization.
 *
 * @api public
 */
Server.prototype.authorize =
Server.prototype.authorization = function(options, validate, immediate, complete) {
  return authorization(this, options, validate, immediate, complete);
};

Server.prototype.resume = function(options, immediate, complete) {
  if (options && options.loadTransaction === false) {
    return resume(this, options, immediate);
  }
  return [transactionLoader(this, options), resume(this, options, immediate, complete)];
};

/**
 * Handle a user's response to an authorization dialog.
 *
 * @api public
 */
Server.prototype.decision = function(options, parse, complete) {
  if (options && options.loadTransaction === false) {
    return decision(this, options, parse, complete);
  }
  return [transactionLoader(this, options), decision(this, options, parse, complete)];
};

Server.prototype.authorizeError =
Server.prototype.authorizationError =
Server.prototype.authorizationErrorHandler = function(options) {
  var loader = transactionLoader(this, options);

  return [
    function transactionLoaderErrorWrapper(err, req, res, next) {
      loader(req, res, function(ierr) {
        return next(err);
      });
    },
    authorizationErrorHandler(this, options)
  ];
};

/**
 * Handle requests to exchange an authorization grant for an access token.
 *
 * @api public
 */
Server.prototype.token = function(options) {
  return token(this, options);
};

/**
 * Respond to errors encountered in OAuth 2.0 endpoints.
 *
 * @api public
 */
Server.prototype.errorHandler = function(options) {
  return errorHandler(options);
};

/**
 * Registers a function used to serialize client objects into the session.
 *
 * Examples:
 *
 *     server.serializeClient(function(client, done) {
 *       done(null, client.id);
 *     });
 *
 * @api public
 */
Server.prototype.serializeClient = function(fn, done) {
  if (typeof fn === 'function') {
    return this._serializers.push(fn);
  }

  // private implementation that traverses the chain of serializers, attempting
  // to serialize a client
  var client = fn;

  var stack = this._serializers;
  (function pass(i, err, obj) {
    // serializers use 'pass' as an error to skip processing
    if ('pass' === err) { err = undefined; }
    // an error or serialized object was obtained, done
    if (err || obj) { return done(err, obj); }

    var layer = stack[i];
    if (!layer) {
      return done(new Error('Failed to serialize client. Register serialization function using serializeClient().'));
    }

    try {
      layer(client, function(e, o) { pass(i + 1, e, o); } );
    } catch (ex) {
      return done(ex);
    }
  })(0);
};

/**
 * Registers a function used to deserialize client objects out of the session.
 *
 * Examples:
 *
 *     server.deserializeClient(function(id, done) {
 *       Client.findById(id, function (err, client) {
 *         done(err, client);
 *       });
 *     });
 *
 * @api public
 */
Server.prototype.deserializeClient = function(fn, done) {
  if (typeof fn === 'function') {
    return this._deserializers.push(fn);
  }

  // private implementation that traverses the chain of deserializers,
  // attempting to deserialize a client
  var obj = fn;

  var stack = this._deserializers;
  (function pass(i, err, client) {
    // deserializers use 'pass' as an error to skip processing
    if ('pass' === err) { err = undefined; }
    // an error or deserialized client was obtained, done
    if (err || client) { return done(err, client); }
    // a valid client existed when establishing the session, but that client has
    // since been deauthorized
    if (client === null || client === false) { return done(null, false); }

    var layer = stack[i];
    if (!layer) {
      return done(new Error('Failed to deserialize client. Register deserialization function using deserializeClient().'));
    }

    try {
      layer(obj, function(e, c) { pass(i + 1, e, c); } );
    } catch (ex) {
      return done(ex);
    }
  })(0);
};

/**
 * Parse authorization request into transaction using registered grant middleware.
 *
 * @param {String} type
 * @param {http.ServerRequest} req
 * @param {Function} cb
 * @api private
 */
Server.prototype._parse = function(type, req, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  var ultype = new UnorderedList(type)
    , stack = this._reqParsers
    , areq = {};

  if (options.parse) { stack.push({ type: null, handle: options.parse }); }
  if (type) { areq.type = type; }

  (function pass(i) {
    var layer = stack[i];
    if (!layer) { return cb(null, areq); }

    try {
      debug('parse:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        var arity = layer.handle.length;
        if (arity == 1) { // sync
          var o = layer.handle(req);
          utils.merge(areq, o);
          pass(i + 1);
        } else { // async
          layer.handle(req, function(err, o) {
            if (err) { return cb(err); }
            utils.merge(areq, o);
            pass(i + 1);
          });
        }
      } else {
        pass(i + 1);
      }
    } catch (ex) {
      return cb(ex);
    }
  })(0);
};

/**
 * Respond to authorization transaction using registered grant middleware.
 *
 * @param {Object} txn
 * @param {http.ServerResponse} res
 * @param {Function} cb
 * @api private
 */
Server.prototype._respond = function(txn, res, complete, cb) {
  if (cb === undefined) {
    cb = complete;
    complete = undefined;
  }

  var ultype = new UnorderedList(txn.req.type)
    , stack = this._resHandlers
    , idx = 0;

  function next(err) {
    if (err) { return cb(err); }

    var layer = stack[idx++];
    if (!layer) { return cb(); }

    try {
      debug('respond:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        var arity = layer.handle.length;
        if (arity == 4) {
          layer.handle(txn, res, complete, next);
        } else {
          layer.handle(txn, res, next);
        }
      } else {
        next();
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next();
};

Server.prototype._respondError = function(err, txn, res, cb) {
  var ultype = new UnorderedList(txn.req.type)
    , stack = this._errHandlers
    , idx = 0;

  function next(err) {
    var layer = stack[idx++];
    if (!layer) { return cb(err); }

    try {
      debug('error:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        layer.handle(err, txn, res, next);
      } else {
        next(err);
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next(err);
}

/**
 * Process token request using registered exchange middleware.
 *
 * @param {String} type
 * @param {http.ServerRequest} req
 * @param {http.ServerResponse} res
 * @param {Function} cb
 * @api private
 */
Server.prototype._exchange = function(type, req, res, cb) {
  var stack = this._exchanges
    , idx = 0;

  function next(err) {
    if (err) { return cb(err); }

    var layer = stack[idx++];
    if (!layer) { return cb(); }

    try {
      debug('exchange:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type === type) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next();
};

/**
 * Expose `Server`.
 */
exports = module.exports = Server;
<commit_msg>Plumb complete callback through.<commit_after>/**
 * Module dependencies.
 */
var SessionStore = require('./txn/session')
  , UnorderedList = require('./unorderedlist')
  , authorization = require('./middleware/authorization')
  , resume = require('./middleware/resume')
  , decision = require('./middleware/decision')
  , transactionLoader = require('./middleware/transactionLoader')
  , token = require('./middleware/token')
  , authorizationErrorHandler = require('./middleware/authorizationErrorHandler')
  , errorHandler = require('./middleware/errorHandler')
  , utils = require('./utils')
  , debug = require('debug')('oauth2orize');

/**
 * `Server` constructor.
 *
 * @api public
 */
function Server(options) {
  options = options || {};
  this._reqParsers = [];
  this._resHandlers = [];
  this._errHandlers = [];
  this._exchanges = [];

  this._serializers = [];
  this._deserializers = [];
  this._txnStore = options.store || new SessionStore();
}

/**
 * Register authorization grant middleware.
 *
 * OAuth 2.0 defines an authorization framework, in which authorization grants
 * can be of a variety of types.  Initiating and responding to an OAuth 2.0
 * authorization transaction is implemented by grant middleware, and the server
 * registers the middleware it wishes to support.
 *
 * Examples:
 *
 *     server.grant(oauth2orize.grant.code());
 *
 *     server.grant('*', function(req) {
 *       return { host: req.headers['host'] }
 *     });
 *
 *     server.grant('foo', function(req) {
 *       return { foo: req.query['foo'] }
 *     });
 *
 * @param {String|Object} type
 * @param {String} phase
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */
Server.prototype.grant = function(type, phase, fn) {
  if (typeof type == 'object') {
    // sig: grant(mod)
    var mod = type;
    if (mod.request) { this.grant(mod.name, 'request', mod.request); }
    if (mod.response) { this.grant(mod.name, 'response', mod.response); }
    if (mod.error) { this.grant(mod.name, 'error', mod.error); }
    return this;
  }
  if (typeof phase == 'object') {
    // sig: grant(type, mod)
    var mod = phase;
    if (mod.request) { this.grant(type, 'request', mod.request); }
    if (mod.response) { this.grant(type, 'response', mod.response); }
    if (mod.error) { this.grant(type, 'error', mod.error); }
    return this;
  }

  if (typeof phase == 'function') {
    // sig: grant(type, fn)
    fn = phase;
    phase = 'request';
  }
  if (type === '*') { type = null; }
  if (type) { type = new UnorderedList(type); }

  if (phase == 'request') {
    debug('register request parser %s %s', type || '*', fn.name || 'anonymous');
    this._reqParsers.push({ type: type, handle: fn });
  } else if (phase == 'response') {
    debug('register response handler %s %s', type || '*', fn.name || 'anonymous');
    this._resHandlers.push({ type: type, handle: fn });
  } else if (phase == 'error') {
    debug('register error handler %s %s', type || '*', fn.name || 'anonymous');
    this._errHandlers.push({ type: type, handle: fn });
  }
  return this;
};

/**
 * Register token exchange middleware.
 *
 * OAuth 2.0 defines an authorization framework, in which authorization grants
 * can be of a variety of types.  Exchanging of these types for access tokens is
 * implemented by exchange middleware, and the server registers the middleware
 * it wishes to support.
 *
 * Examples:
 *
 *     server.exchange(oauth2orize.exchange.authorizationCode(function() {
 *       ...
 *     }));
 *
 * @param {String|Function} type
 * @param {Function} fn
 * @return {Server} for chaining
 * @api public
 */
Server.prototype.exchange = function(type, fn) {
  if (typeof type == 'function') {
    fn = type;
    type = fn.name;
  }
  if (type === '*') { type = null; }

  debug('register exchanger %s %s', type || '*', fn.name || 'anonymous');
  this._exchanges.push({ type: type, handle: fn });
  return this;
};

/**
 * Parses requests to obtain authorization.
 *
 * @api public
 */
Server.prototype.authorize =
Server.prototype.authorization = function(options, validate, immediate, complete) {
  return authorization(this, options, validate, immediate, complete);
};

Server.prototype.resume = function(options, immediate, complete) {
  if (options && options.loadTransaction === false) {
    return resume(this, options, immediate, complete);
  }
  return [transactionLoader(this, options), resume(this, options, immediate, complete)];
};

/**
 * Handle a user's response to an authorization dialog.
 *
 * @api public
 */
Server.prototype.decision = function(options, parse, complete) {
  if (options && options.loadTransaction === false) {
    return decision(this, options, parse, complete);
  }
  return [transactionLoader(this, options), decision(this, options, parse, complete)];
};

Server.prototype.authorizeError =
Server.prototype.authorizationError =
Server.prototype.authorizationErrorHandler = function(options) {
  var loader = transactionLoader(this, options);

  return [
    function transactionLoaderErrorWrapper(err, req, res, next) {
      loader(req, res, function(ierr) {
        return next(err);
      });
    },
    authorizationErrorHandler(this, options)
  ];
};

/**
 * Handle requests to exchange an authorization grant for an access token.
 *
 * @api public
 */
Server.prototype.token = function(options) {
  return token(this, options);
};

/**
 * Respond to errors encountered in OAuth 2.0 endpoints.
 *
 * @api public
 */
Server.prototype.errorHandler = function(options) {
  return errorHandler(options);
};

/**
 * Registers a function used to serialize client objects into the session.
 *
 * Examples:
 *
 *     server.serializeClient(function(client, done) {
 *       done(null, client.id);
 *     });
 *
 * @api public
 */
Server.prototype.serializeClient = function(fn, done) {
  if (typeof fn === 'function') {
    return this._serializers.push(fn);
  }

  // private implementation that traverses the chain of serializers, attempting
  // to serialize a client
  var client = fn;

  var stack = this._serializers;
  (function pass(i, err, obj) {
    // serializers use 'pass' as an error to skip processing
    if ('pass' === err) { err = undefined; }
    // an error or serialized object was obtained, done
    if (err || obj) { return done(err, obj); }

    var layer = stack[i];
    if (!layer) {
      return done(new Error('Failed to serialize client. Register serialization function using serializeClient().'));
    }

    try {
      layer(client, function(e, o) { pass(i + 1, e, o); } );
    } catch (ex) {
      return done(ex);
    }
  })(0);
};

/**
 * Registers a function used to deserialize client objects out of the session.
 *
 * Examples:
 *
 *     server.deserializeClient(function(id, done) {
 *       Client.findById(id, function (err, client) {
 *         done(err, client);
 *       });
 *     });
 *
 * @api public
 */
Server.prototype.deserializeClient = function(fn, done) {
  if (typeof fn === 'function') {
    return this._deserializers.push(fn);
  }

  // private implementation that traverses the chain of deserializers,
  // attempting to deserialize a client
  var obj = fn;

  var stack = this._deserializers;
  (function pass(i, err, client) {
    // deserializers use 'pass' as an error to skip processing
    if ('pass' === err) { err = undefined; }
    // an error or deserialized client was obtained, done
    if (err || client) { return done(err, client); }
    // a valid client existed when establishing the session, but that client has
    // since been deauthorized
    if (client === null || client === false) { return done(null, false); }

    var layer = stack[i];
    if (!layer) {
      return done(new Error('Failed to deserialize client. Register deserialization function using deserializeClient().'));
    }

    try {
      layer(obj, function(e, c) { pass(i + 1, e, c); } );
    } catch (ex) {
      return done(ex);
    }
  })(0);
};

/**
 * Parse authorization request into transaction using registered grant middleware.
 *
 * @param {String} type
 * @param {http.ServerRequest} req
 * @param {Function} cb
 * @api private
 */
Server.prototype._parse = function(type, req, options, cb) {
  if (typeof options == 'function') {
    cb = options;
    options = undefined;
  }
  options = options || {};

  var ultype = new UnorderedList(type)
    , stack = this._reqParsers
    , areq = {};

  if (options.parse) { stack.push({ type: null, handle: options.parse }); }
  if (type) { areq.type = type; }

  (function pass(i) {
    var layer = stack[i];
    if (!layer) { return cb(null, areq); }

    try {
      debug('parse:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        var arity = layer.handle.length;
        if (arity == 1) { // sync
          var o = layer.handle(req);
          utils.merge(areq, o);
          pass(i + 1);
        } else { // async
          layer.handle(req, function(err, o) {
            if (err) { return cb(err); }
            utils.merge(areq, o);
            pass(i + 1);
          });
        }
      } else {
        pass(i + 1);
      }
    } catch (ex) {
      return cb(ex);
    }
  })(0);
};

/**
 * Respond to authorization transaction using registered grant middleware.
 *
 * @param {Object} txn
 * @param {http.ServerResponse} res
 * @param {Function} cb
 * @api private
 */
Server.prototype._respond = function(txn, res, complete, cb) {
  if (cb === undefined) {
    cb = complete;
    complete = undefined;
  }

  var ultype = new UnorderedList(txn.req.type)
    , stack = this._resHandlers
    , idx = 0;

  function next(err) {
    if (err) { return cb(err); }

    var layer = stack[idx++];
    if (!layer) { return cb(); }

    try {
      debug('respond:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        var arity = layer.handle.length;
        if (arity == 4) {
          layer.handle(txn, res, complete, next);
        } else {
          layer.handle(txn, res, next);
        }
      } else {
        next();
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next();
};

Server.prototype._respondError = function(err, txn, res, cb) {
  var ultype = new UnorderedList(txn.req.type)
    , stack = this._errHandlers
    , idx = 0;

  function next(err) {
    var layer = stack[idx++];
    if (!layer) { return cb(err); }

    try {
      debug('error:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type.equalTo(ultype)) {
        layer.handle(err, txn, res, next);
      } else {
        next(err);
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next(err);
}

/**
 * Process token request using registered exchange middleware.
 *
 * @param {String} type
 * @param {http.ServerRequest} req
 * @param {http.ServerResponse} res
 * @param {Function} cb
 * @api private
 */
Server.prototype._exchange = function(type, req, res, cb) {
  var stack = this._exchanges
    , idx = 0;

  function next(err) {
    if (err) { return cb(err); }

    var layer = stack[idx++];
    if (!layer) { return cb(); }

    try {
      debug('exchange:%s', layer.handle.name || 'anonymous');
      if (layer.type === null || layer.type === type) {
        layer.handle(req, res, next);
      } else {
        next();
      }
    } catch (ex) {
      return cb(ex);
    }
  }
  next();
};

/**
 * Expose `Server`.
 */
exports = module.exports = Server;
<|endoftext|>


<commit_before>/*jslint node: true, vars: true, nomen: true */
'use strict';

var express = require('express');
var Http = require('http');
var debug = require('debug')('xpl-db:server');
var async = require('async');
var noCache = require('connect-nocache')();
var bodyParser = require('body-parser');
var serve_static = require('serve-static');
var compress = require('compression');

class Server {
  constructor(configuration, store) {
    this.configuration = configuration || {};
    this.store = store;

    this.app = this.configuration.express || express();

    if (this.configuration.compression !== false) {
      try {
        var compression = require('compression');
        this.app.use(compression())
      } catch (x) {
        console.error("No compression module !");
      }
    }
  }

  listen(callback) {
    var app = this.app;

    app.enable('etag');
    app.use(compress());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({
      extended: true
    }));
    app.use(noCache);

    app.get(/^\/last\/(.*)$/, this.getLast.bind(this));
    app.get(/^\/history\/(.*)$/, this.getHistory.bind(this));
    app.get(/^\/minMaxAvgSum\/(.*)$/, this.getMinMaxAvgSum.bind(this));

    app.post('/last', this.getLastSet.bind(this));
    app.post('/history', this.getHistorySet.bind(this));
    app.post('/minMaxAvgSum', this.getMinMaxAvgSumSet.bind(this));

    if (this.configuration.configPath) {
      app.use("/config", serve_static(this.configuration.configPath, {
        'index' : false
      }));
    }

    app.use(function(req, res, next) {
      res.status(404).send('Sorry cant find that!');
    });

    var server = app.listen(this.configuration.httpPort || 8480, (error) => {
      if (error) {
        console.error("Server can not listen", error);
        return;
      }
      debug("listen", "Server is listening ", server.address());

      callback(error);
    });
  };

  _set(name, request, response, func) {
    var keys = Object.keys(request.body);
    debug("_set", name, "set keys=", keys);

    var options = formatOptions(request);

    var results = {};

    async.eachLimit(keys, 8, (key, callback) => {
      var reg=/(.*)\/([^/]+)$/.exec(key);
      if (reg) {
        key=reg[1]+"@"+reg[2]; // Transform @ to /
      }

      func.call(this.store, key, options, (error, value) => {
        debug("_set", name, " set key=", key, "value=", value, "error=", error);
        if (error) {
          return callback(error);
        }

        var reg2=/(.*)\@([^@]+)$/.exec(key);
        if (reg2) {
          key=reg2[1]+"/"+reg[2]; // Transform / to @
        }

        results[key] = value;

        callback();
      });

    }, (error) => {
      if (error) {
        // send 500
        if (error.code === 'NOT_FOUND') {
          response.status(404).body("Key '" + error.key + "' not found");
          return;
        }

        response.status(500).body(String(error));
        return;
      }

      response.json(results);
    });
  };

  _get(name, request, response, func) {
    var key = request.params[0];
    debug("_get", "type=", name, "key=", key);

    var reg=/(.*)\/([^/]+)$/.exec(key);
    if (reg) {
      key=reg[1]+"@"+reg[2]; // Transform @ to /
    }

    var options = formatOptions(request);

    func.call(this.store, key, options, (error, values) => {
      debug("_get", "type=", name, "key=", key, "values=", values);
      if (error) {
        // send 500
        if (error.code === 'NOT_FOUND') {
          response.status(404).send('Key not found');
          return;
        }

        response.status(500).send(String(error));
        return;
      }

      response.json(values);
    });
  }

  getLast(request, response) {
    this._get("getLast", request, response, this.store.getLast);
  }

  getHistory(request, response) {
    this._get("getHistory", request, response, this.store.getHistory);
  }

  getMinMaxAvgSum(request, response) {
    this._get("getMinMaxAvgSum", request, response, this.store.getMinMaxAvgSum);
  }

  getLastSet(request, response) {
    this._set("getLastSet", request, response, this.store.getLast);
  }

  getHistorySet(request, response) {
    this._set("getHistorySet", request, response, this.store.getHistory);
  }

  getMinMaxAvgSumSet(request, response) {
    this._set("getMinMaxAvgSumSet", request, response, this.store.getMinMaxAvgSum);
  }
}

function formatOptions(request) {
  var ret = {};

  var query = request.query;
  if (query.limit) {
    ret.limit = parseInt(query.limit, 10);
  }

  if (query.minDate) {
    ret.minDate = new Date(query.minDate);
  }

  if (query.maxDate) {
    ret.maxDate = new Date(query.maxDate);
  }

  if (query.averageMs) {
    ret.averageMs = parseInt(query.averageMs, 10);
  }

  if (query.step) {
    ret.stepMs = parseInt(query.step, 10) * 1000;
  }

  return ret;
}

module.exports = Server;
<commit_msg>List in same order<commit_after>/*jslint node: true, vars: true, nomen: true */
'use strict';

var express = require('express');
var Http = require('http');
var debug = require('debug')('xpl-db:server');
var async = require('async');
var noCache = require('connect-nocache')();
var bodyParser = require('body-parser');
var serve_static = require('serve-static');
var compress = require('compression');

class Server {
  constructor(configuration, store) {
    this.configuration = configuration || {};
    this.store = store;

    this.app = this.configuration.express || express();

    if (this.configuration.compression !== false) {
      try {
        var compression = require('compression');
        this.app.use(compression())
      } catch (x) {
        console.error("No compression module !");
      }
    }
  }

  listen(callback) {
    var app = this.app;

    app.enable('etag');
    app.use(compress());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({
      extended: true
    }));
    app.use(noCache);

    app.get(/^\/last\/(.*)$/, this.getLast.bind(this));
    app.get(/^\/history\/(.*)$/, this.getHistory.bind(this));
    app.get(/^\/minMaxAvgSum\/(.*)$/, this.getMinMaxAvgSum.bind(this));

    app.post('/last', this.getLastSet.bind(this));
    app.post('/history', this.getHistorySet.bind(this));
    app.post('/minMaxAvgSum', this.getMinMaxAvgSumSet.bind(this));

    if (this.configuration.configPath) {
      app.use("/config", serve_static(this.configuration.configPath, {
        'index' : false
      }));
    }

    app.use(function(req, res, next) {
      res.status(404).send('Sorry cant find that!');
    });

    var server = app.listen(this.configuration.httpPort || 8480, (error) => {
      if (error) {
        console.error("Server can not listen", error);
        return;
      }
      debug("listen", "Server is listening ", server.address());

      callback(error);
    });
  };

  _set(name, request, response, func) {
    var keys = Object.keys(request.body);
    debug("_set", name, "set keys=", keys);

    var options = formatOptions(request);

    var results = keys.map((key) => null);

    async.eachLimit(keys, 8, (key, callback) => {
      var reg=/(.*)\/([^/]+)$/.exec(key);
      if (reg) {
        key=reg[1]+"@"+reg[2]; // Transform @ to /
      }

      func.call(this.store, key, options, (error, value) => {
        debug("_set", name, " set key=", key, "value=", value, "error=", error);
        if (error) {
          return callback(error);
        }

        var reg2=/(.*)\@([^@]+)$/.exec(key);
        if (reg2) {
          key=reg2[1]+"/"+reg[2]; // Transform / to @
        }

        results[key] = value;

        callback();
      });

    }, (error) => {
      if (error) {
        // send 500
        if (error.code === 'NOT_FOUND') {
          response.status(404).body("Key '" + error.key + "' not found");
          return;
        }

        response.status(500).body(String(error));
        return;
      }

      response.json(results);
    });
  };

  _get(name, request, response, func) {
    var key = request.params[0];
    debug("_get", "type=", name, "key=", key);

    var reg=/(.*)\/([^/]+)$/.exec(key);
    if (reg) {
      key=reg[1]+"@"+reg[2]; // Transform @ to /
    }

    var options = formatOptions(request);

    func.call(this.store, key, options, (error, values) => {
      debug("_get", "type=", name, "key=", key, "values=", values);
      if (error) {
        // send 500
        if (error.code === 'NOT_FOUND') {
          response.status(404).send('Key not found');
          return;
        }

        response.status(500).send(String(error));
        return;
      }

      response.json(values);
    });
  }

  getLast(request, response) {
    this._get("getLast", request, response, this.store.getLast);
  }

  getHistory(request, response) {
    this._get("getHistory", request, response, this.store.getHistory);
  }

  getMinMaxAvgSum(request, response) {
    this._get("getMinMaxAvgSum", request, response, this.store.getMinMaxAvgSum);
  }

  getLastSet(request, response) {
    this._set("getLastSet", request, response, this.store.getLast);
  }

  getHistorySet(request, response) {
    this._set("getHistorySet", request, response, this.store.getHistory);
  }

  getMinMaxAvgSumSet(request, response) {
    this._set("getMinMaxAvgSumSet", request, response, this.store.getMinMaxAvgSum);
  }
}

function formatOptions(request) {
  var ret = {};

  var query = request.query;
  if (query.limit) {
    ret.limit = parseInt(query.limit, 10);
  }

  if (query.minDate) {
    ret.minDate = new Date(query.minDate);
  }

  if (query.maxDate) {
    ret.maxDate = new Date(query.maxDate);
  }

  if (query.averageMs) {
    ret.averageMs = parseInt(query.averageMs, 10);
  }

  if (query.step) {
    ret.stepMs = parseInt(query.step, 10) * 1000;
  }

  return ret;
}

module.exports = Server;
<|endoftext|>


<commit_before>var util = require('./util');
var bodyParser = require('body-parser');
var WebSocketServer = require('ws').Server;
var url = require('url');
var cors = require('cors');

var app = exports = module.exports = {};

/** Initialize WebSocket server. */
app._initializeWSS = function(server) {
  var self = this;

  if (this.mountpath instanceof Array) {
    throw new Error("This app can only be mounted on a single path");
  }

  var path = this.mountpath;
  var path = path + (path[path.length - 1] != '/' ? '/' : '') + 'peerjs';

  // Create WebSocket server as well.
  this._wss = new WebSocketServer({ path: path, server: server});

  this._wss.on('connection', function(socket) {
    var query = url.parse(socket.upgradeReq.url, true).query;
    var id = query.id;
    var token = query.token;
    var key = query.key;
    var ip = socket.upgradeReq.socket.remoteAddress;

    if (!id || !token || !key) {
      socket.send(JSON.stringify({ type: 'ERROR', payload: { msg: 'No id, token, or key supplied to websocket server' } }));
      socket.close();
      return;
    }

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err) {
          if (!self._clients[key][id]) {
            self._clients[key][id] = { token: token, ip: ip };
            self._ips[ip]++;
            socket.send(JSON.stringify({ type: 'OPEN' }));
          }
          self._configureWS(socket, key, id, token);
        } else {
          socket.send(JSON.stringify({ type: 'ERROR', payload: { msg: err } }));
        }
      });
    } else {
      self._configureWS(socket, key, id, token);
    }
  });
};

app._configureWS = function(socket, key, id, token) {

  var self = this;
  var client = this._clients[key][id];

  if (token === client.token) {
    // res 'close' event will delete client.res for us
    client.socket = socket;
    // Client already exists
    if (client.res) {
      client.res.end();
    }
  } else {
    // ID-taken, invalid token
    socket.send(JSON.stringify({ type: 'ID-TAKEN', payload: { msg: 'ID is taken' } }));
    socket.close();
    return;
  }

  this._processOutstanding(key, id);

  // Cleanup after a socket closes.
  socket.on('close', function() {
    self._log('Socket closed:', id);
    if (client.socket == socket) {
      self._removePeer(key, id);
    }
  });

  // Handle messages from peers.
  socket.on('message', function(data) {
    try {
      var message = JSON.parse(data);

      if (['LEAVE', 'CANDIDATE', 'OFFER', 'ANSWER'].indexOf(message.type) !== -1) {
        self._handleTransmission(key, {
          type: message.type,
          src: id,
          dst: message.dst,
          payload: message.payload
        });
      } else {
        util.prettyError('Message unrecognized');
      }
    } catch(e) {
      self._log('Invalid message', data);
      throw e;
    }
  });

  // We're going to emit here, because for XHR we don't *know* when someone
  // disconnects.
  this.emit('connection', id);
};

app._checkAllowsDiscovery = function(key, cb) {
  cb(this._options.allow_discovery);
};

app._checkKey = function(key, ip, cb) {
  if (key == this._options.key) {
    if (!this._clients[key]) {
      this._clients[key] = {};
    }
    if (!this._outstanding[key]) {
      this._outstanding[key] = {};
    }
    if (!this._ips[ip]) {
      this._ips[ip] = 0;
    }
    // Check concurrent limit
    if (Object.keys(this._clients[key]).length >= this._options.concurrent_limit) {
      cb('Server has reached its concurrent user limit');
      return;
    }
    if (this._ips[ip] >= this._options.ip_limit) {
      cb(ip + ' has reached its concurrent user limit');
      return;
    }
    cb(null);
  } else {
    cb('Invalid key provided');
  }
};

/** Initialize HTTP server routes. */
app._initializeHTTP = function() {
  var self = this;

  this.use(cors());

  this.get('/', function(req, res, next) {
    res.send(require('../app.json'));
  });

  // Retrieve guaranteed random ID.
  this.get('/:key/id', function(req, res, next) {
    res.contentType = 'text/html';
    res.send(self._generateClientId(req.params.key));
  });

  // Server sets up HTTP streaming when you get post an ID.
  this.post('/:key/:id/:token/id', function(req, res, next) {
    var id = req.params.id;
    var token = req.params.token;
    var key = req.params.key;
    var ip = req.connection.remoteAddress;

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err && !self._clients[key][id]) {
          self._clients[key][id] = { token: token, ip: ip };
          self._ips[ip]++;
          self._startStreaming(res, key, id, token, true);
        } else {
          res.send(JSON.stringify({ type: 'HTTP-ERROR' }));
        }
      });
    } else {
      self._startStreaming(res, key, id, token);
    }
  });

  // Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
  this.get('/:key/peers', function(req, res, next) {
    var key = req.params.key;
    if (self._clients[key]) {
      self._checkAllowsDiscovery(key, function(isAllowed) {
        if (isAllowed) {
          res.send(Object.keys(self._clients[key]));
        } else {
          res.sendStatus(401);
        }
      });
    } else {
      res.sendStatus(404);
    }
  });

  var handle = function(req, res, next) {
    var key = req.params.key;
    var id = req.params.id;

    var client;
    if (!self._clients[key] || !(client = self._clients[key][id])) {
      if (req.params.retry) {
        res.sendStatus(401);
      } else {
        // Retry this request
        req.params.retry = true;
        setTimeout(handle, 25, req, res);
        return;
      }
    }

    // Auth the req
    if (req.params.token !== client.token) {
      res.sendStatus(401);
      return;
    } else {
      self._handleTransmission(key, {
        type: req.body.type,
        src: id,
        dst: req.body.dst,
        payload: req.body.payload
      });
      res.sendStatus(200);
    }
  };

  var jsonParser = bodyParser.json();

  this.post('/:key/:id/:token/offer', jsonParser, handle);

  this.post('/:key/:id/:token/candidate', jsonParser, handle);

  this.post('/:key/:id/:token/answer', jsonParser, handle);

  this.post('/:key/:id/:token/leave', jsonParser, handle);
};

/** Saves a streaming response and takes care of timeouts and headers. */
app._startStreaming = function(res, key, id, token, open) {
  var self = this;

  res.writeHead(200, {'Content-Type': 'application/octet-stream'});

  var pad = '00';
  for (var i = 0; i < 10; i++) {
    pad += pad;
  }
  res.write(pad + '\n');

  if (open) {
    res.write(JSON.stringify({ type: 'OPEN' }) + '\n');
  }

  var client = this._clients[key][id];

  if (token === client.token) {
    // Client already exists
    res.on('close', function() {
      if (client.res === res) {
        if (!client.socket) {
          // No new request yet, peer dead
          self._removePeer(key, id);
          return;
        }
        delete client.res;
      }
    });
    client.res = res;
    this._processOutstanding(key, id);
  } else {
    // ID-taken, invalid token
    res.end(JSON.stringify({ type: 'HTTP-ERROR' }));
  }
};

app._pruneOutstanding = function() {
  var keys = Object.keys(this._outstanding);
  for (var k = 0, kk = keys.length; k < kk; k += 1) {
    var key = keys[k];
    var dsts = Object.keys(this._outstanding[key]);
    for (var i = 0, ii = dsts.length; i < ii; i += 1) {
      var offers = this._outstanding[key][dsts[i]];
      var seen = {};
      for (var j = 0, jj = offers.length; j < jj; j += 1) {
        var message = offers[j];
        if (!seen[message.src]) {
          this._handleTransmission(key, { type: 'EXPIRE', src: message.dst, dst: message.src });
          seen[message.src] = true;
        }
      }
    }
    this._outstanding[key] = {};
  }
};

/** Cleanup */
app._setCleanupIntervals = function() {
  var self = this;

  // Clean up ips every 10 minutes
  setInterval(function() {
    var keys = Object.keys(self._ips);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      if (self._ips[key] === 0) {
        delete self._ips[key];
      }
    }
  }, 600000);

  // Clean up outstanding messages every 5 seconds
  setInterval(function() {
    self._pruneOutstanding();
  }, 5000);
};

/** Process outstanding peer offers. */
app._processOutstanding = function(key, id) {
  var offers = this._outstanding[key][id];
  if (!offers) {
    return;
  }
  for (var j = 0, jj = offers.length; j < jj; j += 1) {
    this._handleTransmission(key, offers[j]);
  }
  delete this._outstanding[key][id];
};

app._removePeer = function(key, id) {
  if (this._clients[key] && this._clients[key][id]) {
    this._ips[this._clients[key][id].ip]--;
    delete this._clients[key][id];
    this.emit('disconnect', id);
  }
};

/** Handles passing on a message. */
app._handleTransmission = function(key, message) {
  var type = message.type;
  var src = message.src;
  var dst = message.dst;
  var data = JSON.stringify(message);

  var destination = this._clients[key][dst];

  // User is connected!
  if (destination) {
    try {
      this._log(type, 'from', src, 'to', dst);
      if (destination.socket) {
        destination.socket.send(data);
      } else if (destination.res) {
        data += '\n';
        destination.res.write(data);
      } else {
        // Neither socket no res available. Peer dead?
        throw "Peer dead";
      }
    } catch (e) {
      // This happens when a peer disconnects without closing connections and
      // the associated WebSocket has not closed.
      // Tell other side to stop trying.
      this._removePeer(key, dst);
      this._handleTransmission(key, {
        type: 'LEAVE',
        src: dst,
        dst: src
      });
    }
  } else {
    // Wait for this client to connect/reconnect (XHR) for important
    // messages.
    if (type !== 'LEAVE' && type !== 'EXPIRE' && dst) {
      var self = this;
      if (!this._outstanding[key][dst]) {
        this._outstanding[key][dst] = [];
      }
      this._outstanding[key][dst].push(message);
    } else if (type === 'LEAVE' && !dst) {
      this._removePeer(key, src);
    } else {
      // Unavailable destination specified with message LEAVE or EXPIRE
      // Ignore
    }
  }
};

app._generateClientId = function(key) {
  var clientId = util.randomId();
  if (!this._clients[key]) {
    return clientId;
  }
  while (!!this._clients[key][clientId]) {
    clientId = util.randomId();
  }
  return clientId;
};

app._log = function() {
  if (this._options.debug) {
    console.log.apply(console, arguments);
  }
};
<commit_msg>Fixes crash when user is not properly identified<commit_after>var util = require('./util');
var bodyParser = require('body-parser');
var WebSocketServer = require('ws').Server;
var url = require('url');
var cors = require('cors');

var app = exports = module.exports = {};

/** Initialize WebSocket server. */
app._initializeWSS = function(server) {
  var self = this;

  if (this.mountpath instanceof Array) {
    throw new Error("This app can only be mounted on a single path");
  }

  var path = this.mountpath;
  var path = path + (path[path.length - 1] != '/' ? '/' : '') + 'peerjs';

  // Create WebSocket server as well.
  this._wss = new WebSocketServer({ path: path, server: server});

  this._wss.on('connection', function(socket) {
    var query = url.parse(socket.upgradeReq.url, true).query;
    var id = query.id;
    var token = query.token;
    var key = query.key;
    var ip = socket.upgradeReq.socket.remoteAddress;

    if (!id || !token || !key) {
      socket.send(JSON.stringify({ type: 'ERROR', payload: { msg: 'No id, token, or key supplied to websocket server' } }));
      socket.close();
      return;
    }

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err) {
          if (!self._clients[key][id]) {
            self._clients[key][id] = { token: token, ip: ip };
            self._ips[ip]++;
            socket.send(JSON.stringify({ type: 'OPEN' }));
          }
          self._configureWS(socket, key, id, token);
        } else {
          socket.send(JSON.stringify({ type: 'ERROR', payload: { msg: err } }));
        }
      });
    } else {
      self._configureWS(socket, key, id, token);
    }
  });
};

app._configureWS = function(socket, key, id, token) {

  var self = this;
  var client = this._clients[key][id];

  if (token === client.token) {
    // res 'close' event will delete client.res for us
    client.socket = socket;
    // Client already exists
    if (client.res) {
      client.res.end();
    }
  } else {
    // ID-taken, invalid token
    socket.send(JSON.stringify({ type: 'ID-TAKEN', payload: { msg: 'ID is taken' } }));
    socket.close();
    return;
  }

  this._processOutstanding(key, id);

  // Cleanup after a socket closes.
  socket.on('close', function() {
    self._log('Socket closed:', id);
    if (client.socket == socket) {
      self._removePeer(key, id);
    }
  });

  // Handle messages from peers.
  socket.on('message', function(data) {
    try {
      var message = JSON.parse(data);

      if (['LEAVE', 'CANDIDATE', 'OFFER', 'ANSWER'].indexOf(message.type) !== -1) {
        self._handleTransmission(key, {
          type: message.type,
          src: id,
          dst: message.dst,
          payload: message.payload
        });
      } else {
        util.prettyError('Message unrecognized');
      }
    } catch(e) {
      self._log('Invalid message', data);
      throw e;
    }
  });

  // We're going to emit here, because for XHR we don't *know* when someone
  // disconnects.
  this.emit('connection', id);
};

app._checkAllowsDiscovery = function(key, cb) {
  cb(this._options.allow_discovery);
};

app._checkKey = function(key, ip, cb) {
  if (key == this._options.key) {
    if (!this._clients[key]) {
      this._clients[key] = {};
    }
    if (!this._outstanding[key]) {
      this._outstanding[key] = {};
    }
    if (!this._ips[ip]) {
      this._ips[ip] = 0;
    }
    // Check concurrent limit
    if (Object.keys(this._clients[key]).length >= this._options.concurrent_limit) {
      cb('Server has reached its concurrent user limit');
      return;
    }
    if (this._ips[ip] >= this._options.ip_limit) {
      cb(ip + ' has reached its concurrent user limit');
      return;
    }
    cb(null);
  } else {
    cb('Invalid key provided');
  }
};

/** Initialize HTTP server routes. */
app._initializeHTTP = function() {
  var self = this;

  this.use(cors());

  this.get('/', function(req, res, next) {
    res.send(require('../app.json'));
  });

  // Retrieve guaranteed random ID.
  this.get('/:key/id', function(req, res, next) {
    res.contentType = 'text/html';
    res.send(self._generateClientId(req.params.key));
  });

  // Server sets up HTTP streaming when you get post an ID.
  this.post('/:key/:id/:token/id', function(req, res, next) {
    var id = req.params.id;
    var token = req.params.token;
    var key = req.params.key;
    var ip = req.connection.remoteAddress;

    if (!self._clients[key] || !self._clients[key][id]) {
      self._checkKey(key, ip, function(err) {
        if (!err && !self._clients[key][id]) {
          self._clients[key][id] = { token: token, ip: ip };
          self._ips[ip]++;
          self._startStreaming(res, key, id, token, true);
        } else {
          res.send(JSON.stringify({ type: 'HTTP-ERROR' }));
        }
      });
    } else {
      self._startStreaming(res, key, id, token);
    }
  });

  // Get a list of all peers for a key, enabled by the `allowDiscovery` flag.
  this.get('/:key/peers', function(req, res, next) {
    var key = req.params.key;
    if (self._clients[key]) {
      self._checkAllowsDiscovery(key, function(isAllowed) {
        if (isAllowed) {
          res.send(Object.keys(self._clients[key]));
        } else {
          res.sendStatus(401);
        }
      });
    } else {
      res.sendStatus(404);
    }
  });

  var handle = function(req, res, next) {
    var key = req.params.key;
    var id = req.params.id;

    var client;
    if (!self._clients[key] || !(client = self._clients[key][id])) {
      if (req.params.retry) {
        res.sendStatus(401);
        return;
      } else {
        // Retry this request
        req.params.retry = true;
        setTimeout(handle, 25, req, res);
        return;
      }
    }

    // Auth the req
    if (req.params.token !== client.token) {
      res.sendStatus(401);
      return;
    } else {
      self._handleTransmission(key, {
        type: req.body.type,
        src: id,
        dst: req.body.dst,
        payload: req.body.payload
      });
      res.sendStatus(200);
    }
  };

  var jsonParser = bodyParser.json();

  this.post('/:key/:id/:token/offer', jsonParser, handle);

  this.post('/:key/:id/:token/candidate', jsonParser, handle);

  this.post('/:key/:id/:token/answer', jsonParser, handle);

  this.post('/:key/:id/:token/leave', jsonParser, handle);
};

/** Saves a streaming response and takes care of timeouts and headers. */
app._startStreaming = function(res, key, id, token, open) {
  var self = this;

  res.writeHead(200, {'Content-Type': 'application/octet-stream'});

  var pad = '00';
  for (var i = 0; i < 10; i++) {
    pad += pad;
  }
  res.write(pad + '\n');

  if (open) {
    res.write(JSON.stringify({ type: 'OPEN' }) + '\n');
  }

  var client = this._clients[key][id];

  if (token === client.token) {
    // Client already exists
    res.on('close', function() {
      if (client.res === res) {
        if (!client.socket) {
          // No new request yet, peer dead
          self._removePeer(key, id);
          return;
        }
        delete client.res;
      }
    });
    client.res = res;
    this._processOutstanding(key, id);
  } else {
    // ID-taken, invalid token
    res.end(JSON.stringify({ type: 'HTTP-ERROR' }));
  }
};

app._pruneOutstanding = function() {
  var keys = Object.keys(this._outstanding);
  for (var k = 0, kk = keys.length; k < kk; k += 1) {
    var key = keys[k];
    var dsts = Object.keys(this._outstanding[key]);
    for (var i = 0, ii = dsts.length; i < ii; i += 1) {
      var offers = this._outstanding[key][dsts[i]];
      var seen = {};
      for (var j = 0, jj = offers.length; j < jj; j += 1) {
        var message = offers[j];
        if (!seen[message.src]) {
          this._handleTransmission(key, { type: 'EXPIRE', src: message.dst, dst: message.src });
          seen[message.src] = true;
        }
      }
    }
    this._outstanding[key] = {};
  }
};

/** Cleanup */
app._setCleanupIntervals = function() {
  var self = this;

  // Clean up ips every 10 minutes
  setInterval(function() {
    var keys = Object.keys(self._ips);
    for (var i = 0, ii = keys.length; i < ii; i += 1) {
      var key = keys[i];
      if (self._ips[key] === 0) {
        delete self._ips[key];
      }
    }
  }, 600000);

  // Clean up outstanding messages every 5 seconds
  setInterval(function() {
    self._pruneOutstanding();
  }, 5000);
};

/** Process outstanding peer offers. */
app._processOutstanding = function(key, id) {
  var offers = this._outstanding[key][id];
  if (!offers) {
    return;
  }
  for (var j = 0, jj = offers.length; j < jj; j += 1) {
    this._handleTransmission(key, offers[j]);
  }
  delete this._outstanding[key][id];
};

app._removePeer = function(key, id) {
  if (this._clients[key] && this._clients[key][id]) {
    this._ips[this._clients[key][id].ip]--;
    delete this._clients[key][id];
    this.emit('disconnect', id);
  }
};

/** Handles passing on a message. */
app._handleTransmission = function(key, message) {
  var type = message.type;
  var src = message.src;
  var dst = message.dst;
  var data = JSON.stringify(message);

  var destination = this._clients[key][dst];

  // User is connected!
  if (destination) {
    try {
      this._log(type, 'from', src, 'to', dst);
      if (destination.socket) {
        destination.socket.send(data);
      } else if (destination.res) {
        data += '\n';
        destination.res.write(data);
      } else {
        // Neither socket no res available. Peer dead?
        throw "Peer dead";
      }
    } catch (e) {
      // This happens when a peer disconnects without closing connections and
      // the associated WebSocket has not closed.
      // Tell other side to stop trying.
      this._removePeer(key, dst);
      this._handleTransmission(key, {
        type: 'LEAVE',
        src: dst,
        dst: src
      });
    }
  } else {
    // Wait for this client to connect/reconnect (XHR) for important
    // messages.
    if (type !== 'LEAVE' && type !== 'EXPIRE' && dst) {
      var self = this;
      if (!this._outstanding[key][dst]) {
        this._outstanding[key][dst] = [];
      }
      this._outstanding[key][dst].push(message);
    } else if (type === 'LEAVE' && !dst) {
      this._removePeer(key, src);
    } else {
      // Unavailable destination specified with message LEAVE or EXPIRE
      // Ignore
    }
  }
};

app._generateClientId = function(key) {
  var clientId = util.randomId();
  if (!this._clients[key]) {
    return clientId;
  }
  while (!!this._clients[key][clientId]) {
    clientId = util.randomId();
  }
  return clientId;
};

app._log = function() {
  if (this._options.debug) {
    console.log.apply(console, arguments);
  }
};
<|endoftext|>


<commit_before>var express = require('express');
var bodyParser = require('body-parser');
var async = require('async');
var fs = require('fs');

var LISTEN_PORT = 80;
var app, server;

app = express();
app.set('json spaces', 0);

function loadRoutes(app) {

	app.post('/results', function(req, res, next) {
		console.log('New results:', req.body);
		res.send(204);
	});

}

server = app.listen(LISTEN_PORT, function() {
	console.log('Listening on port', server.address().port);
	app.use(bodyParser());
	/*
	app.use(function(req, res, next) {
		console.log('Requesting:', req.url);
		return next();
	});
	*/

	loadRoutes(app);

	app.get('/', function(req, res, next) {
		console.log('Test', req.body);
		res.send(200);
	});

	app.use(function(req, res, next) {
		console.log('Not Found:', req.url);
		res.send(404, 'Not Found: '+req.url);
	});
	app.use(function(err, req, res, next) {
		console.error('Error:', err);
		//next(new Error('Invalid route'));
		res.send(500, {error: err});
	});

	app.server = server;
	return app;
});<commit_msg>Add file writing<commit_after>var express = require('express');
var bodyParser = require('body-parser');
var async = require('async');
var fs = require('fs');

var LISTEN_PORT = 80;
var DATA_FOLDER = './data/';
var app, server;

app = express();
app.set('json spaces', 0);

function loadRoutes(app) {

	app.post('/results', function(req, res, next) {
		console.log('New results for:', req.body.time);

		var results = req.body;

		if(results.time && results.hits.length > 0) {
			results.hits = results.hits.map(function(item) {
				item.x = item.x|0;
				item.y = item.y|0;
				return item;
			});
			var fileName = results.time.replace(/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}).*$/, '$1$2$3_$4$5$6.json');
			fileName = DATA_FOLDER + fileName;
			var writeData = JSON.stringify(results);
			fs.writeFile(fileName, writeData, {encoding: 'utf8'}, function(err, written, buffer) {
				if(err) return console.error(err);
				console.log('File '+fileName+' written.');
			});
		}

		res.send(204);
	});

}

server = app.listen(LISTEN_PORT, function() {
	console.log('Listening on port', server.address().port);
	app.use(bodyParser());

	loadRoutes(app);

	app.get('/', function(req, res, next) {
		console.log('Test', req.body);
		res.send(200);
	});

	app.use(function(req, res, next) {
		console.log('Not Found:', req.url);
		res.send(404, 'Not Found: '+req.url);
	});
	app.use(function(err, req, res, next) {
		console.error('Error:', err, err.stack);
		res.send(500, {error: err});
	});

	app.server = server;
	return app;
});<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';

// Use StrongOps instrumentation, if we have the StrongLoop key in the
// environment.
if (process.env.SL_KEY) {
  require('strong-agent').profile(
    process.env.SL_KEY,
    ['localdata-api', process.env.SL_LOC].join(':')
  );
} else {
  console.log('Skipping StrongOps instrumentation.');
}

var http = require('http');
var express = require('express');
var mongoose = require('mongoose');
var MongoStore = require('connect-mongo')(express);
var uuid = require('node-uuid');
var s3 = require('connect-s3');
var passport = require('passport');

var settings = require('../settings');
var routes = require('./routes');
var postgres = require('./postgres');

// Basic app variables
var server;
var app = express();
var db = null;

// ID generator
var idgen = uuid.v1;

// Specify logging through an environment variable, so we can log differently
// locally, on dev/test Heroku apps, and on production Heroku apps.
if (process.env.EXPRESS_LOGGER) {
  app.use(express.logger(process.env.EXPRESS_LOGGER));
}

// Have Express wrap the response in a function for JSONP requests
// https://github.com/visionmedia/express/issues/664
app.set('jsonp callback', true);

// Use a compact JSON representation
app.set('json spaces', 0);

// Allow Heroku to handle SSL for us
app.enable('trust proxy');

// Allows clients to simulate DELETE and PUT
// (some clients don't support those verbs)
// http://stackoverflow.com/questions/8378338/what-does-connect-js-methodoverride-do
app.use(express.methodOverride());

// IE 8 and 9 can't post application/json for cross-origin requests, so we
// accept text/plain treat it as JSON.
// TODO: if we need to accept text/plain in the future, then we need to adjust
// this
function textParser(req, res, next) {
  if (req._body) { return next(); }
  req.body = req.body || {};

  // For GET/HEAD, there's no body to parse.
  if ('GET' === req.method || 'HEAD' === req.method) { return next(); }

  // Check for text/plain content type
  var type = req.headers['content-type'];
  if (type === undefined || 'text/plain' !== type.split(';')[0]) { return next(); }

  // Flag as parsed
  req._body = true;

  console.log('Got text/plain');

  // Parse text/plain as if it was JSON
  var buf = '';
  req.setEncoding('utf8');
  req.on('data', function(chunk){
    buf += chunk;
  });
  req.on('end', function(){
    try {
      if (!buf.length) {
        req.body = {};
      } else {
        req.body = JSON.parse(buf);
      }
      next();
    } catch (err) {
      err.status = 400;
      next(err);
    }
  });
}

// Actually have Express parse text/plain as JSON
app.use(textParser);

// Default to text/plain if no content-type was provided.
app.use(function(req, res, next) {
  if (req.body) { return next(); }
  if ('GET' === req.method || 'HEAD' === req.method) { return next(); }

  if (!req.headers['content-type']) {
    req.body = {};
    textParser(req, res, next);
  } else {
    next();
  }
});

app.use(express.json());
app.use(express.urlencoded());

// Keep extensions, so we can properly guess MIME types later.
// TODO: We should get the MIME type from the upload headers.
app.use(express.multipart({
  keepExtensions: true
}));

// Let's compress everything!
app.use(express.compress());

// Add common headers
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Mime-Type, X-Requested-With, X-File-Name, Content-Type");
  next();
});

function setupAuth(db) {
  // Authentication-related middleware
  app.use(express.cookieParser());
  app.use(express.session({
    secret: settings.secret,
    store: new MongoStore({
      db: settings.mongo_db,
      mongoose_connection: db,
      maxAge: 300000
      // collection: 'sessions' [default]
    })
  }));

  // Initialize Passport. Also use passport.session() middleware, to support
  // persistent login sessions (recommended).
  app.use(passport.initialize());
  app.use(passport.session());
}

function setupRoutes() {
  routes.setup(app);

  // Serve the mobile collection app from /mobile
  app.use(s3({
    pathPrefix: '/mobile',
    remotePrefix: settings.mobilePrefix
  }));

  app.use(s3({
    pathPrefix: '/apps',
    remotePrefix: settings.appPrefix
  }));

  // Serve tile stuff
  app.use(s3({
    pathPrefix: '/tiles',
    remotePrefix: settings.tilePrefix
  }));

  // Redirect HTTP dashboard requests to HTTPS
  app.use(function (req, res, next) {
    if (req.path === '/' || req.path === '' || req.path === '/index.html') {
      if (!req.secure) {
        return res.redirect('https://' + req.get('host') + req.path);
      }
    }
    next();
  });

  // Serve the ringleader's administration/dashboard app from /
  app.use(s3({
    pathPrefix: '/',
    remotePrefix: settings.adminPrefix
  }));
}

// We're done with our preflight stuff.
// Now, we start listening for requests.
function startServer(port, cb) {
  server = http.createServer(app);
  server.listen(port, function (err) {
    console.log('Listening on ' + port);
    if (cb !== undefined) { cb(err); }
  });
}

function run(cb) {
  // Set up the database object
  if (db === null || db.readyState === 0) {
    if (db !== null) {
      db.removeAllListeners();
    }

    console.log('Using the following settings:');
    console.log('Port: ' + settings.port);
    console.log('Mongo host: ' + settings.mongo_host);
    console.log('Mongo port: ' + settings.mongo_port);
    console.log('Mongo db: ' + settings.mongo_db);
    console.log('Mongo user: ' + settings.mongo_user);
    console.log('Postgresql connection string: ' + settings.psqlConnectionString);

    var opts = {
      db: {
        w: 1,
        safe: true,
        native_parser: settings.mongo_native_parser
      },
      server: {
        socketOptions: {
          // If we attempt to connect for 45 seconds, stop.
          connectTimeoutMS: 45000,
          keepAlive: 1
        }
      }
    };

    if (settings.mongo_user !== undefined) {
      opts.user = settings.mongo_user;
      opts.pass = settings.mongo_password;
    }

    mongoose.connect(settings.mongo_host, settings.mongo_db, settings.mongo_port, opts);

    db = mongoose.connection;

    db.on('error', function (error) {
      console.log('Error connecting to MongoDB server:');
      console.log(error);
      throw error;
    });

    db.on('disconnected', function () {
      console.log('Error: we have been disconnected from the MongoDB server.');
    });

    db.on('reconnected', function () {
      console.log('We have reconnected to the MongoDB server.');
    });

    db.once('open', function () {
      setupAuth(db);
      setupRoutes(); // Needs to happen AFTER setupAuth
      startServer(settings.port, cb);
    });
  }
}

function stop(done) {
  server.close();
  postgres.close();
  db.close(function () {
    console.log('Stopped server');
    if (done) { return done(); }
  });
}

module.exports = {
  run: run,
  stop: stop
};

// If this was run directly, run!
if (require.main === module) {
  run();
}
<commit_msg>Factor out mongo connection code<commit_after>/*jslint node: true */
'use strict';

// Use StrongOps instrumentation, if we have the StrongLoop key in the
// environment.
if (process.env.SL_KEY) {
  require('strong-agent').profile(
    process.env.SL_KEY,
    ['localdata-api', process.env.SL_LOC].join(':')
  );
} else {
  console.log('Skipping StrongOps instrumentation.');
}

var http = require('http');
var express = require('express');
var MongoStore = require('connect-mongo')(express);
var uuid = require('node-uuid');
var s3 = require('connect-s3');
var passport = require('passport');

var mongo = require('./mongo');
var settings = require('../settings');
var routes = require('./routes');
var postgres = require('./postgres');

// Basic app variables
var server;
var app = express();
var db = null;

// ID generator
var idgen = uuid.v1;

// Specify logging through an environment variable, so we can log differently
// locally, on dev/test Heroku apps, and on production Heroku apps.
if (process.env.EXPRESS_LOGGER) {
  app.use(express.logger(process.env.EXPRESS_LOGGER));
}

// Have Express wrap the response in a function for JSONP requests
// https://github.com/visionmedia/express/issues/664
app.set('jsonp callback', true);

// Use a compact JSON representation
app.set('json spaces', 0);

// Allow Heroku to handle SSL for us
app.enable('trust proxy');

// Allows clients to simulate DELETE and PUT
// (some clients don't support those verbs)
// http://stackoverflow.com/questions/8378338/what-does-connect-js-methodoverride-do
app.use(express.methodOverride());

// IE 8 and 9 can't post application/json for cross-origin requests, so we
// accept text/plain treat it as JSON.
// TODO: if we need to accept text/plain in the future, then we need to adjust
// this
function textParser(req, res, next) {
  if (req._body) { return next(); }
  req.body = req.body || {};

  // For GET/HEAD, there's no body to parse.
  if ('GET' === req.method || 'HEAD' === req.method) { return next(); }

  // Check for text/plain content type
  var type = req.headers['content-type'];
  if (type === undefined || 'text/plain' !== type.split(';')[0]) { return next(); }

  // Flag as parsed
  req._body = true;

  console.log('Got text/plain');

  // Parse text/plain as if it was JSON
  var buf = '';
  req.setEncoding('utf8');
  req.on('data', function(chunk){
    buf += chunk;
  });
  req.on('end', function(){
    try {
      if (!buf.length) {
        req.body = {};
      } else {
        req.body = JSON.parse(buf);
      }
      next();
    } catch (err) {
      err.status = 400;
      next(err);
    }
  });
}

// Actually have Express parse text/plain as JSON
app.use(textParser);

// Default to text/plain if no content-type was provided.
app.use(function(req, res, next) {
  if (req.body) { return next(); }
  if ('GET' === req.method || 'HEAD' === req.method) { return next(); }

  if (!req.headers['content-type']) {
    req.body = {};
    textParser(req, res, next);
  } else {
    next();
  }
});

app.use(express.json());
app.use(express.urlencoded());

// Keep extensions, so we can properly guess MIME types later.
// TODO: We should get the MIME type from the upload headers.
app.use(express.multipart({
  keepExtensions: true
}));

// Let's compress everything!
app.use(express.compress());

// Add common headers
app.use(function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Origin, X-Mime-Type, X-Requested-With, X-File-Name, Content-Type");
  next();
});

function setupAuth(db) {
  // Authentication-related middleware
  app.use(express.cookieParser());
  app.use(express.session({
    secret: settings.secret,
    store: new MongoStore({
      db: settings.mongo_db,
      mongoose_connection: db,
      maxAge: 300000
      // collection: 'sessions' [default]
    })
  }));

  // Initialize Passport. Also use passport.session() middleware, to support
  // persistent login sessions (recommended).
  app.use(passport.initialize());
  app.use(passport.session());
}

function setupRoutes() {
  routes.setup(app);

  // Serve the mobile collection app from /mobile
  app.use(s3({
    pathPrefix: '/mobile',
    remotePrefix: settings.mobilePrefix
  }));

  app.use(s3({
    pathPrefix: '/apps',
    remotePrefix: settings.appPrefix
  }));

  // Serve tile stuff
  app.use(s3({
    pathPrefix: '/tiles',
    remotePrefix: settings.tilePrefix
  }));

  // Redirect HTTP dashboard requests to HTTPS
  app.use(function (req, res, next) {
    if (req.path === '/' || req.path === '' || req.path === '/index.html') {
      if (!req.secure) {
        return res.redirect('https://' + req.get('host') + req.path);
      }
    }
    next();
  });

  // Serve the ringleader's administration/dashboard app from /
  app.use(s3({
    pathPrefix: '/',
    remotePrefix: settings.adminPrefix
  }));
}

// We're done with our preflight stuff.
// Now, we start listening for requests.
function startServer(port, cb) {
  server = http.createServer(app);
  server.listen(port, function (err) {
    console.log('Listening on ' + port);
    if (cb !== undefined) { cb(err); }
  });
}

function run(cb) {
  // Set up the database object
  if (db === null || db.readyState === 0) {
    if (db !== null) {
      db.removeAllListeners();
    }

    console.log('Using the following settings:');
    console.log('Port: ' + settings.port);
    console.log('Mongo host: ' + settings.mongo_host);
    console.log('Mongo port: ' + settings.mongo_port);
    console.log('Mongo db: ' + settings.mongo_db);
    console.log('Mongo user: ' + settings.mongo_user);
    console.log('Postgresql connection string: ' + settings.psqlConnectionString);

    db = mongo.connect(function () {
      setupAuth(db);
      setupRoutes(); // Needs to happen AFTER setupAuth
      startServer(settings.port, cb);
    });

    db.on('disconnected', function () {
      console.log('Error: we have been disconnected from the MongoDB server.');
    });

  }
}

function stop(done) {
  server.close();
  postgres.close();
  db.close(function () {
    console.log('Stopped server');
    if (done) { return done(); }
  });
}

module.exports = {
  run: run,
  stop: stop
};

// If this was run directly, run!
if (require.main === module) {
  run();
}
<|endoftext|>


<commit_before>// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) 2012 DotCloud Inc (opensource@dotcloud.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

var socket = require("./socket"),
    _ = require("underscore"),
    nodeUtil = require("util"),
    events = require("events"),
    util = require("./util"),
    middleware = require("./middleware");

//Gets the arguments associated with a function as an array
//fun : Function
//      The function to get the arguments from.
//return : Array of Strings
//      The function's arguments
function getArguments(fun) {
    var m1 = /^[\s\(]*function[^(]*\(([^)]*)\)/,
        m2 = /\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,
        m3 = /\s+/g;

    var names = fun.toString().match(m1)[1].replace(m2, '').replace(m3, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
}

//Extracts the public methods of a context, and injects a method to introspect
//on the context.
//context : Object
//      The object to be exposed
//return : Object of String => Boolean
//      The set of public methods
function publicMethods(context) {
    var methods = {};

    //Ignore members that start with an underscore or are not functions
    for(var name in context) {
        if(!/^_/.test(name) && typeof(context[name]) == 'function') {
            methods[name] = true;
        }
    }

    return methods;
}

//Add introspector function to a method
//context : Object
//      The object to be exposed
//methods : Object
//      The set public methods of the exposed object
function addIntrospector(context, methods) {
    var results = {};

    for(var name in methods) {
        var args = getArguments(context[name]);

        //Remove callback argument
        args.pop();

        results[name] = { doc: "", args: args };
    }

    methods["_zpc_inspect"] = true;

    context["_zpc_inspect"] = function(reply) {
        reply(results);
    };
}

// Creates a new server
// context : Object
//      The object to expose
function Server(context) {
    var self = this;

    self._socket = socket.server();
    util.eventProxy(self._socket, self, "error");

    var methods = publicMethods(context);
    addIntrospector(context, methods);

    self._socket.on("multiplexing-socket/receive", function(event) {
        if(event.name in methods) self._recv(event, context);
    });
}

nodeUtil.inherits(Server, events.EventEmitter);

//Called when a method call event is received
//event : Object
//      The ZeroRPC event
//context : Object
//      The object to expose.
Server.prototype._recv = function(event, context) {
    var self = this;
    var ch = self._socket.openChannel(event);
    var isFirst = true;

    //Adds heartbeating
    middleware.addHeartbeat(ch, function(error) {
        if(error) self.emit("error", error);
    });

    //Sends an error
    var sendError = function(error) {
        var args = [error.type || "Error", error.message, error.stack];
        ch.send("ERR", args);
        ch.close();
    };

    //This is passed to RPC methods to call when they finish, or have a stream
    //update
    var result = function(error, item, more) {
        if(arguments.length === 1) {
            more = false;
            item = error;
            error = undefined;
        } else if(arguments.length === 2) {
            more = item;
            item = error;
            error = undefined;
        }

        if(error) {
            //Create an error object if we were passed a string
            sendError(typeof(error) == 'string' ? new Error(error) : error);
        } else {
            if(isFirst && !more) {
                //Wrap item in an array for backwards compatibility issues with ZeroRPC
                ch.send("OK", [item]);
            } else if(item !== undefined) {
                //Stream is a newer method that does not require item to be wrapped in an array
                ch.send("STREAM", item);
            }

            if(!more) {
                if(!isFirst) ch.send("STREAM_DONE", []);
                ch.close();
            }
        }

        isFirst = false;
    };

    //The arguments should be an array
    if(!(event.args instanceof Array)) {
        self.emit("error", "Invalid event: Bad args");
        return ch.close();
    }

    //Call the method
    event.args.push(result);

    //Catch any errors and send them back to the client
    try {
        context[event.name].apply(context, event.args);
    } catch(e) {
        sendError(e);
    }
}

//Binds to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.bind = function(endpoint) {
    this._socket.bind(endpoint);
};

//Connects to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.connect = function(endpoint) {
    this._socket.connect(endpoint);
};

//Closes the server
Server.prototype.close = function() {
    this._socket.close();
};

exports.Server = Server;<commit_msg>Better introspection<commit_after>// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) 2012 DotCloud Inc (opensource@dotcloud.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

var socket = require("./socket"),
    _ = require("underscore"),
    nodeUtil = require("util"),
    events = require("events"),
    util = require("./util"),
    middleware = require("./middleware");

//Gets the arguments associated with a function as an array
//fun : Function
//      The function to get the arguments from.
//return : Array of Strings
//      The function's arguments
function getArguments(fun) {
    var m1 = /^[\s\(]*function[^(]*\(([^)]*)\)/,
        m2 = /\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,
        m3 = /\s+/g;

    var names = fun.toString().match(m1)[1].replace(m2, '').replace(m3, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
}

//Extracts the public methods of a context, and injects a method to introspect
//on the context.
//context : Object
//      The object to be exposed
//return : Object of String => Boolean
//      The set of public methods
function publicMethods(context) {
    var methods = {};

    //Ignore members that start with an underscore or are not functions
    for(var name in context) {
        if(!/^_/.test(name) && typeof(context[name]) == 'function') {
            methods[name] = true;
        }
    }

    return methods;
}

//Add introspector function to a method
//context : Object
//      The object to be exposed
//methods : Object
//      The set public methods of the exposed object
function addIntrospector(context, methods) {
    var results = {};

    for(var name in methods) {
        var args = getArguments(context[name]);
        var argsObjs = [];

        //Ignore the last arg because it is the callback
        for(var i=0; i<args.length-1; i++) {
            argsObjs.push({ name: args[i] });
        }

        results[name] = { doc: "", args: argsObjs };
    }

    methods["_zpc_inspect"] = true;

    context["_zpc_inspect"] = function(reply) {
        reply(results);
    };
}

// Creates a new server
// context : Object
//      The object to expose
function Server(context) {
    var self = this;

    self._socket = socket.server();
    util.eventProxy(self._socket, self, "error");

    var methods = publicMethods(context);
    addIntrospector(context, methods);

    self._socket.on("multiplexing-socket/receive", function(event) {
        if(event.name in methods) self._recv(event, context);
    });
}

nodeUtil.inherits(Server, events.EventEmitter);

//Called when a method call event is received
//event : Object
//      The ZeroRPC event
//context : Object
//      The object to expose.
Server.prototype._recv = function(event, context) {
    var self = this;
    var ch = self._socket.openChannel(event);
    var isFirst = true;

    //Adds heartbeating
    middleware.addHeartbeat(ch, function(error) {
        if(error) self.emit("error", error);
    });

    //Sends an error
    var sendError = function(error) {
        var args = [error.type || "Error", error.message, error.stack];
        ch.send("ERR", args);
        ch.close();
    };

    //This is passed to RPC methods to call when they finish, or have a stream
    //update
    var result = function(error, item, more) {
        if(arguments.length === 1) {
            more = false;
            item = error;
            error = undefined;
        } else if(arguments.length === 2) {
            more = item;
            item = error;
            error = undefined;
        }

        if(error) {
            //Create an error object if we were passed a string
            sendError(typeof(error) == 'string' ? new Error(error) : error);
        } else {
            if(isFirst && !more) {
                //Wrap item in an array for backwards compatibility issues with ZeroRPC
                ch.send("OK", [item]);
            } else if(item !== undefined) {
                //Stream is a newer method that does not require item to be wrapped in an array
                ch.send("STREAM", item);
            }

            if(!more) {
                if(!isFirst) ch.send("STREAM_DONE", []);
                ch.close();
            }
        }

        isFirst = false;
    };

    //The arguments should be an array
    if(!(event.args instanceof Array)) {
        self.emit("error", "Invalid event: Bad args");
        return ch.close();
    }

    //Call the method
    event.args.push(result);

    //Catch any errors and send them back to the client
    try {
        context[event.name].apply(context, event.args);
    } catch(e) {
        sendError(e);
    }
}

//Binds to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.bind = function(endpoint) {
    this._socket.bind(endpoint);
};

//Connects to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.connect = function(endpoint) {
    this._socket.connect(endpoint);
};

//Closes the server
Server.prototype.close = function() {
    this._socket.close();
};

exports.Server = Server;<|endoftext|>