<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,
            sockets, httpServer, serverLog,
            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,

        sockets    = function(pServer) {
            var listen, msg,
                status = 'off';

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

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

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

            Util.log(msg);
        },

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

            Server      = http.createServer(expressApp || respond);

            Server.on('error', Util.log.bind(Util));
            Server.listen(port, ip);
            serverLog(HTTP, port);
            sockets(Server);
        },

        serverLog  = function(http, port) {
            Util.log('* Server running at ' + http + ip + ':' + port);
        };
        /* server mode or testing mode */
        if (config.server)
            if (isSSL) {
                Util.log('* Redirection http -> https is setted up');
                serverLog(http, port);

                redirectServer =  http.createServer(function(req, res) {
                    var url,
                        host        = req.headers.host,
                        parsed      = url.parse(host),
                        hostName    = parsed.protocol;

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

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

                redirectServer.listen(port, ip);

                Server  =  https.createServer(ssl, respond);
                Server.on('error', function (error) {
                    Util.log(error, '\nCould not use https port: ' + sslPort);
                    redirectServer.close();
                    Util.log('* Redirection http -> https removed');
                    httpServer();
                });

                sockets(Server);

                Server.listen(sslPort, ip);
                serverLog(HTTPS, SSLPort);
            } else
                httpServer();
        else
            Util.log('Cloud Commander testing mode');
    }

    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) start<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,
            sockets, httpServer, serverLog,
            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,

        sockets    = function(pServer) {
            var listen, msg,
                status = 'off';

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

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

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

            Util.log(msg);
        },

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

            Server      = http.createServer(expressApp || respond);

            Server.on('error', Util.log.bind(Util));
            Server.listen(port, ip);
            serverLog(HTTP, port);
            sockets(Server);
        },

        serverLog  = function(http, port) {
            Util.log('* Server running at ' + http + ip + ':' + port);
        };
        /* server mode or testing mode */
        if (config.server)
            if (ssl) {
                Util.log('* Redirection http -> https is setted up');
                serverLog(http, port);

                redirectServer =  http.createServer(function(req, res) {
                    var url,
                        host        = req.headers.host,
                        parsed      = url.parse(host),
                        hostName    = parsed.protocol;

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

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

                redirectServer.listen(port, ip);

                Server  =  https.createServer(ssl, respond);
                Server.on('error', function (error) {
                    Util.log(error, '\nCould not use https port: ' + sslPort);
                    redirectServer.close();
                    Util.log('* Redirection http -> https removed');
                    httpServer();
                });

                sockets(Server);

                Server.listen(sslPort, ip);
                serverLog(HTTPS, SSLPort);
            } else
                httpServer();
        else
            Util.log('Cloud Commander testing mode');
    }

    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;

})();
<|endoftext|>


<commit_before>/*-------------------------------------------------------------------------------------------------------------------*\
|  Copyright (C) 2015 PayPal                                                                                |
|                                                                                |
|  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance     |
|  with the License.                                                                                |
|                                                                                |
|  You may obtain a copy of the License at                                                                            |
|                                                                                |
|       http://www.apache.org/licenses/LICENSE-2.0                                                                    |
|                                                                                |
|  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed   |
|  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for  |
|  the specific language governing permissions and limitations under the License.                                     |
\*-------------------------------------------------------------------------------------------------------------------*/

'use strict';
var debug = require('debug')(require('../package').name);
var path = require('path');
var util = require('./util');
var assert = require('assert');
var Config = require('./config');
var ReactDOMServer = require('react-dom/server');

var format = require('util').format;
var Performance = require('./performance');
var omit = require('lodash-node/compat/object/omit');
var merge = require('lodash-node/compat/object/merge');

// safely require the peer-dependencies
var React = util.safeRequire('react');
var Router = util.safeRequire('react-router');

var match = Router.match;
var RoutingContext = Router.RoutingContext;

// a template of the `script` tag that gets
// injected into the server rendered pages.
var TEMPLATE = [''
              ].join('');
var TEMPLATE_REDIRECT = '<meta http-equiv="refresh" content="0;URL=\'%s\'" />';

