UNPKG

sails

Version:

API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)

443 lines (375 loc) 21.9 kB
/** * Module dependencies. */ var _ = require('@sailshq/lodash'); var getConfiguredHttpMiddlewareFns = require('./get-configured-http-middleware-fns'); module.exports = function(sails) { /** * initialize() * * Configure the encapsulated Express server that will be used to serve actual HTTP requests */ return function initialize(cb) { // Before proceeding, wait for the session hook-- // or if it is disabled, then go ahead and proceed // (but change the middleware order config so we don't // attempt to handle sessions). (function _waitForSessionHookIfApplicable(next){ // If the session hook is available... if (sails.hooks.session) { // Then wait until after session hook has initialized // so that the proper session config is available for use // in the built-in "session" middleware. sails.after('hook:session:loaded', function () { return next(); }); } // Otherwise, the session hook is NOT available. else { // Then, if it present, rip out "session" from the configured // middleware order so we don't try to use the built-in session // middleware. _.pull(sails.config.http.middleware.order, 'session'); return next(); } })(function _afterLoadingSessionHookIfApplicable(err) { if (err) { return cb(err); } try { // Required to be here due to dynamic NODE_ENV settings via command line args // (i.e. if we `require` this above w/ everything else, the NODE_ENV might not be set properly yet) var express = require('express'); // Create express app instance. var expressApp = express(); // Create a new router object to handle routes bound in Sails apps. // We do this because Express doesn't provide a public API to return // its built-in router (expressApp._router), and we need direct access // to the router object in order to do `unbind` and `reset`. // // Note that we don't add this router to the express app until right // before its time to add any "post-router" middleware (i.e. after // the "ready" event is received). var internalExpressRouter = express.Router(); // Expose express app as `sails.hooks.http.app` for use in other files // in this hook, and in other core hooks. sails.hooks.http.app = expressApp; // Disable the default powered-by header (required by Express 3.x). expressApp.disable('x-powered-by'); // Determine whether or not to create an HTTPS server var isUsingSSL = (sails.config.ssl === true) || (sails.config.ssl.key && sails.config.ssl.cert) || sails.config.ssl.pfx; // Merge SSL into server options var serverOptions = sails.config.http.serverOptions || {}; _.extend(serverOptions, sails.config.ssl); // Lodash 3's _.merge attempts to transform buffers into arrays; // so if we detect an array, then transform it back into a buffer. _.each(['key', 'cert', 'pfx'], function _eachSSLOption(sslOption) { if (_.isArray(serverOptions[sslOption])) { serverOptions[sslOption] = Buffer.from(serverOptions[sslOption]); } }); // ^^^ The following is probably not relevant anymore, because `_.merge()` // is not being used above. Leaving for compatibility reasons (just to be safe). // Get the appropriate server creation method for the protocol var createServer = isUsingSSL ? require('https').createServer : require('http').createServer; // Use serverOptions if they were specified // Manually create http server using Express app instance if(process.version.match(/^v(\d+\.\d+)/)[1] < 10.12){ if (sails.config.http.serverOptions || isUsingSSL) { sails.hooks.http.server = createServer(serverOptions, expressApp); } else { sails.hooks.http.server = createServer(expressApp); } } else { sails.hooks.http.server = createServer(serverOptions, expressApp); } // Keep track of all openTcpConnections that come in, // so we can destroy them later if we want to. var openTcpConnections = {}; // Listen for `connection` events on the raw HTTP server. sails.hooks.http.server.on('connection', function _onNewTCPConnection(tcpConnection) { var key = tcpConnection.remoteAddress + ':' + tcpConnection.remotePort; openTcpConnections[key] = tcpConnection; tcpConnection.on('close', function() { delete openTcpConnections[key]; }); }); // Create a `destroy` method we can use to do a hard shutdown of the server. sails.hooks.http.destroy = function(done) { sails.log.verbose('Destroying http server...'); sails.hooks.http.server.close(done); // FUTURE: consider moving this loop ABOVE `sails.hooks.http.server.close(done)` // for clarity (since at this point we've passed control via `done`) for (var key in openTcpConnections) { openTcpConnections[key].destroy(); } };//</define `sails.hooks.http.destroy()`> // Configure views if hook enabled if (sails.hooks.views) { // FUTURE: explore handling this differently to avoid potential // timing issues with view engine configuration sails.after('hook:views:loaded', function() { var View = require('./view'); // Use View subclass to allow case-insensitive view lookups expressApp.set('view', View); // Set up location of server-side views and their engine expressApp.set('views', sails.config.paths.views); // Teach Express how to render templates w/ our configured view extension expressApp.engine(sails.config.views.extension, sails.hooks.views._renderFn); // Set default view engine sails.log.silly('Setting default Express view engine to ' + sails.config.views.extension + '...'); expressApp.set('view engine', sails.config.views.extension); });//</ after hook:views:loaded > }//>- // In non-production environments, format `res.json()` output nicely. // > https://expressjs.com/en/4x/api.html#app.set if (process.env.NODE_ENV !== 'production') { expressApp.set('json spaces', 2); } // Set Express "trust proxy" if appropriate. // > https://expressjs.com/en/guide/behind-proxies.html if (sails.config.http.trustProxy) { expressApp.set('trust proxy', sails.config.http.trustProxy); } // Whenever Sails binds a route, bind it to the internal Express router. sails.on('router:bind', function(route) { // Clone the route so that if a route handler messes with the options, the changes // don't get persisted and used in subsequent requests. route = _.cloneDeep(route); internalExpressRouter[route.verb || 'all'](route.path || '/*', route.target); }); // Whenever Sails unbinds a route, remove it from the internal Express router. sails.on('router:unbind', function(routeToRemove) { // Remove any route which matches the path and verb of the argument _.remove(internalExpressRouter.stack, function(layer) { return (layer.route.path === routeToRemove.path && layer.route.methods[routeToRemove.verb] === true); }); }); // Whenever Sails resets its router, clear out the internal Express router. sails.on('router:reset', function() { internalExpressRouter.stack = []; }); // Now expressApp.use() an initial piece of middleware to bind // _core, mandatory properties_ to the incoming `req`. // This middleware cannot be disabled in userland configuration-- // and that's done on purpose. expressApp.use(function _exposeSailsOnReq (req, res, next){ // Expose req._sails on incoming HTTP request instances. // // This is also handled separately for virtual requests in `lib/router/`: // (see https://github.com/balderdashy/sails/pull/3599#issuecomment-195665040) req._sails = sails; // Wrap `req.param()` in a shim that normalizes the behavior of `req.param('length')`. // (see https://github.com/balderdashy/sails/issues/3738#issue-156095626) var origReqParam = req.param; req.param = function getValForParam (name){ if (name === 'length') { // If `req.params.length` is a string, instead of a number, then we know this request // must have matched a route address like `/foo/bar/:length/baz`, so in that case, we'll // allow `req.param('length')` to return the runtime value of `length` as a string. if (_.isString(req.params.length)) { return req.params.length; } else if (!_.isArray(req.body) && _.isObject(req.body) && !_.isUndefined(req.body.length) && !_.isNull(req.body.length)) { // > In future versions of Sails, this shim will likely be modified to allow the `null` literal to be received // > as a value for a body parameter and accessed in `req.param()`. // > (This is because `null` and `undefined` are distinct and lossless when serializing and deserializing to // > and from JSON-- so it's really specifically for standard JSON response bodies.) // > // > However, this is a departure from the behavior of Express, and a breaking change- so it will // > not happen until the release of Sails v1 (or possibly in a pre v1.0 minor version.) return req.body.length; } else if (_.isObject(req.query) && !_.isUndefined(req.query.length) && !_.isNull(req.query.length)) { return req.query.length; } else { return undefined; } } return origReqParam.apply(req, Array.prototype.slice.call(arguments)); }; return next(); }); // If there's a `handleBodyParserError` middleware in the order, and there isn't // a custom definition for it (i.e. it's trying to use the default) log a // deprecation warning. if (_.contains(sails.config.http.middleware.order, 'handleBodyParserError') && !_.isFunction(sails.config.http.middleware.handleBodyParserError)) { sails.log.debug('The `handleBodyParserError` middleware has been removed in Sails 1.0.'); sails.log.debug('To avoid this message, remove `handleBodyParserError` from '); sails.log.debug('the `order` array in `config/http.js`.'); sails.log.debug('See http://sailsjs.com/upgrading for more info.\n'); } // If there's a `methodOverride` middleware in the order, and there isn't // a custom definition for it (i.e. it's trying to use the default) log a // deprecation warning. if (_.contains(sails.config.http.middleware.order, 'methodOverride') && !_.isFunction(sails.config.http.middleware.methodOverride)) { sails.log.debug('The `methodOverride` middleware has been removed in Sails 1.0.'); sails.log.debug('To avoid this message, remove `methodOverride` from '); sails.log.debug('the `order` array in `config/http.js`.'); sails.log.debug('See http://sailsjs.com/upgrading for more info.\n'); } // Ignore explicit declarations of `startRequestTimer`, `404` and `500` middleware. var removedHttpMiddleware = _.remove(sails.config.http.middleware.order, function(middleware) { return _.contains(['handleBodyParserError', 'startRequestTimer', '404', '500'], middleware); }); // Warn about an explicit `startRequestTimer` in the order, or a custom middleware implementation of it. if (_.contains(removedHttpMiddleware, 'startRequestTimer') || sails.config.http.middleware.startRequestTimer) { sails.log.debug('The `startRequestTimer` middleware is added to your app automatically in Sails 1.0.'); if (sails.config.http.middleware.startRequestTimer) { sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware.startRequestTimer)'); } else { sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)'); } sails.log.debug('See http://sailsjs.com/documentation/reference/request-req/req-start-time for more info.\n'); } // Warn about an explicit `404` in the order, or a custom middleware implementation of it. if (_.contains(removedHttpMiddleware, '404') || sails.config.http.middleware['404']) { sails.log.debug('The `404` middleware is added to your app automatically in Sails 1.0.'); if (sails.config.http.middleware['404']) { sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware[\'404\']`)'); } else { sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)'); } sails.log.debug('If you wish to customize the 404 functionality for your app, you can'); sails.log.debug('do so by creating a custom `notFound` response as `api/responses/notFound.js`.'); sails.log.debug('See http://sailsjs.com/documentation/concepts/custom-responses for more info.\n'); } // Warn about an explicit `500` in the order. if (_.contains(removedHttpMiddleware, '500') || sails.config.http.middleware['500']) { sails.log.debug('The `500` middleware is added to your app automatically in Sails 1.0.'); if (sails.config.http.middleware['500']) { sails.log.debug('(ignoring custom implementation in `sails.config.http.middleware[\'500\']`)'); } else { sails.log.debug('(ignoring entry in the `sails.config.http.middleware.order` list)'); } sails.log.debug('If you wish to customize the 500 functionality for your app, you can'); sails.log.debug('do so by creating a custom `serverError` response as `api/responses/serverError.js`.'); sails.log.debug('See http://sailsjs.com/documentation/concepts/custom-responses for more info.\n'); } // Then build a dictionary of configured middleware functions, including // built-in middleware as well as any middleware provided in // `sails.config.http.middleware`. var configuredHttpMiddlewareFns = getConfiguredHttpMiddlewareFns(expressApp, sails); // Add in the middleware to record the request start time. expressApp.use(function startRequestTimer(req, res, next) { req._startTime = new Date(); next(); }); // Split the middleware order into "pre-router" and "post-router" middleware. // The internal "startRequestTimer" always comes first. var preRouterMiddleware = []; var postRouterMiddleware = null; _.each(sails.config.http.middleware.order, function(middlewareKey) { if (middlewareKey === 'router') { postRouterMiddleware = []; } else if ( _.isArray(postRouterMiddleware) ) { postRouterMiddleware.push(middlewareKey); } else { preRouterMiddleware.push(middlewareKey); } }); // If a custom `loadMiddleware` function was configured, then call it to "use" // the configured middleware (instead of doing it automatically with the more // modern `sails.config.http.middleware.order` configuration). // // This is primarily for backwards compatibility for the undocumented // `express.loadMiddleware` config that is still in use in legacy apps // from the 2013-early 2014 time frame. // // It is no longer relevant in most cases thanks to `sails.config.http.middleware`, // and may be removed in an upcoming release. if (sails.config.http.loadMiddleware) { sails.config.http.loadMiddleware(expressApp, configuredHttpMiddlewareFns, sails); } // Otherwise (i.e. the normal case) we `.use()` each of the configured // middleware functions in the configured order (`sails.config.http.middleware.order`). else { _.each(preRouterMiddleware, function (middlewareKey) { // `$custom` is a special entry in the middleware order config that exists // purely for compatibility. When procesing `$custom`, we check to see if // `sails.config.http.customMiddleware`, was provided and if so, call it // with the express app instance as an argument (rather than calling // `sails.config.http.middleware.$custom`). // If `customMiddleware` is not being used, we just ignore `$custom` altogether. if (middlewareKey === '$custom') { if (sails.config.http.customMiddleware) { // Allows for injecting a custom function to attach middleware. // (This is here for compatibility, and for situations where the raw Express // app instance is necessary for configuring middleware). sails.config.http.customMiddleware(expressApp); } // Either way, bail at this point (we don't want to do anything further with $custom) return; } // Look up the referenced middleware function. var referencedMwr = configuredHttpMiddlewareFns[middlewareKey]; // If a middleware fn by this name is not configured (i.e. `undefined`), // then skip this entry & write a verbose log message. if (_.isUndefined(referencedMwr)) { sails.log.verbose('An entry (`%s`) in `sails.config.http.middleware.order` references an unrecognized middleware function-- that is, it was not provided as a key in the `sails.config.http.middleware` dictionary. Skipping...', middlewareKey); return; } // On the other hand, if the referenced middleware appears to be disabled // _on purpose_, or because _it is not compatible_, then just skip it and // don't log anything. (i.e. it is `null` or `false`) if (!referencedMwr) { return; } // Otherwise, we're good to go, so go ahead and use the referenced // middleware function. expressApp.use(referencedMwr); });//</each item in `sails.config.http.middleware.order`> } // www, favicon, 404 and 500 middleware should be attached at the very end. // In previous Sails versions (that used Express <4), the router was added // as part of the middleware stack in sails.config.http.middleware.order, // so we could just put these 4 middleware after `router` in that list. // In Express 4, the router is built in, so we have to wait until the // server is fully initialized and then add the post-router middleware after. sails.once('ready', function addPostRouterMiddleware() { expressApp.use(internalExpressRouter); _.each(postRouterMiddleware, function (middlewareKey) { // Look up the referenced middleware function. var referencedMwr = configuredHttpMiddlewareFns[middlewareKey]; // If a middleware fn by this name is not configured (i.e. `undefined`), // then skip this entry & write a verbose log message. if (_.isUndefined(referencedMwr)) { sails.log.verbose('An entry (`%s`) in `sails.config.http.middleware.order` references an unrecognized middleware function-- that is, it was not provided as a key in the `sails.config.http.middleware` dictionary. Skipping...', middlewareKey); return; } // On the other hand, if the referenced middleware appears to be disabled // _on purpose_, or because _it is not compatible_, then just skip it and // don't log anything. (i.e. it is `null` or `false`) if (!referencedMwr) { return; } // Otherwise, we're good to go, so go ahead and use the referenced // middleware function. expressApp.use(referencedMwr); }); // Add the default 404 middleware. expressApp.use(function handleUnmatchedRequest(req, res) { // Explicitly ignore error arg to avoid inadvertently // turning this into an error handler sails.emit('router:request:404', req, res); }); // Add the default 500 middleware. expressApp.use(function handleError(err, req, res, next) {// eslint-disable-line no-unused-vars // Note that we _need_ all four arguments in order for this function // to have special meaning as an error handler (i.e. to Express) sails.emit('router:request:500', err, req, res); }); }); // All done! return cb(); } catch (e) { return cb(e); } // ^ for improving the readability of any bugs in the above code, // or for unhandled errors from our dependencies, or for unexpected // configuration. });//</_afterLoadingSessionHookIfApplicable> };//</initialize()> };