<commit_before>'use strict';

var debug     = require('debug')('kiss.io:server');
var http      = require('http');
var eio       = require('engine.io');
var url       = require('url');

var Client    = require('./client');
var Namespace = require('./namespace');
var Socket    = require('./socket');
var Plugin    = require('./plugin');
var Router    = require('./router');

/**
 * Server is the main component of kiss.io.
 * @constructs Server
 * @access public
 *
 * @param {Object} [opts]: special options for the server.
 * @returns {Server} singleton or new instance.
 */
function Server(opts)
{
  if (!(this instanceof Server))
  {
    return Server.instance(opts);
  }

  this.engine = null;
  this.httpServer = null;
  this.nsps = {};
  this.opts = opts || {};

  this.path(this.opts.path || '/socket.io');
  this.origins(this.opts.origins || '*:*');
}

/**
 * Get or create if not exists a Server instance which will act as
 * ..a global singleton which is accessible through the application.
 *
 * @access public
 *
 * @param {Object} [opts]: opts to pass to the `Server` constructor
 * @returns {Server} singleton instance
 */
Server.instance = function(opts)
{
  if(!Server.singleton)
  {
    Server.singleton = new Server(opts);
  }

  return Server.singleton;
};

/*!
 * Static `Server` exports/shortcuts.
 */
Server.Server = Server;
Server.Namespace = Namespace;
Server.Client = Client;
Server.Socket = Socket;
Server.Plugin = Plugin;
Server.Router = Router;

/**
 * Server request verification function, that checks for allowed origins
 *
 * @access private
 *
 * @param {http.IncomingMessage} req
 * @param {Function} fn: callback that takes `err, success`.
 */
Server.prototype.checkRequest = function(req, fn)
{
  var origin = req.headers.origin || req.headers.referer;

  // file:// URLs produce a null Origin which can't be authorized via echo-back
  if ('null' == origin || null == origin) origin = '*';

  if (!!origin && typeof(this._origins) == 'function')
  {
    return this._origins(origin, fn);
  }
  else if (this._origins.indexOf('*:*') !== -1)
  {
    return fn(null, true);
  }

  if (origin)
  {
    try
    {
      var parts = url.parse(origin);
      var defaultPort = 'https:' == parts.protocol ? 443 : 80;

      parts.port = parts.port != null
        ? parts.port
        : defaultPort;

      var ok =
        ~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
        ~this._origins.indexOf(parts.hostname + ':*') ||
        ~this._origins.indexOf('*:' + parts.port);

      return fn(null, !!ok);
    }
    catch (e)
    {
    }
  }

  fn(null, false);
};

/**
 * Sets/gets the transport path of the server, used by the clients to connect
 * ..to the server.
 *
 * @access public
 *
 * @param {String} [path] pathname
 * @returns {String} transport path
 */
Server.prototype.path = function(path)
{
  if (arguments.length)
  {
    this._path = path.replace(/\/$/, '');
    this.opts.path = this._path;
  }

  return this._path;
};

/**
 * Sets/gets the allowed origins for requests.
 *
 * @access public
 *
 * @param {String} [origins]
 * @returns {String} app's allowed origins.
 */
Server.prototype.origins = function(origins)
{
  if (arguments.length)
  {
    this._origins = origins;
  }

  return this._origins;
};

/**
 * Used internally.
 * Creates a basic http server to attach the transport layer on.
 * It can be used in conjunction with express.js by passing its instance as
 * ..param `app`.
 *
 * @static {Server::createHttpServer}
 * @access private
 *
 * @param {Function} [app]: if provided, used instead of the default listener.
 * ..(can be an instance of express.js).
 * @returns {http.Server}
 */
Server.createHttpServer = function(app)
{
  function defaultListener(req, res)
  {
    res.writeHead(404);
    res.end();
  }

  return http.createServer(app || defaultListener);
};

/**
 * Attaches a http sever to the kiss.io `Server`, to be used as a transport
 * ..layer for the clients to connect to.
 * If no http server is provided before calling `Server.listen` - a default
 * ..basic http server will be initiated for you.
 * If `attach` is called while there's already a http server attached to the
 * ..`Server`, the server will close and destroy the old http server and start
 * ..a new one as provided.
 *
 * TODO: add support for https
 *
 * @alias {Server.attachHttp}
 * @access public
 *
 * @param {http.Server|Function} srv
 * @param {Object} [opts]: special opts to pass to the underlying engine.io.
 * @returns {Server} self
 */
Server.prototype.attach =
Server.prototype.attachHttp = function(srv, opts)
{
  if(this.httpServer)
  {
    this.close();
    delete this.httpServer;
  }

  if(!(srv instanceof http.Server))
  {
    srv = Server.createHttpServer(srv);
  }

  opts = opts || {};
  opts.path = opts.path || this.path();
  opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);

  this.httpServer = srv;

  // bind to engine events
  debug('creating engine.io instance with opts %j', opts);
  this.bind(eio.attach(srv, opts));

  return this;
};

/**
 * Until called, `Server` won't be accessible from the "outside world".
 * `arguments` are passed to the http.Server.listen function -- see docs.
 *
 * If no http server is provided before calling `listen` - a default, basic
 * ..http server is created with Server.attachHttp().
 *
 * @see http.Server.listen
 * @url https://nodejs.org/api/http.html#http_server_listen_handle_callback
 *
 * @access public
 *
 * @returns {Server} self
 */
Server.prototype.listen = function()
{
  if(!this.httpServer)
  {
    this.attachHttp();
  }

  this.httpServer.listen.apply(this.httpServer, arguments);

  return this;
};

/**
 * Binds `Server` to an engine.io instance.
 *
 * @access public
 *
 * @param {eio.Server} engine: engine.io (or compatible) server.
 * @return {Server} self
 */
Server.prototype.bind = function(engine)
{
  this.engine = engine;
  this.engine.on('connection', this.onConnection.bind(this));

  return this;
};

/**
 * Called with each incoming transport connection.
 *
 * @access private
 *
 * @param {eio.Socket} conn
 * @return {Client} the new `Client` created for the connection.
 */
Server.prototype.onConnection = function(conn)
{
  debug('incoming connection with id %s', conn.id);

  var client = new Client(this, conn);

  // done to overcome a bug with socket.io-client when connecting
  // ..to default namespace '/'.
  // socket.io-client assumes that the client is connected to '/'
  // ..nsp by default and therefore doesn't send a connect packet
  // ..when trying to connect to '/'.
  if(this.nsps['/'])
  {
    client.connect('/');
  }

  return client;
};

/**
 * Closes server connection, throws sockets and destroys http server instance.
 *
 * @access public
 *
 * @returns {Server} self
 */
Server.prototype.close = function()
{
  for(let nspId of Object.keys(this.nsps))
  {
    let nsp = this.nsps[nspId];

    for(let sid of Object.keys(nsp.sockets))
    {
      nsp.socket(sid).onClose();
    }
  }

  this.engine.close();

  if(this.httpServer)
  {
    this.httpServer.close();
  }

  return this;
};

/**
 * Targets (by returning) existing/newly-created namespace which is mounted
 * ..on the server.
 * If the namespace doesn't exists -- creates a new one with the parameters provided.
 *
 * @alias {Server.namespace}
 * @access public
 *
 * @param {String} name: the name of the namespace to return/create.
 * @param {Object} [opts]: if nsp doesn't exists -- create new and use these opts.
 * @returns {Namespace}
 */

Server.prototype.of =
Server.prototype.namespace = function(name, opts)
{
  var nsp = this.nsps[Namespace.slugify(name)];

  if(!nsp)
  {
    debug('initializing namespace %s', name);
    nsp = new Namespace(name, opts);
    this.nsps[nsp.name] = nsp;
  }

  return nsp;
};

/**
 * Mounts a namespace instance on the server.
 *
 * @alias {Server.mount}
 * @access public
 *
 * @param {String} [name]: if provided overrides the given nsp name.
 * @param {Namespace} nsp: the namespace instance to mount on the server.
 * @returns {Server} self
 */
Server.prototype.use =
Server.prototype.mount = function(name, nsp)
{
  if(!(nsp instanceof Namespace)
    && !(name instanceof Namespace))
  {
    debug('Server.mount: nsp must be instance of `Namespace` class!');
  }

  if(arguments.length == 1)
  {
    nsp = name;
  }
  else if(arguments.length == 2)
  {
    nsp.name = Namespace.slugify(name);
  }

  this.nsps[nsp.name] = nsp;
  return this;
};

/*!
 * Module exports.
 */
module.exports = Server;
<commit_msg>Server.origins now accept also String, not only Array or Fn<commit_after>'use strict';

var debug     = require('debug')('kiss.io:server');
var http      = require('http');
var eio       = require('engine.io');
var url       = require('url');

var Client    = require('./client');
var Namespace = require('./namespace');
var Socket    = require('./socket');
var Plugin    = require('./plugin');
var Router    = require('./router');

/**
 * Server is the main component of kiss.io.
 * @constructs Server
 * @access public
 *
 * @param {Object} [opts]: special options for the server.
 * @returns {Server} singleton or new instance.
 */
function Server(opts)
{
  if (!(this instanceof Server))
  {
    return Server.instance(opts);
  }

  this.engine = null;
  this.httpServer = null;
  this.nsps = {};
  this.opts = opts || {};

  this.path(this.opts.path || '/socket.io');
  this.origins(this.opts.origins || '*:*');
}

/**
 * Get or create if not exists a Server instance which will act as
 * ..a global singleton which is accessible through the application.
 *
 * @access public
 *
 * @param {Object} [opts]: opts to pass to the `Server` constructor
 * @returns {Server} singleton instance
 */
Server.instance = function(opts)
{
  if(!Server.singleton)
  {
    Server.singleton = new Server(opts);
  }

  return Server.singleton;
};

/*!
 * Static `Server` exports/shortcuts.
 */
Server.Server = Server;
Server.Namespace = Namespace;
Server.Client = Client;
Server.Socket = Socket;
Server.Plugin = Plugin;
Server.Router = Router;

/**
 * Server request verification function, that checks for allowed origins
 *
 * @access private
 *
 * @param {http.IncomingMessage} req
 * @param {Function} fn: callback that takes `err, success`.
 */
Server.prototype.checkRequest = function(req, fn)
{
  var origin = req.headers.origin || req.headers.referer;

  // file:// URLs produce a null Origin which can't be authorized via echo-back
  if ('null' == origin || null == origin) origin = '*';

  if (!!origin && typeof(this._origins) == 'function')
  {
    return this._origins(origin, fn);
  }
  else if (this._origins.indexOf('*:*') !== -1)
  {
    return fn(null, true);
  }

  if (origin)
  {
    try
    {
      var parts = url.parse(origin);
      var defaultPort = 'https:' == parts.protocol ? 443 : 80;

      parts.port = parts.port != null
        ? parts.port
        : defaultPort;

      var ok =
        ~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
        ~this._origins.indexOf(parts.hostname + ':*') ||
        ~this._origins.indexOf('*:' + parts.port);

      return fn(null, !!ok);
    }
    catch (e)
    {
    }
  }

  fn(null, false);
};