exports.create = function create(createOptions) {

  createOptions = createOptions || {};
  createOptions.renderOptionsKeysToFilter = createOptions.renderOptionsKeysToFilter || [];

  assert(Array.isArray(createOptions.renderOptionsKeysToFilter),
      '`renderOptionsKeysToFilter` - should be an array');

  createOptions.renderOptionsKeysToFilter =
    createOptions.renderOptionsKeysToFilter.concat(Config.defaultKeysToFilter);

  if (createOptions.performanceCollector) {
    assert.equal(typeof createOptions.performanceCollector,
        'function',
        '`performanceCollector` - should be a function');
  }

  // the render implementation
  return function render(thing, options, callback) {

    var perfInstance;

    if (createOptions.performanceCollector) {
      perfInstance = Performance(thing);
    }

    function done(err, html) {
      if (!options.settings['view cache']) {
        // remove all the files under the express's view folder from require cache.
        // Helps in making changes to react views without restarting the server.
        util.clearRequireCache(createOptions.routesFilePath);
        util.clearRequireCacheInDir(options.settings.views, options.settings['view engine']);
      }

      if (createOptions.performanceCollector) {
        createOptions.performanceCollector(perfInstance());
      }

      console.log('done with %s', html);
      callback(err, html);
    }

    function renderAndDecorate(component, html) {
      // render the component
      html += ReactDOMServer.renderToString(component);

      // state (script) injection
      var script = format(TEMPLATE, Config.client.markupId, JSON.stringify(data));
      html = html.replace('', script + '');

      return html;
    }

    if (createOptions.routes && createOptions.routesFilePath) {
      // if `routesFilePath` property is provided, then in
      // cases where 'view cache' is false, the routes are reloaded for every render.
      createOptions.routes = require(createOptions.routesFilePath);
    }

    // initialize the markup string
    var html = Config.docType;

    // create the data object that will be fed into the React render method.
    // Data is a mash of the express' `render options` and `res.locals`
    // and meta info about `react-engine`
    var data = merge({
      __meta: {
        // get just the relative path for view file name
        view: null,
        markupId: Config.client.markupId
      }
    }, omit(options, createOptions.renderOptionsKeysToFilter));
    if (this.useRouter && !createOptions.routes) {
      return done(new Error('asking to use react router for rendering, but no routes are provided'));
    }

    var isRedirect = false;

    try {
      if (this.useRouter) {
        return match({ routes:createOptions.routes, location:thing},
          function(error, redirectLocation, renderProps) {
          if (redirectLocation) {
            debug('server.js match 302 %s', redirectLocation);

            // perform a client-side redirect, see https://github.com/rackt/history/blob/master/docs/Location.md
            console.log('The redirectLocation is', require('util').inspect(redirectLocation));
            return done(null, format(TEMPLATE_REDIRECT, redirectLocation.pathname + redirectLocation.search));

            //res.redirect(302, redirectLocation.pathname + redirectLocation.search);
          } else if (error) {
            debug('server.js match 500 %s', error);

            //res.send(500, error.message);
          } else if (renderProps) {
            return done(null, renderAndDecorate(React.createElement(RoutingContext, renderProps), html));

            //res.send(404, 'Not found');
          } else {
            console.log('hit 404');
            debug('server.js match 404 %s', renderProps);
          }
        });
      } else {
        // path utility to make path string compatible in different OS
        // ------------------------------------------------------------
        // use `path.normalize()` to normalzie absolute view file path and absolute base directory path
        // to prevent path strings like `/folder1/folder2/../../folder3/exampleFile`
        // then, derive relative view file path
        // and replace backslash with slash to be compatible on Windows
        data.__meta.view = path.normalize(thing)
          .replace(path.normalize(options.settings.views), '').substring(1)
          .replace('\\', '/');

        var view = require(thing);

        // create the Component using react's createFactory
        var component = React.createFactory(view);
        return done(null, renderAndDecorate(component(data), html));
      }
    }
    catch (err) {

      // on error, pass to the next
      // middleware in the chain!
      return done(err);
    }
  };
};
<commit_msg>Remove assorted console.log<commit_after>/*-------------------------------------------------------------------------------------------------------------------*\
|  Copyright (C) 2015 PayPal                                                                                |
|                                                                                |
|  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance     |
|  with the License.                                                                                |
|                                                                                |
|  You may obtain a copy of the License at                                                                            |
|                                                                                |
|       http://www.apache.org/licenses/LICENSE-2.0                                                                    |
|                                                                                |
|  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed   |
|  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for  |
|  the specific language governing permissions and limitations under the License.                                     |
\*-------------------------------------------------------------------------------------------------------------------*/

'use strict';
var debug = require('debug')(require('../package').name);
var path = require('path');
var util = require('./util');
var assert = require('assert');
var Config = require('./config');
var ReactDOMServer = require('react-dom/server');

var format = require('util').format;
var Performance = require('./performance');
var omit = require('lodash-node/compat/object/omit');
var merge = require('lodash-node/compat/object/merge');

// safely require the peer-dependencies
var React = util.safeRequire('react');
var Router = util.safeRequire('react-router');

var match = Router.match;
var RoutingContext = Router.RoutingContext;

// a template of the `script` tag that gets
// injected into the server rendered pages.
var TEMPLATE = [''
              ].join('');
var TEMPLATE_REDIRECT = '<meta http-equiv="refresh" content="0;URL=\'%s\'" />';

