maglev
Version:
Preconfigured NodeJS framework
584 lines (440 loc) • 15.8 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _express = require('express');
var _express2 = _interopRequireDefault(_express);
var _debug = require('debug');
var _debug2 = _interopRequireDefault(_debug);
var _http = require('http');
var _http2 = _interopRequireDefault(_http);
var _isArray = require('lodash/isArray');
var _isArray2 = _interopRequireDefault(_isArray);
var _expressDomainMiddleware = require('express-domain-middleware');
var _expressDomainMiddleware2 = _interopRequireDefault(_expressDomainMiddleware);
var _compression = require('compression');
var _compression2 = _interopRequireDefault(_compression);
var _serveFavicon = require('serve-favicon');
var _serveFavicon2 = _interopRequireDefault(_serveFavicon);
var _serveStatic = require('serve-static');
var _serveStatic2 = _interopRequireDefault(_serveStatic);
var _cookieParser = require('cookie-parser');
var _cookieParser2 = _interopRequireDefault(_cookieParser);
var _expressSession = require('express-session');
var _expressSession2 = _interopRequireDefault(_expressSession);
var _bodyParser = require('body-parser');
var _bodyParser2 = _interopRequireDefault(_bodyParser);
var _methodOverride = require('method-override');
var _methodOverride2 = _interopRequireDefault(_methodOverride);
var _responseTime = require('response-time');
var _responseTime2 = _interopRequireDefault(_responseTime);
var _connectTimeout = require('connect-timeout');
var _connectTimeout2 = _interopRequireDefault(_connectTimeout);
var _morgan = require('morgan');
var _morgan2 = _interopRequireDefault(_morgan);
var _cors = require('cors');
var _cors2 = _interopRequireDefault(_cors);
var _lessMiddleware = require('less-middleware');
var _lessMiddleware2 = _interopRequireDefault(_lessMiddleware);
var _request = require('express/lib/request');
var _request2 = _interopRequireDefault(_request);
var _consolidate = require('consolidate');
var _consolidate2 = _interopRequireDefault(_consolidate);
var _connectFlash = require('connect-flash');
var _connectFlash2 = _interopRequireDefault(_connectFlash);
var _robots = require('robots.txt');
var _robots2 = _interopRequireDefault(_robots);
var _MiddlewareType = require('./constants/MiddlewareType');
var _MiddlewareType2 = _interopRequireDefault(_MiddlewareType);
var _file = require('./controllers/file');
var fileController = _interopRequireWildcard(_file);
var _page = require('./controllers/page');
var pageController = _interopRequireWildcard(_page);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var log = (0, _debug2.default)('maglev:app');
function connectionToUnique(conn) {
return conn.remoteAddress + ':' + conn.remotePort;
}
var App = function () {
function App(server) {
var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
_classCallCheck(this, App);
if (!options.root) {
throw new Error('Root is undefined');
}
log('App root: ' + options.root);
this._server = server;
this._options = options;
this._expressApp = (0, _express2.default)();
this._httpServer = null;
this._activeConnections = {};
// prepare basic
this._prepareErrorHandler();
this._prepareCompression();
this._prepareLog();
this._prepareEngine();
this._prepareHtml();
this._prepareMiddleware(_MiddlewareType2.default.BEFORE_STATIC);
// prepare static
this._prepareStatic();
// prepare middlewares
this._prepareVars();
this._prepareSession();
this._prepareSecure();
this._prepareMiddleware(_MiddlewareType2.default.BEFORE_ROUTER);
this._prepareRouter();
this._prepareMiddleware(_MiddlewareType2.default.AFTER_ROUTER);
}
_createClass(App, [{
key: 'listen',
value: function listen(port, host, callback) {
if (this._httpServer) {
return callback(new Error('You need to close http server first'));
}
this._httpServer = _http2.default.createServer(this.expressApp).listen(port, host, callback);
this.handleConnectionEvents();
return this;
}
}, {
key: 'handleConnectionEvents',
value: function handleConnectionEvents() {
// TODO UNHANDLE
var activeConnections = this.activeConnections;
var httpServer = this.httpServer;
httpServer.on('connection', function onConnectionCallback(connection) {
var key = connectionToUnique(connection);
activeConnections[key] = {
connection: connection,
requests: 0
};
connection.once('close', function onCloseCallback() {
if (activeConnections[key]) {
delete activeConnections[key];
}
});
});
httpServer.on('request', function onRequestCallback(request, response) {
var key = connectionToUnique(request.connection);
var settings = activeConnections[key];
if (!settings) {
return;
}
settings.requests++;
response.once('finish', function onFinishCallback() {
var settings = activeConnections[key];
if (!settings) {
return;
}
settings.requests--;
});
});
}
}, {
key: '_destroyUnusedConnections',
value: function _destroyUnusedConnections() {
var activeConnections = this.activeConnections;
// remove unused connections
Object.keys(activeConnections).forEach(function destroyConnection(key) {
var settings = activeConnections[key];
if (settings.requests) {
return;
}
settings.connection.destroy();
delete activeConnections[key];
});
}
}, {
key: 'close',
value: function close(callback) {
var _this = this;
var activeConnections = this.activeConnections;
var httpServer = this.httpServer;
var options = this.options;
if (!httpServer) {
return callback(new Error('You need to listen first'));
}
log('Closing http server');
httpServer.close(function (err) {
if (err) {
return callback(err);
}
_this._httpServer = null;
// check current state of the connections
if (!Object.keys(activeConnections).length) {
log('There is no idle connections');
return callback();
}
log('Starting idle connection timeout ' + options.socket.idleTimeout);
setTimeout(function () {
Object.keys(activeConnections).forEach(function (key) {
var settings = activeConnections[key];
if (!settings) {
return;
}
log('Destroying connection: ' + key);
settings.connection.destroy();
});
log('All connections destroyed');
callback();
}, options.socket.idleTimeout);
});
// destroy connections without requests
this._destroyUnusedConnections();
return this;
}
}, {
key: '_prepareErrorHandler',
value: function _prepareErrorHandler() {
var app = this.expressApp;
app.use(_expressDomainMiddleware2.default);
}
}, {
key: '_prepareCompression',
value: function _prepareCompression() {
var app = this.expressApp;
var options = this.options;
if (!options.compression) {
return;
}
app.use((0, _compression2.default)(options.compression));
}
}, {
key: '_prepareLog',
value: function _prepareLog() {
var app = this.expressApp;
var options = this.options;
if (!options.log) {
return;
}
app.set('showStackError', true);
if (!options.morgan) {
return;
}
app.use((0, _morgan2.default)(options.morgan.format, options.morgan.options));
}
}, {
key: '_prepareEngine',
value: function _prepareEngine() {
var app = this.expressApp;
var options = this.options;
app.locals.pretty = true;
app.locals.cache = 'memory';
app.enable('jsonp callback');
app.engine('html', _consolidate2.default[options.view.engine]);
app.set('view engine', 'html');
app.set('views', options.root + '/views');
}
}, {
key: '_prepareHtml',
value: function _prepareHtml() {
var app = this.expressApp;
var options = this.options;
if (!options.powered) {
app.disable('x-powered-by');
}
if (options.responseTime) {
app.use((0, _responseTime2.default)(options.responseTime));
}
if (options.cors) {
app.use((0, _cors2.default)(options.cors));
}
if (options.request.timeout) {
app.use((0, _connectTimeout2.default)(options.request.timeout));
}
if (options.cookieParser) {
app.use((0, _cookieParser2.default)(options.cookieParser.secret, options.cookieParser.options));
}
if (options.bodyParser) {
for (var index = 0; index < options.bodyParser.length; index++) {
var bp = options.bodyParser[index];
app.use(_bodyParser2.default[bp.parse](bp.options));
}
}
if (options.methodOverride) {
app.use((0, _methodOverride2.default)(options.methodOverride.getter, options.methodOverride.options));
}
}
}, {
key: '_prepareVars',
value: function _prepareVars() {
var app = this.expressApp;
var server = this.server;
var options = this.options;
// add access to req from template
app.use(function setTemplateVariables(req, res, next) {
res.locals._req = req;
res.locals._production = process.env.NODE_ENV === 'production';
res.locals._build = options.server.build;
next();
});
// add access to req from template
app.use(function setBasicVariables(req, res, next) {
req.objects = {};
req.server = server;
req.models = server.models;
next();
});
}
}, {
key: '_prepareSession',
value: function _prepareSession() {
var app = this.expressApp;
var options = this.options;
if (!options.session) {
return;
}
// use session middleware
var sessionMiddleware = (0, _expressSession2.default)(options.session);
app.use(sessionMiddleware);
if (!options.sessionRecovery) {
return;
}
// session recovery
app.use(function sessionRecovery(req, res, next) {
var tries = options.sessionRecovery.tries;
function lookupSession(error) {
if (error) {
return next(error);
}
if (typeof req.session !== 'undefined') {
return next();
}
tries -= 1;
if (tries < 0) {
return next(new Error('Session is undefined'));
}
sessionMiddleware(req, res, lookupSession);
}
lookupSession();
});
}
}, {
key: '_prepareSecure',
value: function _prepareSecure() {
var app = this.expressApp;
var server = this.server;
var options = this.options;
app.use(server.secure.passport.initialize());
if (options.session) {
app.use(server.secure.passport.session());
}
}
}, {
key: '_prepareStatic',
value: function _prepareStatic() {
var app = this.expressApp;
var options = this.options;
if (options.flash) {
app.use((0, _connectFlash2.default)());
}
try {
if (options.favicon) {
log('FavIcon root: ' + options.favicon.root);
app.use((0, _serveFavicon2.default)(options.favicon.root, options.favicon.options));
}
if (options.robots) {
log('Robots root: ' + options.robots.root);
app.use((0, _robots2.default)(options.robots.root));
}
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
log(err.message);
}
if (options.css) {
log('CSS root: ' + options.css.root);
app.use(options.css.path, (0, _lessMiddleware2.default)(options.css.root, options.css.options));
}
if (options.static) {
if (!options.static.path || !options.static.root) {
throw new Error('Static path or root is undefined');
}
log('Static root: ' + options.static.root);
app.use(options.static.path, (0, _serveStatic2.default)(options.static.root, options.static.options));
}
}
}, {
key: '_prepareRouter',
value: function _prepareRouter() {
var app = this.expressApp;
var options = this.options;
var server = this.server;
// use server router
app.use(server.router.expressRouter);
// delete uploaded files
app.use(fileController.clearAfterError); // error must be first
app.use(fileController.clear);
// at the end add 500 and 404
app.use(options.page.notFound || pageController.notFound);
app.use(options.page.error || pageController.error);
}
}, {
key: '_prepareMiddleware',
value: function _prepareMiddleware(type) {
var app = this.expressApp;
var options = this.options;
var middlewares = options.middleware;
if (!middlewares || !middlewares[type]) {
return;
}
var middleware = middlewares[type];
if (typeof middleware === 'function') {
app.use(middleware);
} else if ((0, _isArray2.default)(middleware)) {
middleware.forEach(function (fn) {
app.use(fn);
});
}
}
}, {
key: 'options',
get: function get() {
return this._options;
}
}, {
key: 'activeConnections',
get: function get() {
return this._activeConnections;
}
}, {
key: 'server',
get: function get() {
return this._server;
}
}, {
key: 'httpServer',
get: function get() {
return this._httpServer;
}
}, {
key: 'expressApp',
get: function get() {
return this._expressApp;
}
}]);
return App;
}();
exports.default = App;
function prepareRequest(req) {
req.__defineGetter__('httpHost', function getHttpHost() {
var trustProxy = this.app.get('trust proxy');
var host = trustProxy && this.get('X-Forwarded-Host');
return host || this.get('Host');
});
req.__defineGetter__('port', function getPort() {
var host = this.httpHost;
if (!host) {
return null;
}
var parts = host.split(':');
return parts.length === 2 ? parseInt(parts[1], 10) : 80;
});
req.__defineGetter__('protocolHost', function getProtocolHost() {
return this.protocol + '://' + this.httpHost;
});
}
prepareRequest(_request2.default);