/**
 * Sets/gets the transport path of the server, used by the clients to connect
 * ..to the server.
 *
 * @access public
 *
 * @param {String} [path] pathname
 * @returns {String} transport path
 */
Server.prototype.path = function(path)
{
  if (arguments.length)
  {
    this._path = path.replace(/\/$/, '');
    this.opts.path = this._path;
  }

  return this._path;
};

/**
 * Sets/gets the allowed origins for requests.
 *
 * @access public
 *
 * @param {String|Array<String>|Function} [origins]
 * @returns {String} app's allowed origins.
 */
Server.prototype.origins = function(origins)
{
  if (arguments.length)
  {
    if(typeof(origins) != 'function' &&
      !Array.isArray(origins))
    {
      origins = [origins];
    }

    this._origins = origins;
  }

  return this._origins;
};

/**
 * Used internally.
 * Creates a basic http server to attach the transport layer on.
 * It can be used in conjunction with express.js by passing its instance as
 * ..param `app`.
 *
 * @static {Server::createHttpServer}
 * @access private
 *
 * @param {Function} [app]: if provided, used instead of the default listener.
 * ..(can be an instance of express.js).
 * @returns {http.Server}
 */
Server.createHttpServer = function(app)
{
  function defaultListener(req, res)
  {
    res.writeHead(404);
    res.end();
  }

  return http.createServer(app || defaultListener);
};

/**
 * Attaches a http sever to the kiss.io `Server`, to be used as a transport
 * ..layer for the clients to connect to.
 * If no http server is provided before calling `Server.listen` - a default
 * ..basic http server will be initiated for you.
 * If `attach` is called while there's already a http server attached to the
 * ..`Server`, the server will close and destroy the old http server and start
 * ..a new one as provided.
 *
 * TODO: add support for https
 *
 * @alias {Server.attachHttp}
 * @access public
 *
 * @param {http.Server|Function} srv
 * @param {Object} [opts]: special opts to pass to the underlying engine.io.
 * @returns {Server} self
 */
Server.prototype.attach =
Server.prototype.attachHttp = function(srv, opts)
{
  if(this.httpServer)
  {
    this.close();
    delete this.httpServer;
  }

  if(!(srv instanceof http.Server))
  {
    srv = Server.createHttpServer(srv);
  }

  opts = opts || {};
  opts.path = opts.path || this.path();
  opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);

  this.httpServer = srv;

  // bind to engine events
  debug('creating engine.io instance with opts %j', opts);
  this.bind(eio.attach(srv, opts));

  return this;
};

/**
 * Until called, `Server` won't be accessible from the "outside world".
 * `arguments` are passed to the http.Server.listen function -- see docs.
 *
 * If no http server is provided before calling `listen` - a default, basic
 * ..http server is created with Server.attachHttp().
 *
 * @see http.Server.listen
 * @url https://nodejs.org/api/http.html#http_server_listen_handle_callback
 *
 * @access public
 *
 * @returns {Server} self
 */
Server.prototype.listen = function()
{
  if(!this.httpServer)
  {
    this.attachHttp();
  }

  this.httpServer.listen.apply(this.httpServer, arguments);

  return this;
};

/**
 * Binds `Server` to an engine.io instance.
 *
 * @access public
 *
 * @param {eio.Server} engine: engine.io (or compatible) server.
 * @return {Server} self
 */
Server.prototype.bind = function(engine)
{
  this.engine = engine;
  this.engine.on('connection', this.onConnection.bind(this));

  return this;
};

/**
 * Called with each incoming transport connection.
 *
 * @access private
 *
 * @param {eio.Socket} conn
 * @return {Client} the new `Client` created for the connection.
 */
Server.prototype.onConnection = function(conn)
{
  debug('incoming connection with id %s', conn.id);

  var client = new Client(this, conn);

  // done to overcome a bug with socket.io-client when connecting
  // ..to default namespace '/'.
  // socket.io-client assumes that the client is connected to '/'
  // ..nsp by default and therefore doesn't send a connect packet
  // ..when trying to connect to '/'.
  if(this.nsps['/'])
  {
    client.connect('/');
  }

  return client;
};

/**
 * Closes server connection, throws sockets and destroys http server instance.
 *
 * @access public
 *
 * @returns {Server} self
 */
Server.prototype.close = function()
{
  for(let nspId of Object.keys(this.nsps))
  {
    let nsp = this.nsps[nspId];

    for(let sid of Object.keys(nsp.sockets))
    {
      nsp.socket(sid).onClose();
    }
  }

  this.engine.close();

  if(this.httpServer)
  {
    this.httpServer.close();
  }

  return this;
};

/**
 * Targets (by returning) existing/newly-created namespace which is mounted
 * ..on the server.
 * If the namespace doesn't exists -- creates a new one with the parameters provided.
 *
 * @alias {Server.namespace}
 * @access public
 *
 * @param {String} name: the name of the namespace to return/create.
 * @param {Object} [opts]: if nsp doesn't exists -- create new and use these opts.
 * @returns {Namespace}
 */

Server.prototype.of =
Server.prototype.namespace = function(name, opts)
{
  var nsp = this.nsps[Namespace.slugify(name)];

  if(!nsp)
  {
    debug('initializing namespace %s', name);
    nsp = new Namespace(name, opts);
    this.nsps[nsp.name] = nsp;
  }

  return nsp;
};

/**
 * Mounts a namespace instance on the server.
 *
 * @alias {Server.mount}
 * @access public
 *
 * @param {String} [name]: if provided overrides the given nsp name.
 * @param {Namespace} nsp: the namespace instance to mount on the server.
 * @returns {Server} self
 */
Server.prototype.use =
Server.prototype.mount = function(name, nsp)
{
  if(!(nsp instanceof Namespace)
    && !(name instanceof Namespace))
  {
    debug('Server.mount: nsp must be instance of `Namespace` class!');
  }

  if(arguments.length == 1)
  {
    nsp = name;
  }
  else if(arguments.length == 2)
  {
    nsp.name = Namespace.slugify(name);
  }

  this.nsps[nsp.name] = nsp;
  return this;
};

/*!
 * Module exports.
 */
module.exports = Server;
<|endoftext|>


<commit_before>var _ = require('underscore'),
    async = require('async'),
    child_process = require('child_process'),
    handlebars = require('handlebars'),
    tempfile = require('tempfile'),
    jsonfile = require('jsonfile'),
    express = require('express'),
    assert = require('assert'),
    body_parser = require('body-parser');

function server(config) {
  var defaults = {
    'port': 3000,
    'verbose': false
  }
  var config = _.extend(_.clone(defaults), config || {});
  if (config.verbose) {
    console.log(config);
  }

  var queues = {};
  _.each(config.repos, function (repo, url) {
    queues[url] = async.queue(function (payload, callback) {
      var jsonFile = tempfile('.json');
      jsonfile.writeFileSync(jsonFile, payload);
      payload.file = jsonFile;
      var command = handlebars.compile(repo.command)(payload);
      if (config.verbose) {
        console.log("Running " + command);
      }
      child_process.exec(command, function (err) {
        if (err && config.verbose) {
          console.log("Command failed: " + err);
        }
        callback();
      });
    }, 1);
  });

  this.app = express();
  app.use(body_parser.urlencoded({extended: true}));
  app.post(/.*/, function (req, res) {
    var payload = decodeURIComponent(req.body.payload);
    if (config.verbose) {
      console.log(payload);
    }

    try {
      payload = JSON.parse(payload);
    } catch (err) {
      if (config.verbose) {
        console.log("Error parsing payload: " + err);
      }
      res.status(500).send();
      return;
    }
    try {
      var url = parseURL(payload);
      assert(url in config.repos);
    } catch (err) {
      if (config.verbose) {
        console.log("Repository not registered: " + url);
      }
      res.status(404).send();
      return;
    }
    try {
      var branch = parseBranch(payload);
      assert(branch);
    } catch (err) {
      if (config.verbose) {
        console.log("Couldn't parse branch from payload");
      }
      res.status(500).send();
      return;
    }
    var normalizedInfo = {
      commit: payload.commit || payload.head_commit.id,
      branch: branch,
      message: payload.message || payload.head_commit.message,
      author_name: payload.author_name || payload.head_commit.author.name,
      author_email: payload.author_email || payload.head_commit.author.email,
      timestamp: payload.committed_at || payload.head_commit.timestamp
    }
    queues[url].push(normalizedInfo);
    res.status(200).send();
  });

  this.start = function() {
    app.listen(config.port);
  }

  return this;
}

var branchPattern = /^refs\/heads\/(\w+)$/;
var parseBranch = function (payload) {
  return payload.branch || branchPattern.exec(payload.ref)[1];
}

var repoURLPattern = /^(.*?)\/compare\/.*$/;
var parseURL = function (payload) {
  return repoURLPattern.exec(payload.compare || payload.compare_url)[1];
}

exports = module.exports = server
exports.parseURL = parseURL

// vim: ts=2:sw=2:et
<commit_msg>More output when commands succeed.<commit_after>var _ = require('underscore'),
    async = require('async'),
    child_process = require('child_process'),
    handlebars = require('handlebars'),
    tempfile = require('tempfile'),
    jsonfile = require('jsonfile'),
    express = require('express'),
    assert = require('assert'),
    body_parser = require('body-parser');

function server(config) {
  var defaults = {
    'port': 3000,
    'verbose': false
  }
  var config = _.extend(_.clone(defaults), config || {});
  if (config.verbose) {
    console.log(config);
  }

  var queues = {};
  _.each(config.repos, function (repo, url) {
    queues[url] = async.queue(function (payload, callback) {
      var jsonFile = tempfile('.json');
      jsonfile.writeFileSync(jsonFile, payload);
      payload.file = jsonFile;
      var command = handlebars.compile(repo.command)(payload);
      if (config.verbose) {
        console.log("Running " + command);
      }
      child_process.exec(command, function (err) {
        if (config.verbose) {
          if (err) {
            console.log("Command failed: " + err);
          } else {
            console.log("Command succeeded");
          }
        }
        callback();
      });
    }, 1);
  });

  this.app = express();
  app.use(body_parser.urlencoded({extended: true}));
  app.post(/.*/, function (req, res) {
    var payload = decodeURIComponent(req.body.payload);
    if (config.verbose) {
      console.log(payload);
    }

    try {
      payload = JSON.parse(payload);
    } catch (err) {
      if (config.verbose) {
        console.log("Error parsing payload: " + err);
      }
      res.status(500).send();
      return;
    }
    try {
      var url = parseURL(payload);
      assert(url in config.repos);
    } catch (err) {
      if (config.verbose) {
        console.log("Repository not registered: " + url);
      }
      res.status(404).send();
      return;
    }
    try {
      var branch = parseBranch(payload);
      assert(branch);
    } catch (err) {
      if (config.verbose) {
        console.log("Couldn't parse branch from payload");
      }
      res.status(500).send();
      return;
    }
    var normalizedInfo = {
      commit: payload.commit || payload.head_commit.id,
      branch: branch,
      message: payload.message || payload.head_commit.message,
      author_name: payload.author_name || payload.head_commit.author.name,
      author_email: payload.author_email || payload.head_commit.author.email,
      timestamp: payload.committed_at || payload.head_commit.timestamp
    }
    queues[url].push(normalizedInfo);
    res.status(200).send();
  });

  this.start = function() {
    app.listen(config.port);
  }

  return this;
}