exports.create = function create(createOptions) {

  createOptions = createOptions || {};
  createOptions.renderOptionsKeysToFilter = createOptions.renderOptionsKeysToFilter || [];

  assert(Array.isArray(createOptions.renderOptionsKeysToFilter),
      '`renderOptionsKeysToFilter` - should be an array');

  createOptions.renderOptionsKeysToFilter =
    createOptions.renderOptionsKeysToFilter.concat(Config.defaultKeysToFilter);

  if (createOptions.performanceCollector) {
    assert.equal(typeof createOptions.performanceCollector,
        'function',
        '`performanceCollector` - should be a function');
  }

  // the render implementation
  return function render(thing, options, callback) {

    var perfInstance;

    if (createOptions.performanceCollector) {
      perfInstance = Performance(thing);
    }

    function done(err, html) {
      if (!options.settings['view cache']) {
        // remove all the files under the express's view folder from require cache.
        // Helps in making changes to react views without restarting the server.
        util.clearRequireCache(createOptions.routesFilePath);
        util.clearRequireCacheInDir(options.settings.views, options.settings['view engine']);
      }

      if (createOptions.performanceCollector) {
        createOptions.performanceCollector(perfInstance());
      }

      callback(err, html);
    }

    function renderAndDecorate(component, html) {
      // render the component
      html += ReactDOMServer.renderToString(component);

      // state (script) injection
      var script = format(TEMPLATE, Config.client.markupId, JSON.stringify(data));
      html = html.replace('', script + '');

      return html;
    }

    if (createOptions.routes && createOptions.routesFilePath) {
      // if `routesFilePath` property is provided, then in
      // cases where 'view cache' is false, the routes are reloaded for every render.
      createOptions.routes = require(createOptions.routesFilePath);
    }

    // initialize the markup string
    var html = Config.docType;

    // create the data object that will be fed into the React render method.
    // Data is a mash of the express' `render options` and `res.locals`
    // and meta info about `react-engine`
    var data = merge({
      __meta: {
        // get just the relative path for view file name
        view: null,
        markupId: Config.client.markupId
      }
    }, omit(options, createOptions.renderOptionsKeysToFilter));
    if (this.useRouter && !createOptions.routes) {
      return done(new Error('asking to use react router for rendering, but no routes are provided'));
    }

    var isRedirect = false;

    try {
      if (this.useRouter) {
        return match({ routes:createOptions.routes, location:thing},
          function(error, redirectLocation, renderProps) {
          if (redirectLocation) {
            debug('server.js match 302 %s', redirectLocation);

            // perform a client-side redirect, see https://github.com/rackt/history/blob/master/docs/Location.md
            return done(null, format(TEMPLATE_REDIRECT, redirectLocation.pathname + redirectLocation.search));

            //res.redirect(302, redirectLocation.pathname + redirectLocation.search);
          } else if (error) {
            debug('server.js match 500 %s', error);

            //res.send(500, error.message);
          } else if (renderProps) {
            return done(null, renderAndDecorate(React.createElement(RoutingContext, renderProps), html));

            //res.send(404, 'Not found');
          } else {
            debug('server.js match 404 %s', renderProps);
          }
        });
      } else {
        // path utility to make path string compatible in different OS
        // ------------------------------------------------------------
        // use `path.normalize()` to normalzie absolute view file path and absolute base directory path
        // to prevent path strings like `/folder1/folder2/../../folder3/exampleFile`
        // then, derive relative view file path
        // and replace backslash with slash to be compatible on Windows
        data.__meta.view = path.normalize(thing)
          .replace(path.normalize(options.settings.views), '').substring(1)
          .replace('\\', '/');

        var view = require(thing);

        // create the Component using react's createFactory
        var component = React.createFactory(view);
        return done(null, renderAndDecorate(component(data), html));
      }
    }
    catch (err) {

      // on error, pass to the next
      // middleware in the chain!
      return done(err);
    }
  };
};
<|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.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false;
  this.httpCompression = false !== opts.httpCompression ? (opts.httpCompression || {}) : false;

  if (true === this.httpCompression) this.httpCompression = {};
  if (this.httpCompression && null == this.httpCompression.threshold) {
    this.httpCompression.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) {
    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]
    }));
 }

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

Server.prototype.handshake = function(transport, req){
  var id = base64id.generateId();

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

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

    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){
      headers['Set-Cookie'] = self.cookie + '=' + id;
    });
  }

  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) {
    if (!this.clients[id]) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (this.clients[id].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;
      }
      this.clients[id].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>Allow overriding the cookiePath<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;
  this.perMessageDeflate = false !== opts.perMessageDeflate ? (opts.perMessageDeflate || true) : false;
  this.httpCompression = false !== opts.httpCompression ? (opts.httpCompression || {}) : false;

  if (true === this.httpCompression) this.httpCompression = {};
  if (this.httpCompression && null == this.httpCompression.threshold) {
    this.httpCompression.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) {
    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]
    }));
 }

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

Server.prototype.handshake = function(transport, req){
  var id = base64id.generateId();

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

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

    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) {
    if (!this.clients[id]) {
      debug('upgrade attempt for closed client');
      socket.close();
    } else if (this.clients[id].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;
      }
      this.clients[id].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);
      }
    });
  }
};
<|endoftext|>


