UNPKG

sails

Version:

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

258 lines (207 loc) 8.5 kB
/** * Dependencies */ var _ = require('@sailshq/lodash'); var chalk = require('chalk'); var STRINGFILE = require('sails-stringfile'); var Err = require('../../../errors/fatal'); var onRoute = require('./onRoute'); /** * Expose hook definition */ module.exports = function(sails) { return { defaults: {}, configure: function() { // Legacy (< v0.10) support for configured handlers if (typeof sails.config[500] === 'function') { sails.after('lifted', function() { STRINGFILE.logDeprecationNotice('sails.config[500]', STRINGFILE.get('links.docs.migrationGuide.responses'), sails.log.debug); sails.log.debug('sails.config[500] (i.e. `config/500.js`) has been superceded in Sails v0.10.'); sails.log.debug('Please define a "response" instead. (i.e. api/responses/serverError.js)'); sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)'); sails.log.debug('If you\'d like to use the default handler, just remove this configuration option.'); }); } if (typeof sails.config[404] === 'function') { sails.after('lifted', function() { STRINGFILE.logDeprecationNotice('sails.config[404]', STRINGFILE.get('links.docs.migrationGuide.responses'), sails.log.debug); sails.log.debug('Please define a "response" instead. (i.e. api/responses/notFound.js)'); sails.log.debug('Your old handler is being ignored. (the format has been upgraded in v0.10)'); sails.log.debug('If you\'d like to use the default handler, just remove this configuration option.'); }); } }, /** * When this hook is loaded... */ initialize: function(cb) { // Register route syntax that allows explicit routes // to be bound directly to custom responses by name. // (e.g. {response: 'foo'}) sails.on('route:typeUnknown', onRoute(sails)); cb(); }, /** * Fetch relevant modules, exposing them on `sails` subglobal if necessary, */ loadModules: function(cb) { var hook = this; sails.log.silly('Loading runtime custom response definitions...'); sails.modules.loadResponses(function loadedRuntimeErrorModules(err, responseDefs) { if (err) { return cb(err); } // Check that none of the custom responses provided from userland collie with // reserved response methods/properties. // // Note: this could be made more flexible in the future-- I've found it to be // helpful to sometimes override res.view() in apps. That said, in those circumstances, // I've been able to accomplish this by manually overriding res.view in a custom hook. // That said, if that won't work for your use case, please let me know (tweet @mikermcneil). var reservedResKeys = [ 'view', 'status', 'set', 'get', 'cookie', 'clearCookie', 'redirect', 'location', 'charset', 'send', 'json', 'jsonp', 'type', 'format', 'attachment', 'sendfile', 'download', 'links', 'locals', 'render' ]; _.each(responseDefs, function (responseDef, customResponseKey) { if ( _.contains(reservedResKeys, customResponseKey) ) { Err.invalidCustomResponse(customResponseKey); } }); // Mix in the built-in default definitions for custom responses. _.defaults(responseDefs, { ok: require('./defaults/ok'), negotiate: require('./defaults/negotiate'), notFound: require('./defaults/notFound'), serverError: require('./defaults/serverError'), forbidden: require('./defaults/forbidden'), badRequest: require('./defaults/badRequest') }); // Expose combined custom/default response method definitions on the hook. // (e.g. `serverError`, `notFound`, `ok`, etc.) // TODO: use this instead of exposing as "middleware", since that's confusing naming. // Register blueprint actions as middleware of this hook. hook.middleware = responseDefs; return cb(); }); }, /** * Shadow route bindings * @type {Object} */ routes: { before: { /** * Add custom response methods to `res`. * * @param {Request} req * @param {Response} res * @param {Function} next * @api private */ 'all /*': function addResponseMethods(req, res, next) { // Attach res.jsonx to `res` object _mixinJsonxMethod(req, res); // Attach custom responses to `res` object // Provide access to `req` and `res` in each of their `this` contexts. _.each(sails.middleware.responses, function eachMethod(responseFn, name) { res[name] = responseFn.bind({ req: req, res: res }); }); // Proceed! return next(); } } } }; }; /** * [_mixinJsonxMethod description] * @param {[type]} req [description] * @param {[type]} res [description] * @return {[type]} [description] */ function _mixinJsonxMethod(req, res) { function _stringifyJsonxError(err) { var plainObject = {}; Object.getOwnPropertyNames(err).forEach(function (key) { plainObject[key] = err[key]; }); return JSON.stringify(plainObject); } function _handleJsonxError(err){ var serializedErr; var jsonSerializedErr; try { serializedErr = _stringifyJsonxError(err); jsonSerializedErr = JSON.parse(serializedErr); if (!jsonSerializedErr.stack || !jsonSerializedErr.message) { jsonSerializedErr.message = err.message; jsonSerializedErr.stack = err.stack; } return jsonSerializedErr; } catch (unusedErr){ return {name: err.name, message: err.message, stack: err.stack}; } } /** * res.jsonx(data) * * Serve JSON (and allow JSONP if enabled in `req.options`) * * @param {Object} data * * - - - - - - - - - - - - - - - - - - * WARNING: THIS IS DEPRECATED! * - - - - - - - - - - - - - - - - - - */ res.jsonx = res.jsonx || function jsonx (data){ var caller = jsonx.caller && jsonx.caller.name; // Get easy access to Sails app instance. var sails = req._sails; // Log a deprecation notice. sails.log.debug('***********************************************************************************'); sails.log.debug('`res.jsonx()` is deprecated in Sails v1.0 and will be removed in a future release.'); sails.log.debug(chalk.bold('Any files in `api/responses/` that haven\'t been customized can simply be removed.')); sails.log.debug(chalk.gray('Otherwise see http://sailsjs.com/upgrading for options to replace `res.jsonx()`.')); if (caller) { sails.log.debug(chalk.gray('(jsonx was called from `' + caller + '`)')); } sails.log.debug('***********************************************************************************'); // Send conventional status message if no data was provided // (see http://expressjs.com/en/api.html#res.send) if (_.isUndefined(data)) { return res.status(res.statusCode).send(); } else if (typeof data !== 'object') { // (note that this guard includes arrays, functions, and even `null`) return res.send(data); } // When responding with an Error instance, if it's going to get sringified into // a dictionary with no `.stack` or `.message` properties, add them in. if (data instanceof Error) { data = _handleJsonxError(data); } if ( req.options.jsonp && !req.isSocket ) { return res.jsonp(data); } else { return res.json(data); } };//ƒ }//ƒ // Note for later // We could differentiate between 500 (generic error message) // and 504 (gateway did not receive response from upstream server) which could describe an IO problem // This is worth having a think about, since there are 2 fundamentally different kinds of "server errors": // (a) An infrastructural issue, or 504 (e.g. MySQL database randomly crashed or Twitter is down) // (b) Unexpected bug in app code, or 500 (e.g. `req.session.user.id`, but `req.session.user` doesn't exist) // // See the Sails project roadmap (sailsjs.com/roadmap) for future plans to better account for this.