var branchPattern = /^refs\/heads\/(\w+)$/;
var parseBranch = function (payload) {
  return payload.branch || branchPattern.exec(payload.ref)[1];
}

var repoURLPattern = /^(.*?)\/compare\/.*$/;
var parseURL = function (payload) {
  return repoURLPattern.exec(payload.compare || payload.compare_url)[1];
}

exports = module.exports = server
exports.parseURL = parseURL

// vim: ts=2:sw=2:et
<|endoftext|>


<commit_before>var fs = require('fs');
var URL = require('url');
var path = require('path');
var exec = require('child_process').exec;

var mimes = require('./mime.json');
var $ = require('./helper');
var connect = require('connect');
var libDir = __dirname;
var rootDir = path.join(libDir, '../') + path.sep;
var templateDir = path.join(rootDir, 'template') + path.sep;

var chokidar = require('chokidar');
var md_parser = require('./md_parser');
//session相关
var Cookie = require('cookie');
var parseSignedCookie = connect.utils.parseSignedCookie;
var MemoryStore = connect.middleware.session.MemoryStore;
var Session = connect.middleware.session.Session;
//建立一个memory store的实例
var storeMemory = new MemoryStore({
    reapInterval: 60000 * 10
});
module.exports.start = function(port, pptDir, host, argvObj) {
    port = parseInt(port, 10) || 8080;
    pptDir = (pptDir || path.join(__dirname, '../ppts')) + path.sep;
    try {
        pptDir = fs.realpathSync(pptDir) + path.sep;
    } catch (e) {}

    var app = startApp(port, pptDir, host, argvObj);
    var io = require('socket.io').listen(app, {
        log: false,
        origins: '*:*' //解决同源策略
    });
    //设置session
    io.set('authorization', function(handshakeData, accept) {
        // 通过客户端的cookie字符串来获取其session数据
        var ccc = '';
        if (handshakeData.headers && handshakeData.headers.cookie) {
            ccc = handshakeData.headers.cookie;
        }
        handshakeData.cookie = Cookie.parse(ccc);
        var connect_sid = parseSignedCookie(handshakeData.cookie['connect.sid'], 'wyq');
        if (connect_sid) {
            storeMemory.get(connect_sid, function(error, session) {
                if (error) {
                    // if we cannot grab a session, turn down the connection
                    accept(error.message, false);
                } else {
                    // save the session data and accept the connection
                    handshakeData.session = new Session(handshakeData, session);
                    handshakeData.connect_sid = connect_sid;
                    accept(null, true);
                }
            });
        } else {
            accept('nosession');
        }
    });
    //watcher对应的socket
    var watchSockets = {};
    if (argvObj.watch) {
        //添加watch功能
        var exclude = ["\\/\\.", "node_modules"];

        var watcher = chokidar.watch(pptDir, {
            ignored: new RegExp(exclude.join('|')),
            persistent: true
        });
        watcher.on('change', function() {
            sendChangedNotice();
        });
        io.of('/watcher').on('connection', function(socket) {
            watchSockets[socket.id] = socket;
        });
    }

    //用户双屏通信
    var now = Date.now();

    var sockets = {};
    var userMap = {};

    io.of('/ppt').on('connection', function(socket) {
        //解析cookie中的sid
        var cookie = socket.handshake.headers.cookie;
        cookie = Cookie.parse(cookie || '');
        var uid = cookie['connect.sid'];
        socket.uid = uid;

        sockets[uid] = socket;
        //监听添加map
        socket.on('control.addClient', function(data) {
            var targetUid = socket.targetUid = data.targetUid;
            var otherSocket = sockets[targetUid];
            userMap[targetUid] = uid;
            userMap[uid] = targetUid;

            otherSocket && otherSocket.emit('system', {
                action: 'join',
                joinUid: uid
            });

        });
        //建立消息中转站
        socket.on('transfer.data', function(data) {
            var uid = socket.uid;
            var targetUid = userMap[uid];
            var otherSocket = sockets[targetUid];
            otherSocket && otherSocket.emit('transfer.data', data);
        });
        //将当前分配的uid告知客户端
        socket.emit('UUID', uid);

        socket.on('disconnect', function() {
            var uid = socket.uid;
            var targetUid = userMap[uid];
            var otherSocket = sockets[targetUid];
            otherSocket && otherSocket.emit('system', {
                action: 'leave',
                leaveUid: uid
            });
            delete sockets[uid];
        });
    });

    function sendChangedNotice() {
        if (!Object.keys(watchSockets)) {
            return console.error('[watch-connect]', 'No client connected to socket.io');
        }
        Object.keys(watchSockets).forEach(function(s) {
            if (watchSockets[s] && watchSockets[s].emit) {
                watchSockets[s].emit('file changed');
            }
        });
    }
};

function startApp(port, dir, host, argvObj) {
    host = host || '0.0.0.0';
    var staticDir = path.normalize(path.join(__dirname, '../assets')) + path.sep;
    var nodeModules = path.normalize(path.join(__dirname, '../node_modules')) + path.sep;
    var pptDir = dir;

    var now = Date.now();
    var app = connect(
        connect.cookieParser(),
        connect.session({
            secret: 'wyq',
            store: storeMemory
        }),
        function(req, res) {
            var url = URL.parse(req.url).pathname;
            var dirname = path.dirname(url);
            var realPath, ext;
            if (url === '/') {
                //根目录显示list
                pptlist(res, pptDir, argvObj);
                return;
            } else if (dirname === '/md') {
                //md文件解析
                url = URL.parse(req.url).pathname;
                var basename = path.basename(url);
                try {
                    basename = decodeURIComponent(basename);
                } catch (e) {
                    basename = path.basename(url);
                }
                var queryObj = URL.parse(req.url, true).query;
                realPath = pptDir + basename;
                console.log('markdown', realPath, basename);
                markdown(realPath, basename, res, argvObj, queryObj);
                return;
            } else if (/^\/js\/mathjax/.test(dirname)) {
                //处理mathjax
                realPath = url.replace('/js/', nodeModules);
                ext = path.extname(realPath);
                ext = ext ? ext.slice(1) : 'unknown';

            } else {
                //优先选择pptDir的静态资源
                url = url.indexOf('/') === 0 ? url.substring(1) : url;
                realPath = pptDir + url;
                if (!fs.existsSync(realPath)) {
                    realPath = staticDir + url;
                }
                ext = path.extname(realPath);
                ext = ext ? ext.slice(1) : 'unknown';
            }
            assets(realPath, ext, url, res, argvObj);
        }).listen(port, host);
    return app.on('listening', function() {
        var server = app.address();
        var address = server.address === '0.0.0.0' ? '127.0.0.1' : server.address;
        var url = '  http://' + address + ':' + server.port

        console.log('ppt directory:'.cyan + '           ' + pptDir);
        console.log('assets directory:'.cyan + '        ' + staticDir);
        console.log('nodeppt server started:'.cyan + url.yellow);

        if (process.platform === 'win32') {
            exec('start' + url);
        } else {
            exec('open' + url);
        }
    }).on('error', function(e) {
        if (e.code === 'EADDRINUSE' || e.code === 'EACCES') {
            console.log('ERROR: '.red + 'port ' + port + ' is in use!');
        } else {
            console.log('ERROR: '.red + 'server start ' + host + ':' + port + ' info : ' + e.code);
        }
    });
}

function assets(realPath, ext, url, res, argvObj) {
    //静态资源
    if (fs.existsSync(realPath)) {

        fs.readFile(realPath, 'binary', function(err, file) {
            if (err) {
                res.writeHead(500, {
                    'Powered-By': 'nodePPT',
                    'Content-Type': 'text/plain'
                });
                res.end(err);
            } else {
                res.writeHead(200, {
                    'Powered-By': 'nodePPT',
                    'Content-Type': mimes[ext] || 'text/plain'
                });
                res.write(file, 'binary');
                res.end();
            }

        });

    } else {
        page404(res, url);
    }
}

function page404(res, url) {
    res.writeHead(404, {
        'Powered-By': 'nodePPT',
        'Content-Type': mimes.txt
    });

    res.write('This request URL ' + url + ' was not found on this server.');
    res.end();
}

function markdown(realPath, url, res, argvObj, queryObj) {
    if (fs.existsSync(realPath)) {
        var content = fs.readFileSync(realPath, 'utf-8').toString();
        try {
            var html = md_parser(content, function() {}, argvObj, queryObj);
            //添加socket的html代码
            var extraHtml = '';
            if (argvObj.watch) {
                extraHtml += getWatcherHtml();
            }
            if (extraHtml.trim() !== '') {
                html = html.split('').join(extraHtml);
            }

            res.writeHead(200, {
                'Powered-By': 'nodePPT',
                'Content-Type': mimes.html
            });
            res.write(html);
            res.end();
        } catch (e) {
            res.writeHead(500, {
                'Powered-By': 'nodePPT',
                'Content-Type': 'text/plain'
            });
            res.end(e.toString());
        }

    } else {
        page404(res, url);
    }
}

/**
 * 读取ppt列表
 * @param  {[type]} res [description]
 * @param  {[type]} dir [description]
 * @return {[type]}     [description]
 */

function pptlist(res, dir, argvObj) {
    var staticDir = path.join(__dirname, '../assets') + '/';
    res.writeHead(200, {
        'Powered-By': 'nodePPT',
        'Content-Type': mimes.html
    });

    var curPath = dir;

    var count = 0,
        list = '';
    //遍历html文件
    $.recurse(dir, function(realpath, rootdir, subdir, filename) {
        count++;
        var content = fs.readFileSync(realpath, 'utf-8').toString();
        var title = content.match(/<title>(.*?)<\/title>/) || [];
        if (title[1]) {
            title = title[1];
        } else {
            title = filename;
        }
        var url = filename;
        list += '
' + title + '
';
    }, '', '', function(filename) {
        return /\.htm[l]?/.test(filename);
    });

    //遍历markdown文件
    $.recurse(dir, function(realpath, rootdir, subdir, filename) {
        count++;
        var content = fs.readFileSync(realpath, 'utf-8').toString();
        //第一个是封面
        var cover = content.split('[slide]').shift();

        var title = md_parser.parseCover(cover).title;

        if (!title) {
            title = filename;
        }
        var url = '/md/' + filename;
        list += '
' + title + '';
        list += (argvObj.controller === 'socket' ? '' : '多窗口远程控制') + '
';
    }, '', '', function(filename) {
        return /\.(md|markdown)$/.test(filename);
    });

    var json = require(path.join(__dirname, '../package.json'));

    var data = {
        version: json.version,
        site: json.site,
        date: Date.now(),
        list: list,
        dir: curPath
    };
    //渲染模板
    var html = $.renderFile(templateDir + 'list.ejs', data);
    res.write(html);
    if (count == 0) {
        //路径不存在ppt
        res.write('This directory ' + curPath + ' was not found html on this server.');
        console.log(('This directory ' + curPath + ' was not found html on this server.').red);
    }
    res.end();
}