<commit_before>var assert = require('assert-plus');
var dgram = require('dgram');
var net = require('net');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var Query = require('./query');
var DnsError = require('./errors');

///--- Globals

var sprintf = util.format;

var ExceptionError = DnsError.ExceptionError;
var ProtocolError = DnsError.ProtocolError;

///--- API

function Server(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');
        assert.optionalString(options.name, 'options.name');

        this._log = options.log.child({component: 'named.Server'}, true);
        this._name = options.name || "named";
        this._socket = null;

}
util.inherits(Server, EventEmitter);

Server.prototype.close = function close(callback) {
        if (typeof (callback) === 'function')
                this._socket.once('close', callback);

        this._socket.close();
};

Server.prototype.listenTcp = function listenTcp(opts, callback) {
        assert.object(opts, 'options');
        assert.number(opts.port, 'port');
        if (opts.address === undefined)
                opts.address = '0.0.0.0';
        assert.string(opts.address, 'address');
        assert.optionalFunc(callback, 'callback');

        var self = this;
        assert.strictEqual(this._server, undefined,
            'listenTcp can only be called once');
        this._server = net.createServer();
        this._server.listen(opts.port, opts.address, callback);
        this._server.on('close', function onSocketClose() {
                self.emit('close');
        });
        this._server.on('error', function onSocketError(err) {
                self.emit('error', err);
        });
        this._server.on('connection', function(sock) {
                var qbuf = new Buffer(0);
                sock.on('error', function onSocketError(err) {
                        self.emit('error', err);
                });
                sock.on('data', function(data) {
                        qbuf = Buffer.concat([qbuf, data]);

                        if (qbuf.length < 2)
                                return;
                        var len = qbuf.readUInt16BE(0);
                        if (qbuf.length - 2 < len)
                                return;

                        qbuf = qbuf.slice(2);

                        var query;
                        var qopts = {
                                family: 'tcp',
                                address: sock.remoteAddress,
                                socket: sock,
                                data: qbuf
                        };
                        try {
                                query = Query.parse(qopts);
                        } catch (e) {
                                if (self.listenerCount('clientError') <= 0) {
                                    self._log.warn({
                                            err: e
                                    }, 'query failed to parse');
                                } else {
                                    var err = new ProtocolError(
                                        'invalid DNS datagram');
                                    err.client = {
                                        address: sock.remoteAddress
                                    };
                                    err.innerError = e;
                                    self.emit('clientError', err);
                                }
                                return;
                        }

                        if (query === undefined || query === null) {
                                return;
                        }

                        sock.on("end", function() {
                                delete query.src.socket;
                        });
                        sock.on("close", function() {
                                delete query.src.socket;
                        });

                        query.respond = function respond() {
                                self.send(query);
                        };

                        try {
                                self.emit('query', query);
                        } catch (e) {
                                self._log.warn({
                                        err: e
                                }, 'query handler threw an uncaughtException');
                                self.emit('error', e);
                        }
                });
                sock.on('close', function() {
                        qbuf = undefined;
                })
        });
        this._server.on('close', function() {
                self.emit('close');
        })
}

Server.prototype.listenUdp = function listenUdp(opts, callback) {
        assert.object(opts, 'options');
        assert.number(opts.port, 'port');
        if (opts.address === undefined)
                opts.address = '0.0.0.0';
        assert.string(opts.address, 'address');
        if (opts.family === undefined)
                opts.family = 'udp6';
        assert.string(opts.family, 'family');
        assert.optionalFunc(callback, 'callback');

        var self = this;
        assert.strictEqual(this._socket, null,
            'listen can only be called once');

        this._socket = dgram.createSocket(opts.family);
        this._socket.once('listening', function () {
                self.emit('listening');
                if (typeof (callback) === 'function')
                        process.nextTick(callback);
        });
        this._socket.on('close', function onSocketClose() {
                self.emit('close');
        });
        this._socket.on('error', function onSocketError(err) {
                if (self._socket.bound || opts.family === 'udp4') {
                        self.emit('error', err);
                } else {
                        if (err.code && err.code === 'EINVAL') {
                                self._log.debug({err: err},
                                    '%s socket failed to bind, falling ' +
                                    'back to udp4', opts.family);
                                delete (self._socket);
                                self._socket = null;
                                var opts2 = {family: 'udp4'};
                                Object.setPrototypeOf(opts2, opts);
                                self.listenUdp(opts2, callback);
                        } else {
                                self.emit('error', err);
                        }
                }
        });
        this._socket.on('message', function (buffer, rinfo) {
                var query;

                var qopts = {
                        family: 'udp',
                        address: rinfo.address,
                        port: rinfo.port,
                        data: buffer
                };

                try {
                        query = Query.parse(qopts);
                } catch (e) {
                        if (self.listenerCount('clientError') <= 0) {
                            self._log.warn({
                                    err: e
                            }, 'query failed to parse');
                        } else {
                            var err = new ProtocolError(
                                'invalid DNS datagram');
                            err.client = {
                                address: rinfo.address,
                                port: rinfo.port
                            };
                            err.innerError = e;
                            self.emit('clientError', err);
                        }
                        return;
                }

                if (query === undefined || query === null) {
                        return;
                }

                query.respond = function respond() {
                        self.send(query);
                };

                try {
                        self.emit('query', query);
                } catch (e) {
                        self._log.warn({
                                err: e
                        }, 'query handler threw an uncaughtException');
                        self.emit('error', e);
                }
        });
        this._socket.bind(opts.port, opts.address, function () {
                self._socket.bound = true;
        });
};

Server.prototype.listen = function listen(port, address, callback) {
        var opts = {};
        opts.port = port;
        if (typeof (address) === 'function')
                callback = address;
        else
                opts.address = address;
        return (this.listenUdp(opts, callback));
}

Server.prototype.send = function send(res) {
        assert.object(res);

        assert.func(res.encode);

        assert.object(res.src);
        assert.string(res.src.address);
        assert.string(res.src.family);

        var buf = res.encode();

        var family = res.src.family;
        switch (family) {
                case 'udp':
                        assert.number(res.src.port);

                        var addr = res.src.address;
                        var len = buf.length;
                        var port = res.src.port;
                        var self = this;

                        this._log.trace({
                                adddress: addr,
                                port: port,
                                len: len
                        }, 'send: writing DNS message to socket');

                        this._socket.send(buf, 0, len, port, addr, function (err, bytes) {
                                if (err) {
                                        self._log.warn({
                                                adddress: addr,
                                                port: port,
                                                err: err
                                        }, 'send: unable to send response');
                                        self.emit('error', new ExceptionError(err.message));
                                } else {
                                        self._log.trace({
                                                adddress: addr,
                                                port: port
                                        }, 'send: DNS response sent');
                                        self.emit('after', res, bytes);
                                }
                        });
                        break;

                case 'tcp':
                        var sock = res.src.socket;
                        if (!sock) {
                                this._log.debug("send: connection already closed, dropping data");
                                break;
                        }

                        var len = buf.length;
                        var self = this;
                        this._log.trace({
                                adddress: sock.remoteAddress,
                                len: len
                        }, 'send: writing DNS message to TCP socket');
                        var lenbuf = new Buffer(2);
                        lenbuf.writeUInt16BE(buf.length, 0);
                        sock.write(Buffer.concat([lenbuf, buf]));

                        res.reset();
                        break;
        }
};

Server.prototype.toString = function toString() {
        var str = '[object named.Server <';
        str += sprintf('name=%s, ', this._name);
        str += sprintf('socket=%j', this._socket ? this._socket.address() : {});
        str += '>]';
        return (str);
};

///--- Exports

module.exports = Server;
<commit_msg>Provide an end callback on query events<commit_after>var assert = require('assert-plus');
var dgram = require('dgram');
var net = require('net');
var EventEmitter = require('events').EventEmitter;
var util = require('util');

var Query = require('./query');
var DnsError = require('./errors');

///--- Globals

var sprintf = util.format;

var ExceptionError = DnsError.ExceptionError;
var ProtocolError = DnsError.ProtocolError;

///--- API

function Server(options) {
        assert.object(options, 'options');
        assert.object(options.log, 'options.log');
        assert.optionalString(options.name, 'options.name');

        this._log = options.log.child({component: 'named.Server'}, true);
        this._name = options.name || "named";
        this._socket = null;

}
util.inherits(Server, EventEmitter);

Server.prototype.close = function close(callback) {
        if (typeof (callback) === 'function')
                this._socket.once('close', callback);

        this._socket.close();
};

Server.prototype.listenTcp = function listenTcp(opts, callback) {
        assert.object(opts, 'options');
        assert.number(opts.port, 'port');
        if (opts.address === undefined)
                opts.address = '0.0.0.0';
        assert.string(opts.address, 'address');
        assert.optionalFunc(callback, 'callback');

        var self = this;
        assert.strictEqual(this._server, undefined,
            'listenTcp can only be called once');
        this._server = net.createServer();
        this._server.listen(opts.port, opts.address, callback);
        this._server.on('close', function onSocketClose() {
                self.emit('close');
        });
        this._server.on('error', function onSocketError(err) {
                self.emit('error', err);
        });
        this._server.on('connection', function(sock) {
                var qbuf = new Buffer(0);
                sock.on('error', function onSocketError(err) {
                        self.emit('error', err);
                });
                sock.on('data', function(data) {
                        qbuf = Buffer.concat([qbuf, data]);

                        if (qbuf.length < 2)
                                return;
                        var len = qbuf.readUInt16BE(0);
                        if (qbuf.length - 2 < len)
                                return;

                        qbuf = qbuf.slice(2);

                        var query;
                        var qopts = {
                                family: 'tcp',
                                address: sock.remoteAddress,
                                socket: sock,
                                data: qbuf
                        };
                        try {
                                query = Query.parse(qopts);
                        } catch (e) {
                                if (self.listenerCount('clientError') <= 0) {
                                    self._log.warn({
                                            err: e
                                    }, 'query failed to parse');
                                } else {
                                    var err = new ProtocolError(
                                        'invalid DNS datagram');
                                    err.client = {
                                        address: sock.remoteAddress
                                    };
                                    err.innerError = e;
                                    self.emit('clientError', err);
                                }
                                return;
                        }

                        if (query === undefined || query === null) {
                                return;
                        }

                        sock.on("end", function() {
                                delete query.src;
                        });
                        sock.on("close", function() {
                                delete query.src;
                        });

                        query.respond = query.send = function respond() {
                                self.send(query);
                        };

                        query.end = function end() {
                                sock.destroy();
                                delete query.src;
                        };

                        try {
                                self.emit('query', query, query.end);
                        } catch (e) {
                                self._log.warn({
                                        err: e
                                }, 'query handler threw an uncaughtException');
                                self.emit('error', e);
                        }
                });
                sock.on('close', function() {
                        qbuf = undefined;
                })
        });
        this._server.on('close', function() {
                self.emit('close');
        })
}