function getWatcherHtml() {
    var html = ['',
        ''
    ].join('');
    return html;
}
<commit_msg>解决最新版本npm会将依赖扁平化,Mathjax路径时效<commit_after>var fs = require('fs');
var URL = require('url');
var path = require('path');
var exec = require('child_process').exec;

var mimes = require('./mime.json');
var $ = require('./helper');
var connect = require('connect');
var libDir = __dirname;
var rootDir = path.join(libDir, '../') + path.sep;
var templateDir = path.join(rootDir, 'template') + path.sep;

var chokidar = require('chokidar');
var md_parser = require('./md_parser');
//session相关
var Cookie = require('cookie');
var parseSignedCookie = connect.utils.parseSignedCookie;
var MemoryStore = connect.middleware.session.MemoryStore;
var Session = connect.middleware.session.Session;
//建立一个memory store的实例
var storeMemory = new MemoryStore({
    reapInterval: 60000 * 10
});
module.exports.start = function(port, pptDir, host, argvObj) {
    port = parseInt(port, 10) || 8080;
    pptDir = (pptDir || path.join(__dirname, '../ppts')) + path.sep;
    try {
        pptDir = fs.realpathSync(pptDir) + path.sep;
    } catch (e) {}

    var app = startApp(port, pptDir, host, argvObj);
    var io = require('socket.io').listen(app, {
        log: false,
        origins: '*:*' //解决同源策略
    });
    //设置session
    io.set('authorization', function(handshakeData, accept) {
        // 通过客户端的cookie字符串来获取其session数据
        var ccc = '';
        if (handshakeData.headers && handshakeData.headers.cookie) {
            ccc = handshakeData.headers.cookie;
        }
        handshakeData.cookie = Cookie.parse(ccc);
        var connect_sid = parseSignedCookie(handshakeData.cookie['connect.sid'], 'wyq');
        if (connect_sid) {
            storeMemory.get(connect_sid, function(error, session) {
                if (error) {
                    // if we cannot grab a session, turn down the connection
                    accept(error.message, false);
                } else {
                    // save the session data and accept the connection
                    handshakeData.session = new Session(handshakeData, session);
                    handshakeData.connect_sid = connect_sid;
                    accept(null, true);
                }
            });
        } else {
            accept('nosession');
        }
    });
    //watcher对应的socket
    var watchSockets = {};
    if (argvObj.watch) {
        //添加watch功能
        var exclude = ["\\/\\.", "node_modules"];

        var watcher = chokidar.watch(pptDir, {
            ignored: new RegExp(exclude.join('|')),
            persistent: true
        });
        watcher.on('change', function() {
            sendChangedNotice();
        });
        io.of('/watcher').on('connection', function(socket) {
            watchSockets[socket.id] = socket;
        });
    }

    //用户双屏通信
    var now = Date.now();

    var sockets = {};
    var userMap = {};

    io.of('/ppt').on('connection', function(socket) {
        //解析cookie中的sid
        var cookie = socket.handshake.headers.cookie;
        cookie = Cookie.parse(cookie || '');
        var uid = cookie['connect.sid'];
        socket.uid = uid;

        sockets[uid] = socket;
        //监听添加map
        socket.on('control.addClient', function(data) {
            var targetUid = socket.targetUid = data.targetUid;
            var otherSocket = sockets[targetUid];
            userMap[targetUid] = uid;
            userMap[uid] = targetUid;

            otherSocket && otherSocket.emit('system', {
                action: 'join',
                joinUid: uid
            });

        });
        //建立消息中转站
        socket.on('transfer.data', function(data) {
            var uid = socket.uid;
            var targetUid = userMap[uid];
            var otherSocket = sockets[targetUid];
            otherSocket && otherSocket.emit('transfer.data', data);
        });
        //将当前分配的uid告知客户端
        socket.emit('UUID', uid);

        socket.on('disconnect', function() {
            var uid = socket.uid;
            var targetUid = userMap[uid];
            var otherSocket = sockets[targetUid];
            otherSocket && otherSocket.emit('system', {
                action: 'leave',
                leaveUid: uid
            });
            delete sockets[uid];
        });
    });

    function sendChangedNotice() {
        if (!Object.keys(watchSockets)) {
            return console.error('[watch-connect]', 'No client connected to socket.io');
        }
        Object.keys(watchSockets).forEach(function(s) {
            if (watchSockets[s] && watchSockets[s].emit) {
                watchSockets[s].emit('file changed');
            }
        });
    }
};

function startApp(port, dir, host, argvObj) {
    host = host || '0.0.0.0';
    var staticDir = path.normalize(path.join(__dirname, '../assets')) + path.sep;
    var nodeModules = path.normalize(path.join(require.resolve('mathjax/package.json'),'../..')) + path.sep;
    var pptDir = dir;

    var now = Date.now();
    var app = connect(
        connect.cookieParser(),
        connect.session({
            secret: 'wyq',
            store: storeMemory
        }),
        function(req, res) {
            var url = URL.parse(req.url).pathname;
            var dirname = path.dirname(url);
            var realPath, ext;
            if (url === '/') {
                //根目录显示list
                pptlist(res, pptDir, argvObj);
                return;
            } else if (dirname === '/md') {
                //md文件解析
                url = URL.parse(req.url).pathname;
                var basename = path.basename(url);
                try {
                    basename = decodeURIComponent(basename);
                } catch (e) {
                    basename = path.basename(url);
                }
                var queryObj = URL.parse(req.url, true).query;
                realPath = pptDir + basename;
                console.log('markdown', realPath, basename);
                markdown(realPath, basename, res, argvObj, queryObj);
                return;
            } else if (/^\/js\/mathjax/.test(dirname)) {
                //处理mathjax
                realPath = url.replace('/js/', nodeModules);
                ext = path.extname(realPath);
                ext = ext ? ext.slice(1) : 'unknown';

            } else {
                //优先选择pptDir的静态资源
                url = url.indexOf('/') === 0 ? url.substring(1) : url;
                realPath = pptDir + url;
                if (!fs.existsSync(realPath)) {
                    realPath = staticDir + url;
                }
                ext = path.extname(realPath);
                ext = ext ? ext.slice(1) : 'unknown';
            }
            assets(realPath, ext, url, res, argvObj);
        }).listen(port, host);
    return app.on('listening', function() {
        var server = app.address();
        var address = server.address === '0.0.0.0' ? '127.0.0.1' : server.address;
        var url = '  http://' + address + ':' + server.port

        console.log('ppt directory:'.cyan + '           ' + pptDir);
        console.log('assets directory:'.cyan + '        ' + staticDir);
        console.log('nodeppt server started:'.cyan + url.yellow);

        if (process.platform === 'win32') {
            exec('start' + url);
        } else {
            exec('open' + url);
        }
    }).on('error', function(e) {
        if (e.code === 'EADDRINUSE' || e.code === 'EACCES') {
            console.log('ERROR: '.red + 'port ' + port + ' is in use!');
        } else {
            console.log('ERROR: '.red + 'server start ' + host + ':' + port + ' info : ' + e.code);
        }
    });
}

function assets(realPath, ext, url, res, argvObj) {
    //静态资源
    if (fs.existsSync(realPath)) {

        fs.readFile(realPath, 'binary', function(err, file) {
            if (err) {
                res.writeHead(500, {
                    'Powered-By': 'nodePPT',
                    'Content-Type': 'text/plain'
                });
                res.end(err);
            } else {
                res.writeHead(200, {
                    'Powered-By': 'nodePPT',
                    'Content-Type': mimes[ext] || 'text/plain'
                });
                res.write(file, 'binary');
                res.end();
            }

        });

    } else {
        page404(res, url);
    }
}

function page404(res, url) {
    res.writeHead(404, {
        'Powered-By': 'nodePPT',
        'Content-Type': mimes.txt
    });

    res.write('This request URL ' + url + ' was not found on this server.');
    res.end();
}

function markdown(realPath, url, res, argvObj, queryObj) {
    if (fs.existsSync(realPath)) {
        var content = fs.readFileSync(realPath, 'utf-8').toString();
        try {
            var html = md_parser(content, function() {}, argvObj, queryObj);
            //添加socket的html代码
            var extraHtml = '';
            if (argvObj.watch) {
                extraHtml += getWatcherHtml();
            }
            if (extraHtml.trim() !== '') {
                html = html.split('').join(extraHtml);
            }

            res.writeHead(200, {
                'Powered-By': 'nodePPT',
                'Content-Type': mimes.html
            });
            res.write(html);
            res.end();
        } catch (e) {
            res.writeHead(500, {
                'Powered-By': 'nodePPT',
                'Content-Type': 'text/plain'
            });
            res.end(e.toString());
        }

    } else {
        page404(res, url);
    }
}

/**
 * 读取ppt列表
 * @param  {[type]} res [description]
 * @param  {[type]} dir [description]
 * @return {[type]}     [description]
 */

function pptlist(res, dir, argvObj) {
    var staticDir = path.join(__dirname, '../assets') + '/';
    res.writeHead(200, {
        'Powered-By': 'nodePPT',
        'Content-Type': mimes.html
    });

    var curPath = dir;

    var count = 0,
        list = '';
    //遍历html文件
    $.recurse(dir, function(realpath, rootdir, subdir, filename) {
        count++;
        var content = fs.readFileSync(realpath, 'utf-8').toString();
        var title = content.match(/<title>(.*?)<\/title>/) || [];
        if (title[1]) {
            title = title[1];
        } else {
            title = filename;
        }
        var url = filename;
        list += '
' + title + '
';
    }, '', '', function(filename) {
        return /\.htm[l]?/.test(filename);
    });

    //遍历markdown文件
    $.recurse(dir, function(realpath, rootdir, subdir, filename) {
        count++;
        var content = fs.readFileSync(realpath, 'utf-8').toString();
        //第一个是封面
        var cover = content.split('[slide]').shift();

        var title = md_parser.parseCover(cover).title;

        if (!title) {
            title = filename;
        }
        var url = '/md/' + filename;
        list += '
' + title + '';
        list += (argvObj.controller === 'socket' ? '' : '多窗口远程控制') + '
';
    }, '', '', function(filename) {
        return /\.(md|markdown)$/.test(filename);
    });

    var json = require(path.join(__dirname, '../package.json'));

    var data = {
        version: json.version,
        site: json.site,
        date: Date.now(),
        list: list,
        dir: curPath
    };
    //渲染模板
    var html = $.renderFile(templateDir + 'list.ejs', data);
    res.write(html);
    if (count == 0) {
        //路径不存在ppt
        res.write('This directory ' + curPath + ' was not found html on this server.');
        console.log(('This directory ' + curPath + ' was not found html on this server.').red);
    }
    res.end();
}