Server.prototype.listenUdp = function listenUdp(opts, callback) {
        assert.object(opts, 'options');
        assert.number(opts.port, 'port');
        if (opts.address === undefined)
                opts.address = '0.0.0.0';
        assert.string(opts.address, 'address');
        if (opts.family === undefined)
                opts.family = 'udp6';
        assert.string(opts.family, 'family');
        assert.optionalFunc(callback, 'callback');

        var self = this;
        assert.strictEqual(this._socket, null,
            'listen can only be called once');

        this._socket = dgram.createSocket(opts.family);
        this._socket.once('listening', function () {
                self.emit('listening');
                if (typeof (callback) === 'function')
                        process.nextTick(callback);
        });
        this._socket.on('close', function onSocketClose() {
                self.emit('close');
        });
        this._socket.on('error', function onSocketError(err) {
                if (self._socket.bound || opts.family === 'udp4') {
                        self.emit('error', err);
                } else {
                        if (err.code && err.code === 'EINVAL') {
                                self._log.debug({err: err},
                                    '%s socket failed to bind, falling ' +
                                    'back to udp4', opts.family);
                                delete (self._socket);
                                self._socket = null;
                                var opts2 = {family: 'udp4'};
                                Object.setPrototypeOf(opts2, opts);
                                self.listenUdp(opts2, callback);
                        } else {
                                self.emit('error', err);
                        }
                }
        });
        this._socket.on('message', function (buffer, rinfo) {
                var query;

                var qopts = {
                        family: 'udp',
                        address: rinfo.address,
                        port: rinfo.port,
                        data: buffer
                };

                try {
                        query = Query.parse(qopts);
                } catch (e) {
                        if (self.listenerCount('clientError') <= 0) {
                            self._log.warn({
                                    err: e
                            }, 'query failed to parse');
                        } else {
                            var err = new ProtocolError(
                                'invalid DNS datagram');
                            err.client = {
                                address: rinfo.address,
                                port: rinfo.port
                            };
                            err.innerError = e;
                            self.emit('clientError', err);
                        }
                        return;
                }

                if (query === undefined || query === null) {
                        return;
                }

                query.respond = query.send = function respond() {
                        self.send(query);
                };

                query.end = function end() {
                        delete query.src;
                };

                try {
                        self.emit('query', query, query.end);
                } catch (e) {
                        self._log.warn({
                                err: e
                        }, 'query handler threw an uncaughtException');
                        self.emit('error', e);
                }
        });
        this._socket.bind(opts.port, opts.address, function () {
                self._socket.bound = true;
        });
};

Server.prototype.listen = function listen(port, address, callback) {
        var opts = {};
        opts.port = port;
        if (typeof (address) === 'function')
                callback = address;
        else
                opts.address = address;
        return (this.listenUdp(opts, callback));
}

Server.prototype.send = function send(res) {
        assert.object(res);

        assert.func(res.encode);

        assert.object(res.src);
        assert.string(res.src.address);
        assert.string(res.src.family);

        var buf = res.encode();

        var family = res.src.family;
        switch (family) {
                case 'udp':
                        assert.number(res.src.port);

                        var addr = res.src.address;
                        var len = buf.length;
                        var port = res.src.port;
                        var self = this;

                        this._log.trace({
                                adddress: addr,
                                port: port,
                                len: len
                        }, 'send: writing DNS message to socket');

                        this._socket.send(buf, 0, len, port, addr, function (err, bytes) {
                                if (err) {
                                        self._log.warn({
                                                adddress: addr,
                                                port: port,
                                                err: err
                                        }, 'send: unable to send response');
                                        self.emit('error', new ExceptionError(err.message));
                                } else {
                                        self._log.trace({
                                                adddress: addr,
                                                port: port
                                        }, 'send: DNS response sent');
                                        self.emit('after', res, bytes);
                                }
                        });
                        break;

                case 'tcp':
                        var sock = res.src.socket;
                        if (!sock) {
                                this._log.debug("send: connection already closed, dropping data");
                                break;
                        }

                        var len = buf.length;
                        var self = this;
                        this._log.trace({
                                adddress: sock.remoteAddress,
                                len: len
                        }, 'send: writing DNS message to TCP socket');
                        var lenbuf = new Buffer(2);
                        lenbuf.writeUInt16BE(buf.length, 0);
                        sock.write(Buffer.concat([lenbuf, buf]));

                        res.reset();
                        break;
        }
};