function getWatcherHtml() {
    var html = ['',
        ''
    ].join('');
    return html;
}
<|endoftext|>


<commit_before>var http     = require('http')
  , fs       = require('fs')
  , path     = require('path')
  , url      = require('url')
  , check    = require('./check')
  , parser   = require('./parser')
  , template = require('./template')
  , countdown = require('./countdown');

var countdownExit = countdown(function() {
  process.exit(1);
}, 4e3);

var response = function(res, code,type, content) {
  var map = {
      html: 'text/html;charset=utf-8'
    , jpeg: 'image/jpeg'
    , text: 'text/plain'
  }
  res.writeHead(code, {'Content-Type': map[type]});
  res.end(content);
};

var handler = function(req, res) {
  var filename = decodeURI(url.parse(req.url).path.substr(1))
    , basename = path.basename(filename);

  if (req.url == '/get-markdown-preview-response-data') {
    countdownExit.restart();
    response(res, 200, 'jpeg', '');
    return;
  }

  check(filename, function(ok) {
    if (ok) {
      fs.readFile(filename, 'utf-8', function(err, input) {
        response(res, 200, 'html',
          template.render({
              title: basename
            , body: parser.parse(input)
          })
        );
      });
    } else {
      response(res, 404, 'text',
        'Cannot open file ' + basename
      );
    }
  });
}

exports.startAt = (function() {
  var server = http.createServer(handler);
  return function(port, fn) {
    countdownExit.start();
    http
      .get('http://localhost:' + port, fn)
      .on('error', function(){
        server.listen(port);
        fn();
      });
  };
}());

<commit_msg>format<commit_after>var http      = require('http')
  , fs        = require('fs')
  , path      = require('path')
  , url       = require('url')

var check     = require('./check')
  , parser    = require('./parser')
  , template  = require('./template')
  , countdown = require('./countdown');

var exitCountdown = countdown(function() {
  process.exit(1);
}, 4e3);

var response = function(res, code,type, content) {
  var map = {
      html: 'text/html;charset=utf-8'
    , jpeg: 'image/jpeg'
    , text: 'text/plain'
  }
  res.writeHead(code, {'Content-Type': map[type]});
  res.end(content);
};

var handler = function(req, res) {
  var filename = decodeURI(url.parse(req.url).path.substr(1))
    , basename = path.basename(filename);

  if (req.url == '/get-markdown-preview-response-data') {
    exitCountdown.restart();
    response(res, 200, 'jpeg', '');
    return;
  }

  check(filename, function(ok) {
    if (ok) {
      fs.readFile(filename, 'utf-8', function(err, input) {
        response(res, 200, 'html', template.render({
            title: basename
          , body: parser.parse(input)
        }));
      });
    } else {
      response(res, 404, 'text', 'Cannot open file ' + basename);
    }
  });
}

exports.startAt = (function() {
  var server = http.createServer(handler);
  return function(port, fn) {
    exitCountdown.start();
    http
      .get('http://localhost:' + port, fn)
      .on('error', function(){
        server.listen(port);
        fn();
      });
  };
}());

<|endoftext|>


<commit_before> (function() {
    'use strict';

    if (!global.cloudcmd)
        return console.log(
             '# server.js'                                      + '\n'  +
             '# -----------'                                    + '\n'  +
             '# Module is part of Cloud Commander,'             + '\n'  +
             '# easy to use web server.'                        + '\n'  +
             '# http://cloudcmd.io'                             + '\n');

    var main                = global.cloudcmd.main,

        DIR                 = main.DIR,
        LIBDIR              = main.LIBDIR,
        SRVDIR              = main.SRVDIR,

        URL                 = main.url,
        Path                = main.path,
        Querystring         = main.querystring,

        Minify              = main.minify,
        CloudFunc           = main.cloudfunc,
        AppCache            = main.appcache,
        Socket              = main.socket,
        Console             = main.console,
        Terminal            = main.terminal,

        zlib                = main.zlib,
        http                = main.http,
        https               = main.https,
        Util                = main.util,
        express             = main.express,
        expressApp,
        files               = main.files,

        Server, Rest, Route;

    /* базовая инициализация  */
    function init(pAppCachProcessing) {
        var config = main.config;

        /* создаём файл app cache */
        if (config.appcache  && AppCache && config.server )
            Util.exec( pAppCachProcessing );
    }

    /**
     * start server function
     * @param pConfig
     * @param pProcessing {index, appcache, rest}
     */
    function start(options) {
        var redirectServer, port, ip, ssl, sslPort,
            HTTP        = 'http://',
            HTTPS       = 'https://',
            config      = main.config;

        if (!options)
            options = {};

        Rest    =   options.rest;
        Route   =   options.route;

        init(options.appcache);

        port    =   process.env.PORT            ||  /* c9           */
                    process.env.app_port        ||  /* nodester     */
                    process.env.VCAP_APP_PORT   ||  /* cloudfoundry */
                    config.port,
        ip      =   process.env.IP              ||  /* c9           */
                    config.ip                   ||
                    (main.WIN32 ? '127.0.0.1' : '0.0.0.0'),

        ssl     = options.ssl,
        sslPort = config.sslPort;

        /* server mode or testing mode */
        if (!config.server)
            Util.log('Cloud Commander testing mode');
        else
            if (!ssl)
                createServer(port, ip, HTTP);
            else {
                createRedirect(port, ip, HTTPS, sslPort);

                createServer(sslPort, ip, HTTP, ssl, function() {
                    Util.log('Could not use https port: ' + sslPort);
                    redirectServer.close();
                    Util.log('* Redirection http -> https removed');
                    createServer(port, ip);
                });
            }
    }

    function createRedirect(port, ip, protocol, sslPort) {
        var server = function(req, res) {
            var url,
                host        = req.headers.host,
                parsed      = URL.parse(host),
                hostName    = parsed.protocol;

            url             = protocol + hostName + sslPort + req.url;

            main.redirect({
                response: res,
                url: url
            });
        };

        Util.log('* Redirection http -> https is setted up');
        logServer(port, ip, protocol);

        http.createServer(server)
            .listen(port, ip);
    }

    function createServer(port, ip, protocol, ssl, callback) {
        var server, app;

        expressApp  = express.getApp([
            Rest,
            Route,
            join,
            controller
        ]);

        app         = expressApp || respond;

        if (ssl)
            server  =  https.createServer(ssl, app);
        else
            server  = http.createServer(app);

        server.on('error', function(error) {
            Util.log(error);
            Util.exec(callback, error);
        });

        server.listen(port, ip);

        logServer(port, ip, protocol);
        addSockets(server);
    }

    function logServer(port, ip, http) {
        Util.log('* Server running at ' + http + ip + ':' + port);
    }

    function addSockets(server) {
        var listen, msg,
            config  = main.config,
            status  = 'off';

        if (config.socket && Socket) {
            listen = Socket.listen(server);

            if (listen) {
                status = 'on';
                Console.init();
                Terminal.init();
            }
        }

        msg     = CloudFunc.formatMsg('sockets', '', status);

        Util.log(msg);
    }

    function respond(req, res) {
        var i, n, func,
            funcs = ([
                Rest,
                Route,
                join,
                controller
            ]);

        n = funcs.length;
        for (i = 0; i < n; i++) {
            func        = funcs[i];
            funcs[i]    = func.bind(null, req, res);
        }

        Util.loadOnLoad(funcs);
    }

    /**
     * Главная функция, через которую проихсодит
     * взаимодействие, обмен данными с клиентом
     * @param req - запрос клиента (Request)
     * @param res - ответ сервера (Response)
     */
    function controller(req, res) {
        var check, result,
            config      = main.config,
            isMin       = config.minify,
            parsedUrl   = URL.parse(req.url),
            query       = parsedUrl.search || '',
            path        = main.getPathName(req),
            name        = path;

        if (!expressApp)
            Util.log(req.method + ' ' + path + query);

        /* watching is file changed */
        if (config.appcache)
            AppCache.watch(name);

        name    = Path.join(DIR, name);
        check   = checkExtension(name);
        result  = isMin && check;

        Util.ifExec(!result,
            function(params) {
                var sendName = params && params.name || name;

                main.sendFile({
                    name        : sendName,
                    cache       : config.cache,
                    gzip        : true,
                    request     : req,
                    response    : res
                });
        }, function(callback) {
            Minify.optimize(name, {
                callback    : callback,
                returnName  : true
            });
        });
    }

    function minify(name, callback) {
        Minify.optimize(name, {
            callback    : callback
        });
    }

    function join(request, response, callback) {
        var names, i, n, name, minName, stream, check,
            funcs       = [],
            config      = main.config,
            dir         = DIR,
            gzip        = zlib.createGzip(),
            isGzip      = main.isGZIP(request),
            path        = main.getPathName(request),

            isJoin      = CloudFunc.isJoinURL(path),
            readPipe    = function() {
                main.mainSetHeader({
                    name        : names[0],
                    cache       : config.cache,
                    gzip        : isGzip,
                    request     : request,
                    response    : response
                });

                if (!isGzip)
                    stream = response;
                else
                    stream = gzip;

                files.readPipe({
                    names       : names,
                    dir         : dir,
                    write       : stream,
                    callback    : function(error) {
                        var errorStr;

                        if (error)
                            if (!response.headersSent)
                                main.sendError({
                                    request     : request,
                                    response    : response,
                                    name        : path
                                }, error);
                            else {
                                Util.log(error);
                                errorStr = error.toString();
                                stream.end(errorStr);
                            }
                    }
                });

                /*
                 * pipe should be setted up after
                 * readPipe called with stream param
                 */
                if (isGzip)
                    gzip.pipe(response);
            };

        if (isJoin) {
            names   = CloudFunc.getJoinArray(path);
            n       = names.length;

            if (!config.minify)
                readPipe();
            else {
                for (i = 0; i < n; i++) {
                    name        = Path.join(DIR, names[i]);
                    check       = checkExtension(name);

                    if (check) {
                        minName = Minify.getName(name);

                        if (name !== minName) {
                            names[i] = minName;
                            dir = '';
                        }
                    }

                    funcs.push(minify.bind(null, name));
                }
                Util.asyncCall(funcs, readPipe);
            }
        } else
            Util.exec(callback);

        return isJoin;
    }

    function checkExtension(name) {
        var ret;

        ret = Util.checkExtension(name, ['.js', '.css', '.html']);

        return ret;
    }

    exports.start           = start;

})();
<commit_msg>refactor(server) join<commit_after> (function() {
    'use strict';

    if (!global.cloudcmd)
        return console.log(
             '# server.js'                                      + '\n'  +
             '# -----------'                                    + '\n'  +
             '# Module is part of Cloud Commander,'             + '\n'  +
             '# easy to use web server.'                        + '\n'  +
             '# http://cloudcmd.io'                             + '\n');

    var main                = global.cloudcmd.main,

        DIR                 = main.DIR,
        LIBDIR              = main.LIBDIR,
        SRVDIR              = main.SRVDIR,

        URL                 = main.url,
        Path                = main.path,
        Querystring         = main.querystring,

        Minify              = main.minify,
        CloudFunc           = main.cloudfunc,
        AppCache            = main.appcache,
        Socket              = main.socket,
        Console             = main.console,
        Terminal            = main.terminal,

        zlib                = main.zlib,
        http                = main.http,
        https               = main.https,
        Util                = main.util,
        express             = main.express,
        expressApp,
        files               = main.files,

        Server, Rest, Route;

    /* базовая инициализация  */
    function init(pAppCachProcessing) {
        var config = main.config;

        /* создаём файл app cache */
        if (config.appcache  && AppCache && config.server )
            Util.exec( pAppCachProcessing );
    }

    /**
     * start server function
     * @param pConfig
     * @param pProcessing {index, appcache, rest}
     */
    function start(options) {
        var redirectServer, port, ip, ssl, sslPort,
            HTTP        = 'http://',
            HTTPS       = 'https://',
            config      = main.config;

        if (!options)
            options = {};

        Rest    =   options.rest;
        Route   =   options.route;

        init(options.appcache);

        port    =   process.env.PORT            ||  /* c9           */
                    process.env.app_port        ||  /* nodester     */
                    process.env.VCAP_APP_PORT   ||  /* cloudfoundry */
                    config.port,
        ip      =   process.env.IP              ||  /* c9           */
                    config.ip                   ||
                    (main.WIN32 ? '127.0.0.1' : '0.0.0.0'),

        ssl     = options.ssl,
        sslPort = config.sslPort;

        /* server mode or testing mode */
        if (!config.server)
            Util.log('Cloud Commander testing mode');
        else
            if (!ssl)
                createServer(port, ip, HTTP);
            else {
                createRedirect(port, ip, HTTPS, sslPort);

                createServer(sslPort, ip, HTTP, ssl, function() {
                    Util.log('Could not use https port: ' + sslPort);
                    redirectServer.close();
                    Util.log('* Redirection http -> https removed');
                    createServer(port, ip);
                });
            }
    }

    function createRedirect(port, ip, protocol, sslPort) {
        var server = function(req, res) {
            var url,
                host        = req.headers.host,
                parsed      = URL.parse(host),
                hostName    = parsed.protocol;

            url             = protocol + hostName + sslPort + req.url;

            main.redirect({
                response: res,
                url: url
            });
        };

        Util.log('* Redirection http -> https is setted up');
        logServer(port, ip, protocol);

        http.createServer(server)
            .listen(port, ip);
    }

    function createServer(port, ip, protocol, ssl, callback) {
        var server, app;

        expressApp  = express.getApp([
            Rest,
            Route,
            join,
            controller
        ]);

        app         = expressApp || respond;

        if (ssl)
            server  =  https.createServer(ssl, app);
        else
            server  = http.createServer(app);

        server.on('error', function(error) {
            Util.log(error);
            Util.exec(callback, error);
        });

        server.listen(port, ip);

        logServer(port, ip, protocol);
        addSockets(server);
    }

    function logServer(port, ip, http) {
        Util.log('* Server running at ' + http + ip + ':' + port);
    }

    function addSockets(server) {
        var listen, msg,
            config  = main.config,
            status  = 'off';

        if (config.socket && Socket) {
            listen = Socket.listen(server);

            if (listen) {
                status = 'on';
                Console.init();
                Terminal.init();
            }
        }

        msg     = CloudFunc.formatMsg('sockets', '', status);

        Util.log(msg);
    }

    function respond(req, res) {
        var i, n, func,
            funcs = ([
                Rest,
                Route,
                join,
                controller
            ]);

        n = funcs.length;
        for (i = 0; i < n; i++) {
            func        = funcs[i];
            funcs[i]    = func.bind(null, req, res);
        }

        Util.loadOnLoad(funcs);
    }

    /**
     * Главная функция, через которую проихсодит
     * взаимодействие, обмен данными с клиентом
     * @param req - запрос клиента (Request)
     * @param res - ответ сервера (Response)
     */
    function controller(req, res) {
        var check, result,
            config      = main.config,
            isMin       = config.minify,
            parsedUrl   = URL.parse(req.url),
            query       = parsedUrl.search || '',
            path        = main.getPathName(req),
            name        = path;

        if (!expressApp)
            Util.log(req.method + ' ' + path + query);

        /* watching is file changed */
        if (config.appcache)
            AppCache.watch(name);

        name    = Path.join(DIR, name);
        check   = checkExtension(name);
        result  = isMin && check;

        Util.ifExec(!result,
            function(params) {
                var sendName = params && params.name || name;

                main.sendFile({
                    name        : sendName,
                    cache       : config.cache,
                    gzip        : true,
                    request     : req,
                    response    : res
                });
        }, function(callback) {
            Minify.optimize(name, {
                callback    : callback,
                returnName  : true
            });
        });
    }

    function minify(name, callback) {
        Minify.optimize(name, {
            callback    : callback
        });
    }

    function join(request, response, callback) {
        var names, i, n, name, minName, stream, check,
            funcs       = [],
            config      = main.config,
            dir         = DIR,
            gzip        = zlib.createGzip(),
            isGzip      = main.isGZIP(request),
            path        = main.getPathName(request),

            isJoin      = CloudFunc.isJoinURL(path),
            readPipe    = function() {
                main.mainSetHeader({
                    name        : names[0],
                    cache       : config.cache,
                    gzip        : isGzip,
                    request     : request,
                    response    : response
                });

                if (!isGzip)
                    stream = response;
                else
                    stream = gzip;

                files.readPipe({
                    names       : names,
                    dir         : dir,
                    write       : stream,
                    callback    : function(error) {
                        var errorStr;

                        if (error)
                            if (!response.headersSent)
                                main.sendError({
                                    request     : request,
                                    response    : response,
                                    name        : path
                                }, error);
                            else {
                                Util.log(error);
                                errorStr = error.toString();
                                stream.end(errorStr);
                            }
                    }
                });

                /*
                 * pipe should be setted up after
                 * readPipe called with stream param
                 */
                if (isGzip)
                    gzip.pipe(response);
            };

        if (!isJoin)
            Util.exec(callback);
        else {
            names   = CloudFunc.getJoinArray(path);
            n       = names.length;

            if (!config.minify)
                readPipe();
            else {
                for (i = 0; i < n; i++) {
                    name        = Path.join(DIR, names[i]);
                    check       = checkExtension(name);

                    if (check) {
                        minName = Minify.getName(name);

                        if (name !== minName) {
                            names[i] = minName;
                            dir = '';
                        }
                    }

                    funcs.push(minify.bind(null, name));
                }

                Util.asyncCall(funcs, readPipe);
            }
        }

        return isJoin;
    }

    function checkExtension(name) {
        var ret;

        ret = Util.checkExtension(name, ['.js', '.css', '.html']);

        return ret;
    }

    exports.start           = start;

})();
<|endoftext|>


<commit_before>
/**
 * Module dependencies.
 */

var qs = require('querystring')
  , parse = require('url').parse
  , readFileSync = require('fs').readFileSync
  , crypto = require('crypto')
  , base64id = require('base64id')
  , transports = require('./transports')
  , EventEmitter = require('events').EventEmitter
  , Socket = require('./socket')
  , WebSocketServer = require('ws').Server
  , debug = require('debug')('engine');

/**
 * Module exports.
 */

module.exports = Server;

/**
 * Server constructor.
 *
 * @param {Object} options
 * @api public
 */

function Server(opts){
  if (!(this instanceof Server)) {
    return new Server(opts);
  }

  this.clients = {};
  this.clientsCount = 0;

  opts = opts || {};
  this.pingTimeout = opts.pingTimeout || 60000;
  this.pingInterval = opts.pingInterval || 25000;
  this.upgradeTimeout = opts.upgradeTimeout || 10000;
  this.maxHttpBufferSize = opts.maxHttpBufferSize || 10E7;
  this.transports = opts.transports || Object.keys(transports);
  this.allowUpgrades = false !== opts.allowUpgrades;
  this.allowRequest = opts.allowRequest;
  this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;
  this.cookiePath = false !== opts.cookiePath ? (opts.cookiePath || false) : false;
  this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false;
  this.httpCompression = false !== opts.httpCompression ? (opts.httpCompression || {}) : false;

  var self = this;

  // turn off per message deflate for node 0.8
  // due to it not supporting DeflateRaw#close
  // and thus not working with ws 0.8.x
  if (/^v0\.8\./.test(process.version)) {
    debug('perMessageDeflate not supported by node 0.8');
    this.perMessageDeflate = false;
  }

  // initialize compression options
  ['perMessageDeflate', 'httpCompression'].forEach(function(type) {
    var compression = self[type];
    if (true === compression) self[type] = compression = {};
    if (compression && null == compression.threshold) {
      compression.threshold = 1024;
    }
  });

  // initialize websocket server
  if (~this.transports.indexOf('websocket')) {
    this.ws = new WebSocketServer({
      noServer: true,
      clientTracking: false,
      perMessageDeflate: this.perMessageDeflate
    });
  }
}

/**
 * Protocol errors mappings.
 */

Server.errors = {
  UNKNOWN_TRANSPORT: 0,
  UNKNOWN_SID: 1,
  BAD_HANDSHAKE_METHOD: 2,
  BAD_REQUEST: 3
};

Server.errorMessages = {
  0: 'Transport unknown',
  1: 'Session ID unknown',
  2: 'Bad handshake method',
  3: 'Bad request'
};

/**
 * Inherits from EventEmitter.
 */

Server.prototype.__proto__ = EventEmitter.prototype;

/**
 * Hash of open clients.
 *
 * @api public
 */

Server.prototype.clients;

/**
 * Returns a list of available transports for upgrade given a certain transport.
 *
 * @return {Array}
 * @api public
 */

Server.prototype.upgrades = function(transport){
  if (!this.allowUpgrades) return [];
  return transports[transport].upgradesTo || [];
};

/**
 * Verifies a request.
 *
 * @param {http.ServerRequest}
 * @return {Boolean} whether the request is valid
 * @api private
 */

Server.prototype.verify = function(req, upgrade, fn){
  // transport check
  var transport = req._query.transport;
  if (!~this.transports.indexOf(transport)) {
    debug('unknown transport "%s"', transport);
    return fn(Server.errors.UNKNOWN_TRANSPORT, false);
  }

  // sid check
  var sid = req._query.sid;
  if (sid) {
    if (!this.clients.hasOwnProperty(sid))
      return fn(Server.errors.UNKNOWN_SID, false);
    if (!upgrade && this.clients[sid].transport.name !== transport) {
      debug('bad request: unexpected transport without upgrade');
      return fn(Server.errors.BAD_REQUEST, false);
    }
  } else {
    // handshake is GET only
    if ('GET' != req.method) return fn(Server.errors.BAD_HANDSHAKE_METHOD, false);
    if (!this.allowRequest) return fn(null, true);
    return this.allowRequest(req, fn);
  }

  fn(null, true);
};