Server.prototype.toString = function toString() {
        var str = '[object named.Server <';
        str += sprintf('name=%s, ', this._name);
        str += sprintf('socket=%j', this._socket ? this._socket.address() : {});
        str += '>]';
        return (str);
};

///--- Exports

module.exports = Server;
<|endoftext|>


<commit_before>/**
 * Copyright reelyActive 2015
 * We believe in an open Internet of Things
 */

var http = require('http');
var express = require('express');
var responseHandler = require('./responsehandler');

var HTTP_PORT = 3001;

/**
 * Barterer Class
 * API for real-time location and hyperlocal context.
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function Barterer(options) {
  var self = this;
  options = options || {};
  self.specifiedHttpPort = options.httpPort || HTTP_PORT;
  self.httpPort = process.env.PORT || self.specifiedHttpPort;

  self.app = express();

  self.router = express.Router();
  self.router.use(function(req, res, next) {
    // TODO: basic error checking goes here in the middleware
    next();
  });

  // ----- route: / ------
  self.router.route('/')

    .get(function(req, res) {
      routeNotImplemented(function(response, status) {
        res.status(status).json(response);
      });
    });

  // ----- route: /id/:id ------
  self.router.route('/id/:id')

    .get(function(req, res) {
      var identifiers = [ req.param('id') ];
      self.retrieveDevices(identifiers, req, function(response, status) {
        res.status(status).json(response);
      });
    });

  // ----- route: /at/:place ------
  self.router.route('/at/:place')

    .get(function(req, res) {
      var place = req.param('place');
      self.retrievePlace(place, req, function(response, status) {
        res.status(status).json(response);
      });
    });

  self.app.use('/', self.router);

  console.log("reelyActive Barterer instance is exchanging data in an open IoT");

  self.app.listen(self.httpPort, function() {
    console.log("barterer is listening on port", self.httpPort);
  });
};

/**
 * Bind the API to the given data source and/or associations.
 * @param {Object} options The options as a JSON object.
 */
Barterer.prototype.bind = function(options) {
  options = options || {};
  var self = this;

  if(options.barnacles) {
    self.dataStore = options.barnacles;
  }
  if(options.chickadee) {
    self.associations = options.chickadee;
  }
}

/**
 * Retrieve (get) the state of the given device(s).
 * @param {Array} identifiers The array of identifiers.
 * @param {Object} req The request.
 * @param {callback} callback Function to call on completion.
 */
Barterer.prototype.retrieveDevices = function(identifiers, req, callback) {
  var self = this;

  if(self.dataStore) {
    self.dataStore.getState({ ids: identifiers }, function(state) {
      var foundNothing = !Object.keys(state).length;
      if(foundNothing) {
        var data = { devices: { } };
        callback(responseHandler.prepareResponse(responseHandler.OK, req,
                                                 data), responseHandler.OK);
      }
      else if(self.associations) {
        self.associations.addUrls(state, function(associated) {
          var data = { devices: associated };
          callback(responseHandler.prepareResponse(responseHandler.OK, req,
                                                   data), responseHandler.OK);
        });
      }
      else {
        var data = { devices: state };
        callback(responseHandler.prepareResponse(responseHandler.OK, req,
                                                 data), responseHandler.OK);
      }
    });
  }
  else {
    callback(responseHandler.prepareResponse(responseHandler.NOTIMPLEMENTED),
             responseHandler.NOTIMPLEMENTED);
  }
}

/**
 * Retrieve (get) the state of the given place.
 * @param {String} place The name of the place.
 * @param {Object} req The request.
 * @param {callback} callback Function to call on completion.
 */
Barterer.prototype.retrievePlace = function(place, req, callback) {
  var self = this;

  if(self.associations) {
    self.associations.retrievePlace(place, req, function(response, status) {
      if(status === responseHandler.OK) {
        var identifiers = response.places[place].identifiers;
        if(identifiers.length > 0) {
          self.retrieveDevices(identifiers, req, callback);
        }
        else {
          callback(responseHandler.prepareResponse(responseHandler.NOTFOUND),
                   responseHandler.NOTFOUND);
        }
      }
      else {
        callback(responseHandler.prepareResponse(responseHandler.NOTFOUND),
                 responseHandler.NOTFOUND);
      }
    });
  }
  else {
    callback(responseHandler.prepareResponse(responseHandler.NOTIMPLEMENTED),
             responseHandler.NOTIMPLEMENTED);
  }
}