/**
 * Prepares a request by processing the query string.
 *
 * @api private
 */

Server.prototype.prepare = function(req){
  // try to leverage pre-existing `req._query` (e.g: from connect)
  if (!req._query) {
    req._query = ~req.url.indexOf('?') ? qs.parse(parse(req.url).query) : {};
  }
};

/**
 * Closes all clients.
 *
 * @api public
 */

Server.prototype.close = function(){
  debug('closing all open clients');
  for (var i in this.clients) {
    if (this.clients.hasOwnProperty(i)) {
      this.clients[i].close();
    }
  }
  return this;
};

/**
 * Handles an Engine.IO HTTP request.
 *
 * @param {http.ServerRequest} request
 * @param {http.ServerResponse|http.OutgoingMessage} response
 * @api public
 */

Server.prototype.handleRequest = function(req, res){
  debug('handling "%s" http request "%s"', req.method, req.url);
  this.prepare(req);
  req.res = res;

  var self = this;
  this.verify(req, false, function(err, success) {
    if (!success) {
      sendErrorMessage(req, res, err);
      return;
    }

    if (req._query.sid) {
      debug('setting new request for existing client');
      self.clients[req._query.sid].transport.onRequest(req);
    } else {
      self.handshake(req._query.transport, req);
    }
  });
};

/**
 * Sends an Engine.IO Error Message
 *
 * @param {http.ServerResponse} response
 * @param {code} error code
 * @api private
 */

 function sendErrorMessage(req, res, code) {
    var headers = { 'Content-Type': 'application/json' };

    if (req.headers.origin) {
      headers['Access-Control-Allow-Credentials'] = 'true';
      headers['Access-Control-Allow-Origin'] = req.headers.origin;
    } else {
      headers['Access-Control-Allow-Origin'] = '*';
    }
    res.writeHead(400, headers);
    res.end(JSON.stringify({
      code: code,
      message: Server.errorMessages[code]
    }));
 }

/**
 * generate a socket id.
 * Overwrite this method to generate your custom socket id
 *
 * @param {Object} request object
 * @api public
 */

Server.prototype.generateId = function(req){
  return base64id.generateId();
};

/**
 * Handshakes a new client.
 *
 * @param {String} transport name
 * @param {Object} request object
 * @api private
 */

Server.prototype.handshake = function(transportName, req){
  var id = this.generateId(req);

  debug('handshaking client "%s"', id);

  try {
    var transport = new transports[transportName](req);
    if ('polling' == transportName) {
      transport.maxHttpBufferSize = this.maxHttpBufferSize;
      transport.httpCompression = this.httpCompression;
    } else if ('websocket' == transportName) {
      transport.perMessageDeflate = this.perMessageDeflate;
    }

    if (req._query && req._query.b64) {
      transport.supportsBinary = false;
    } else {
      transport.supportsBinary = true;
    }
  }
  catch (e) {
    sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
    return;
  }
  var socket = new Socket(id, this, transport, req);
  var self = this;

  if (false !== this.cookie) {
    transport.on('headers', function(headers){
      var cookie = self.cookie + '=' + id;
      if(false !== self.cookiePath) {
        cookie += '; path=' + self.cookiePath;
      }
      headers['Set-Cookie'] = cookie;
    });
  }

  transport.onRequest(req);

  this.clients[id] = socket;
  this.clientsCount++;

  socket.once('close', function(){
    delete self.clients[id];
    self.clientsCount--;
  });

  this.emit('connection', socket);
};

/**
 * Handles an Engine.IO HTTP Upgrade.
 *
 * @api public
 */

Server.prototype.handleUpgrade = function(req, socket, upgradeHead){
  this.prepare(req);

  var self = this;
  this.verify(req, true, function(err, success) {
    if (!success) {
      socket.end();
      return;
    }

    var head = new Buffer(upgradeHead.length);
    upgradeHead.copy(head);
    upgradeHead = null;

    // delegate to ws
    self.ws.handleUpgrade(req, socket, head, function(conn){
      self.onWebSocket(req, conn);
    });
  });
};

/**
 * Called upon a ws.io connection.
 *
 * @param {ws.Socket} websocket
 * @api private
 */

Server.prototype.onWebSocket = function(req, socket){
  if (!transports[req._query.transport].prototype.handlesUpgrades) {
    debug('transport doesnt handle upgraded requests');
    socket.close();
    return;
  }

  // get client id
  var id = req._query.sid;

  // keep a reference to the ws.Socket
  req.websocket = socket;

  if (id) {
    var client = this.clients[id];
    if (!client) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (client.upgrading) {
      debug('transport has already been trying to upgrade');
      socket.close();
    } else if (client.upgraded) {
      debug('transport had already been upgraded');
      socket.close();
    } else {
      debug('upgrading existing transport');
      var transport = new transports[req._query.transport](req);
      if (req._query && req._query.b64) {
        transport.supportsBinary = false;
      } else {
        transport.supportsBinary = true;
      }
      transport.perMessageDeflate = this.perMessageDeflate;
      client.maybeUpgrade(transport);
    }
  } else {
    this.handshake(req._query.transport, req);
  }
};

/**
 * Captures upgrade requests for a http.Server.
 *
 * @param {http.Server} server
 * @param {Object} options
 * @api public
 */

Server.prototype.attach = function(server, options){
  var self = this;
  var options = options || {};
  var path = (options.path || '/engine.io').replace(/\/$/, '');

  var destroyUpgrade = (options.destroyUpgrade !== undefined) ? options.destroyUpgrade : true;
  var destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;

  // normalize path
  path += '/';

  function check (req) {
    return path == req.url.substr(0, path.length);
  }

  // cache and clean up listeners
  var listeners = server.listeners('request').slice(0);
  server.removeAllListeners('request');
  server.on('close', self.close.bind(self));

  // add request handler
  server.on('request', function(req, res){
    if (check(req)) {
      debug('intercepting request for path "%s"', path);
      self.handleRequest(req, res);
    } else {
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].call(server, req, res);
      }
    }
  });

  if(~self.transports.indexOf('websocket')) {
    server.on('upgrade', function (req, socket, head) {
      if (check(req)) {
        self.handleUpgrade(req, socket, head);
      } else if (false !== options.destroyUpgrade) {
        // default node behavior is to disconnect when no handlers
        // but by adding a handler, we prevent that
        // and if no eio thing handles the upgrade
        // then the socket needs to die!
        setTimeout(function() {
           if (socket.writable && socket.bytesWritten <= 0) {
             return socket.end();
           }
        }, options.destroyUpgradeTimeout);
      }
    });
  }
};
<commit_msg>server: catch websocket errors before upgrade<commit_after>
/**
 * Module dependencies.
 */

var qs = require('querystring')
  , parse = require('url').parse
  , readFileSync = require('fs').readFileSync
  , crypto = require('crypto')
  , base64id = require('base64id')
  , transports = require('./transports')
  , EventEmitter = require('events').EventEmitter
  , Socket = require('./socket')
  , WebSocketServer = require('ws').Server
  , debug = require('debug')('engine');

/**
 * Module exports.
 */

module.exports = Server;

/**
 * Server constructor.
 *
 * @param {Object} options
 * @api public
 */

function Server(opts){
  if (!(this instanceof Server)) {
    return new Server(opts);
  }

  this.clients = {};
  this.clientsCount = 0;

  opts = opts || {};
  this.pingTimeout = opts.pingTimeout || 60000;
  this.pingInterval = opts.pingInterval || 25000;
  this.upgradeTimeout = opts.upgradeTimeout || 10000;
  this.maxHttpBufferSize = opts.maxHttpBufferSize || 10E7;
  this.transports = opts.transports || Object.keys(transports);
  this.allowUpgrades = false !== opts.allowUpgrades;
  this.allowRequest = opts.allowRequest;
  this.cookie = false !== opts.cookie ? (opts.cookie || 'io') : false;
  this.cookiePath = false !== opts.cookiePath ? (opts.cookiePath || false) : false;
  this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false;
  this.httpCompression = false !== opts.httpCompression ? (opts.httpCompression || {}) : false;

  var self = this;

  // turn off per message deflate for node 0.8
  // due to it not supporting DeflateRaw#close
  // and thus not working with ws 0.8.x
  if (/^v0\.8\./.test(process.version)) {
    debug('perMessageDeflate not supported by node 0.8');
    this.perMessageDeflate = false;
  }

  // initialize compression options
  ['perMessageDeflate', 'httpCompression'].forEach(function(type) {
    var compression = self[type];
    if (true === compression) self[type] = compression = {};
    if (compression && null == compression.threshold) {
      compression.threshold = 1024;
    }
  });

  // initialize websocket server
  if (~this.transports.indexOf('websocket')) {
    this.ws = new WebSocketServer({
      noServer: true,
      clientTracking: false,
      perMessageDeflate: this.perMessageDeflate
    });
  }
}

/**
 * Protocol errors mappings.
 */

Server.errors = {
  UNKNOWN_TRANSPORT: 0,
  UNKNOWN_SID: 1,
  BAD_HANDSHAKE_METHOD: 2,
  BAD_REQUEST: 3
};

Server.errorMessages = {
  0: 'Transport unknown',
  1: 'Session ID unknown',
  2: 'Bad handshake method',
  3: 'Bad request'
};

/**
 * Inherits from EventEmitter.
 */

Server.prototype.__proto__ = EventEmitter.prototype;

/**
 * Hash of open clients.
 *
 * @api public
 */

Server.prototype.clients;

/**
 * Returns a list of available transports for upgrade given a certain transport.
 *
 * @return {Array}
 * @api public
 */

Server.prototype.upgrades = function(transport){
  if (!this.allowUpgrades) return [];
  return transports[transport].upgradesTo || [];
};

/**
 * Verifies a request.
 *
 * @param {http.ServerRequest}
 * @return {Boolean} whether the request is valid
 * @api private
 */

Server.prototype.verify = function(req, upgrade, fn){
  // transport check
  var transport = req._query.transport;
  if (!~this.transports.indexOf(transport)) {
    debug('unknown transport "%s"', transport);
    return fn(Server.errors.UNKNOWN_TRANSPORT, false);
  }

  // sid check
  var sid = req._query.sid;
  if (sid) {
    if (!this.clients.hasOwnProperty(sid))
      return fn(Server.errors.UNKNOWN_SID, false);
    if (!upgrade && this.clients[sid].transport.name !== transport) {
      debug('bad request: unexpected transport without upgrade');
      return fn(Server.errors.BAD_REQUEST, false);
    }
  } else {
    // handshake is GET only
    if ('GET' != req.method) return fn(Server.errors.BAD_HANDSHAKE_METHOD, false);
    if (!this.allowRequest) return fn(null, true);
    return this.allowRequest(req, fn);
  }

  fn(null, true);
};

/**
 * Prepares a request by processing the query string.
 *
 * @api private
 */

Server.prototype.prepare = function(req){
  // try to leverage pre-existing `req._query` (e.g: from connect)
  if (!req._query) {
    req._query = ~req.url.indexOf('?') ? qs.parse(parse(req.url).query) : {};
  }
};

/**
 * Closes all clients.
 *
 * @api public
 */

Server.prototype.close = function(){
  debug('closing all open clients');
  for (var i in this.clients) {
    if (this.clients.hasOwnProperty(i)) {
      this.clients[i].close();
    }
  }
  return this;
};

/**
 * Handles an Engine.IO HTTP request.
 *
 * @param {http.ServerRequest} request
 * @param {http.ServerResponse|http.OutgoingMessage} response
 * @api public
 */

Server.prototype.handleRequest = function(req, res){
  debug('handling "%s" http request "%s"', req.method, req.url);
  this.prepare(req);
  req.res = res;

  var self = this;
  this.verify(req, false, function(err, success) {
    if (!success) {
      sendErrorMessage(req, res, err);
      return;
    }

    if (req._query.sid) {
      debug('setting new request for existing client');
      self.clients[req._query.sid].transport.onRequest(req);
    } else {
      self.handshake(req._query.transport, req);
    }
  });
};

/**
 * Sends an Engine.IO Error Message
 *
 * @param {http.ServerResponse} response
 * @param {code} error code
 * @api private
 */

 function sendErrorMessage(req, res, code) {
    var headers = { 'Content-Type': 'application/json' };

    if (req.headers.origin) {
      headers['Access-Control-Allow-Credentials'] = 'true';
      headers['Access-Control-Allow-Origin'] = req.headers.origin;
    } else {
      headers['Access-Control-Allow-Origin'] = '*';
    }
    res.writeHead(400, headers);
    res.end(JSON.stringify({
      code: code,
      message: Server.errorMessages[code]
    }));
 }

/**
 * generate a socket id.
 * Overwrite this method to generate your custom socket id
 *
 * @param {Object} request object
 * @api public
 */

Server.prototype.generateId = function(req){
  return base64id.generateId();
};

/**
 * Handshakes a new client.
 *
 * @param {String} transport name
 * @param {Object} request object
 * @api private
 */

Server.prototype.handshake = function(transportName, req){
  var id = this.generateId(req);

  debug('handshaking client "%s"', id);

  try {
    var transport = new transports[transportName](req);
    if ('polling' == transportName) {
      transport.maxHttpBufferSize = this.maxHttpBufferSize;
      transport.httpCompression = this.httpCompression;
    } else if ('websocket' == transportName) {
      transport.perMessageDeflate = this.perMessageDeflate;
    }

    if (req._query && req._query.b64) {
      transport.supportsBinary = false;
    } else {
      transport.supportsBinary = true;
    }
  }
  catch (e) {
    sendErrorMessage(req, req.res, Server.errors.BAD_REQUEST);
    return;
  }
  var socket = new Socket(id, this, transport, req);
  var self = this;

  if (false !== this.cookie) {
    transport.on('headers', function(headers){
      var cookie = self.cookie + '=' + id;
      if(false !== self.cookiePath) {
        cookie += '; path=' + self.cookiePath;
      }
      headers['Set-Cookie'] = cookie;
    });
  }

  transport.onRequest(req);

  this.clients[id] = socket;
  this.clientsCount++;

  socket.once('close', function(){
    delete self.clients[id];
    self.clientsCount--;
  });

  this.emit('connection', socket);
};

/**
 * Handles an Engine.IO HTTP Upgrade.
 *
 * @api public
 */

Server.prototype.handleUpgrade = function(req, socket, upgradeHead){
  this.prepare(req);

  var self = this;
  this.verify(req, true, function(err, success) {
    if (!success) {
      socket.end();
      return;
    }

    var head = new Buffer(upgradeHead.length);
    upgradeHead.copy(head);
    upgradeHead = null;

    // delegate to ws
    self.ws.handleUpgrade(req, socket, head, function(conn){
      self.onWebSocket(req, conn);
    });
  });
};

/**
 * Called upon a ws.io connection.
 *
 * @param {ws.Socket} websocket
 * @api private
 */

Server.prototype.onWebSocket = function(req, socket){
  socket.on('error', onUpgradeError);

  if (!transports[req._query.transport].prototype.handlesUpgrades) {
    debug('transport doesnt handle upgraded requests');
    socket.close();
    return;
  }

  // get client id
  var id = req._query.sid;

  // keep a reference to the ws.Socket
  req.websocket = socket;

  if (id) {
    var client = this.clients[id];
    if (!client) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (client.upgrading) {
      debug('transport has already been trying to upgrade');
      socket.close();
    } else if (client.upgraded) {
      debug('transport had already been upgraded');
      socket.close();
    } else {
      debug('upgrading existing transport');

      // transport error handling takes over
      socket.removeListener('error', onUpgradeError);

      var transport = new transports[req._query.transport](req);
      if (req._query && req._query.b64) {
        transport.supportsBinary = false;
      } else {
        transport.supportsBinary = true;
      }
      transport.perMessageDeflate = this.perMessageDeflate;
      client.maybeUpgrade(transport);
    }
  } else {
    // transport error handling takes over
    socket.removeListener('error', onUpgradeError);

    this.handshake(req._query.transport, req);
  }

  function onUpgradeError(){
    debug('websocket error before upgrade');
    // socket.close() not needed
  }
};

/**
 * Captures upgrade requests for a http.Server.
 *
 * @param {http.Server} server
 * @param {Object} options
 * @api public
 */

Server.prototype.attach = function(server, options){
  var self = this;
  var options = options || {};
  var path = (options.path || '/engine.io').replace(/\/$/, '');

  var destroyUpgrade = (options.destroyUpgrade !== undefined) ? options.destroyUpgrade : true;
  var destroyUpgradeTimeout = options.destroyUpgradeTimeout || 1000;

  // normalize path
  path += '/';

  function check (req) {
    return path == req.url.substr(0, path.length);
  }

  // cache and clean up listeners
  var listeners = server.listeners('request').slice(0);
  server.removeAllListeners('request');
  server.on('close', self.close.bind(self));

  // add request handler
  server.on('request', function(req, res){
    if (check(req)) {
      debug('intercepting request for path "%s"', path);
      self.handleRequest(req, res);
    } else {
      for (var i = 0, l = listeners.length; i < l; i++) {
        listeners[i].call(server, req, res);
      }
    }
  });

  if(~self.transports.indexOf('websocket')) {
    server.on('upgrade', function (req, socket, head) {
      if (check(req)) {
        self.handleUpgrade(req, socket, head);
      } else if (false !== options.destroyUpgrade) {
        // default node behavior is to disconnect when no handlers
        // but by adding a handler, we prevent that
        // and if no eio thing handles the upgrade
        // then the socket needs to die!
        setTimeout(function() {
           if (socket.writable && socket.bytesWritten <= 0) {
             return socket.end();
           }
        }, options.destroyUpgradeTimeout);
      }
    });
  }
};
<|endoftext|>


<commit_before>/**
 *  Copyright (c) 2014, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

'use strict';

var http = require('http');
var WSS = require('websocket').server;

module.exports = Server;

/**
 * Starts an http server with the given options and attaches a websocket server
 * to it.
 *
 * @class Server
 * @param {object} options
 */

function Server(options) {
  this.log = options.log || function() {};
  this.httpServer = http.createServer(function(req, res) {
    res.writeHead(404);
    res.end();
  });
  this.httpServer.listen(options.port);
  this.wsServer = new WSS({
    httpServer: this.httpServer,
    autoAcceptConnections: false
  });
  this.wsServer.on('request', this.onRequest.bind(this));
  this.connections = [];
}

/**
 * Request handler.
 *
 * @param {object} req
 * @private
 */

Server.prototype.onRequest = function(req) {
  this.log('Client connected', req.socket.address());
  var ws = req.accept();
  this.connections.push(ws);
  ws.on('close', this.onClose.bind(this, ws));
};

/**
 * Websocket connection close handler.
 *
 * @param {object} ws
 * @private
 */

Server.prototype.onClose = function(ws) {
  this.log('Client disconnected', ws.remoteAddress);
  if (this.connections) {
    this.connections.splice(this.connections.indexOf(ws), 1);
  }
};

/**
 * Message handler.
 *
 * @param {object} msg
 * @public
 */

Server.prototype.broadcast = function(msg) {
  this.log('Broadcasting', msg);
  msg = JSON.stringify(msg);
  this.connections.forEach(function(ws) {
    ws.send(msg);
  });
};

/**
 * Close the server.
 *
 * @public
 */

Server.prototype.close = function() {
  this.log('Shutting down WebSocket server');
  this.connections = null;
  this.wsServer.shutDown();
  this.httpServer.close();
};
<commit_msg>Honour the 'host' option.<commit_after>/**
 *  Copyright (c) 2014, Facebook, Inc.
 *  All rights reserved.
 *
 *  This source code is licensed under the BSD-style license found in the
 *  LICENSE file in the root directory of this source tree. An additional grant
 *  of patent rights can be found in the PATENTS file in the same directory.
 */

'use strict';

var http = require('http');
var WSS = require('websocket').server;

module.exports = Server;

/**
 * Starts an http server with the given options and attaches a websocket server
 * to it.
 *
 * @class Server
 * @param {object} options
 */

function Server(options) {
  this.log = options.log || function() {};
  this.httpServer = http.createServer(function(req, res) {
    res.writeHead(404);
    res.end();
  });
  this.httpServer.listen(options.port, options.host);
  this.wsServer = new WSS({
    httpServer: this.httpServer,
    autoAcceptConnections: false
  });
  this.wsServer.on('request', this.onRequest.bind(this));
  this.connections = [];
}

/**
 * Request handler.
 *
 * @param {object} req
 * @private
 */

Server.prototype.onRequest = function(req) {
  this.log('Client connected', req.socket.address());
  var ws = req.accept();
  this.connections.push(ws);
  ws.on('close', this.onClose.bind(this, ws));
};

/**
 * Websocket connection close handler.
 *
 * @param {object} ws
 * @private
 */

Server.prototype.onClose = function(ws) {
  this.log('Client disconnected', ws.remoteAddress);
  if (this.connections) {
    this.connections.splice(this.connections.indexOf(ws), 1);
  }
};

/**
 * Message handler.
 *
 * @param {object} msg
 * @public
 */

Server.prototype.broadcast = function(msg) {
  this.log('Broadcasting', msg);
  msg = JSON.stringify(msg);
  this.connections.forEach(function(ws) {
    ws.send(msg);
  });
};

/**
 * Close the server.
 *
 * @public
 */

Server.prototype.close = function() {
  this.log('Shutting down WebSocket server');
  this.connections = null;
  this.wsServer.shutDown();
  this.httpServer.close();
};
<|endoftext|>