/**
 * Handle a route that is not implemented.
 * @param {callback} callback Function to call on completion.
 */
function routeNotImplemented(callback) {
  callback(responseHandler.prepareResponse(responseHandler.NOTIMPLEMENTED),
           responseHandler.NOTIMPLEMENTED);
}

module.exports = Barterer;
<commit_msg>Improved error status handling<commit_after>/**
 * Copyright reelyActive 2015
 * We believe in an open Internet of Things
 */

var http = require('http');
var express = require('express');
var path = require('path');
var responseHandler = require('./responsehandler');

var HTTP_PORT = 3001;

/**
 * Barterer Class
 * API for real-time location and hyperlocal context.
 * @param {Object} options The options as a JSON object.
 * @constructor
 */
function Barterer(options) {
  var self = this;
  options = options || {};
  self.specifiedHttpPort = options.httpPort || HTTP_PORT;
  self.httpPort = process.env.PORT || self.specifiedHttpPort;

  self.app = express();

  self.app.use(function(req, res, next) {
    req.instance = self;
    next();
  });

  self.app.use('/id', require('./routes/id'));
  self.app.use('/at', require('./routes/at'));
  self.app.use('/', express.static(path.resolve(__dirname + '/../web')));

  console.log("reelyActive Barterer instance is exchanging data in an open IoT");

  self.app.listen(self.httpPort, function() {
    console.log("barterer is listening on port", self.httpPort);
  });
};

/**
 * Bind the API to the given data source and/or associations.
 * @param {Object} options The options as a JSON object.
 */
Barterer.prototype.bind = function(options) {
  options = options || {};
  var self = this;

  if(options.barnacles) {
    self.dataStore = options.barnacles;
  }
  if(options.chickadee) {
    self.associations = options.chickadee;
  }
}

/**
 * Get the state of the given device(s).
 * @param {Array} identifiers The device identifier(s) to query.
 * @param {String} rootUrl The root URL of the original query.
 * @param {String} queryPath The query path of the original query.
 * @param {callback} callback Function to call on completion.
 */
Barterer.prototype.getDevices = function(identifiers, rootUrl, queryPath,
                                         callback) {
  var self = this;

  getState(self, identifiers, function(state, err) {
    if(err) {
      var status = err.status;
      var response = responseHandler.prepareResponse(status, rootUrl,
                                                     queryPath);
      callback(response, status);
    }
    else {
      var status = responseHandler.OK;
      var response = responseHandler.prepareResponse(status, rootUrl,
                                                     queryPath, state);
      callback(response, status);
    }
  });
}

/**
 * Get the state of the given place.
 * @param {String} place The place name to query.
 * @param {String} rootUrl The root URL of the original query.
 * @param {String} queryPath The query path of the original query.
 * @param {callback} callback Function to call on completion.
 */
Barterer.prototype.getPlace = function(place, rootUrl, queryPath, callback) {
  var self = this;

  getPlaceIdentifiers(self, place, function(identifiers, err) {
    if(err) {
      var status = err.status;
      var response = responseHandler.prepareResponse(status, rootUrl,
                                                     queryPath);
      callback(response, status);
    }
    else {
      self.getDevices(identifiers, rootUrl, queryPath,
                      function(response, status) {
        callback(response, status);
      });
    }
  });
}

/**
 * Get the current state from the dataStore.
 * @param {Barterer} instance The given barterer instance.
 * @param {Array} identifiers The array of identifiers to query.
 * @param {callback} callback Function to call on completion.
 */
function getState(instance, identifiers, callback) {
  if(instance.dataStore) {
    instance.dataStore.getState({ ids: identifiers }, function(state) {
      var foundNothing = !Object.keys(state).length;
      if(foundNothing) {
        callback( { devices: { } } );
      }
      else if(instance.associations) {
        instance.associations.addUrls(state, function(associated) {
          callback( { devices: associated } );
        });
      }
      else {
        callback( { devices: state } );
      }
    });
  }
  else {
    var err = { message: "Not bound to a barnacles instance",
                status: responseHandler.SERVICEUNAVAILABLE };
    callback(null, err);
  }
}

/**
 * Get the identifiers associated with the given place.
 * @param {Barterer} instance The given barterer instance.
 * @param {String} place The place to query.
 * @param {callback} callback Function to call on completion.
 */
function getPlaceIdentifiers(instance, place, callback) {
  if(instance.associations) {
    instance.associations.getPlace(place, function(response, status) {
      if(response.places) {
        var identifiers = response.places[place].identifiers;
        callback(identifiers);
      }
      else {
        var err = { message: "Unknown place",
                    status: responseHandler.NOTFOUND };
        callback(null, err);
      }
    });
  }
  else {
    var err = { message: "Not bound to a chickadee instance",
                status: responseHandler.SERVICEUNAVAILABLE };
    callback(null, err);
  }
}

module.exports = Barterer;
<|endoftext|>