diff --git a/core/frontend/web/middleware/error-handler.js b/core/frontend/web/middleware/error-handler.js new file mode 100644 index 0000000000..220c6a134d --- /dev/null +++ b/core/frontend/web/middleware/error-handler.js @@ -0,0 +1,93 @@ +const hbs = require('express-hbs'); +const _ = require('lodash'); +const tpl = require('@tryghost/tpl'); +const sentry = require('../../../shared/sentry'); + +const config = require('../../../shared/config'); +const helpers = require('../../services/routing/helpers'); + +// @TODO: make this properly shared code +const shared = require('../../../server/web/shared/middleware/error-handler'); + +const messages = { + oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.', + encounteredError: 'Encountered the error: ', + whilstTryingToRender: 'whilst trying to render an error page for the error: ' +}; + +const escapeExpression = hbs.Utils.escapeExpression; + +/** + * This is a bare minimum setup, which allows us to render the error page + * It uses the {{asset}} helper, and nothing more + */ +const createHbsEngine = () => { + const engine = hbs.create(); + engine.registerHelper('asset', require('../../helpers/asset')); + + return engine.express4(); +}; + +const errorFallbackMessage = err => `

${tpl(messages.oopsErrorTemplateHasError)}

+

${tpl(messages.encounteredError)}

+
${escapeExpression(err.message || err)}
+

${tpl(messages.whilstTryingToRender)}

+ ${err.statusCode}
${escapeExpression(err.message || err)}
`; + +const themeErrorRenderer = (err, req, res, next) => { + // If the error code is explicitly set to STATIC_FILE_NOT_FOUND, + // Skip trying to render an HTML error, and move on to the basic error renderer + // We do this because customised 404 templates could reference the image that's missing + // A better long term solution might be to do this based on extension + if (err.code === 'STATIC_FILE_NOT_FOUND') { + return next(err); + } + + // Renderer begin + // Format Data + const data = { + message: err.message, + // @deprecated Remove in Ghost 5.0 + code: err.statusCode, + statusCode: err.statusCode, + errorDetails: err.errorDetails || [] + }; + + // Template + // @TODO: very dirty !!!!!! + helpers.templates.setTemplate(req, res); + + // It can be that something went wrong with the theme or otherwise loading handlebars + // This ensures that no matter what res.render will work here + // @TODO: split the error handler for assets, admin & theme to refactor this away + if (_.isEmpty(req.app.engines)) { + res._template = 'error'; + req.app.engine('hbs', createHbsEngine()); + req.app.set('view engine', 'hbs'); + req.app.set('views', config.get('paths').defaultViews); + } + + // @TODO use renderer here?! + // Render Call - featuring an error handler for what happens if rendering fails + res.render(res._template, data, (_err, html) => { + if (!_err) { + return res.send(html); + } + + // re-attach new error e.g. error template has syntax error or misusage + req.err = _err; + + // And then try to explain things to the user... + // Cheat and output the error using handlebars escapeExpression + return res.status(500).send(errorFallbackMessage(_err)); + }); +}; + +module.exports.handleThemeResponse = [ + // Make sure the error can be served + shared.prepareError, + // Handle the error in Sentry + sentry.errorHandler, + // Render the error using theme template + themeErrorRenderer +]; diff --git a/core/frontend/web/middleware/index.js b/core/frontend/web/middleware/index.js index 2ed0e79ba1..0d76186bfd 100644 --- a/core/frontend/web/middleware/index.js +++ b/core/frontend/web/middleware/index.js @@ -1,4 +1,5 @@ module.exports = { + errorHandler: require('./error-handler'), handleImageSizes: require('./handle-image-sizes'), redirectGhostToAdmin: require('./redirect-ghost-to-admin'), serveFavicon: require('./serve-favicon'), diff --git a/core/frontend/web/site.js b/core/frontend/web/site.js index 8df7956eb6..36549343b2 100644 --- a/core/frontend/web/site.js +++ b/core/frontend/web/site.js @@ -184,7 +184,7 @@ module.exports = function setupSiteApp(options = {}) { app.setupErrorHandling(siteApp); } }); - siteApp.use(shared.middleware.errorHandler.handleThemeResponse); + siteApp.use(mw.errorHandler.handleThemeResponse); debug('Site setup end'); diff --git a/core/server/web/shared/middleware/error-handler.js b/core/server/web/shared/middleware/error-handler.js index 520608f997..db74f5112d 100644 --- a/core/server/web/shared/middleware/error-handler.js +++ b/core/server/web/shared/middleware/error-handler.js @@ -1,16 +1,10 @@ -const hbs = require('express-hbs'); const _ = require('lodash'); const debug = require('@tryghost/debug')('error-handler'); const errors = require('@tryghost/errors'); const tpl = require('@tryghost/tpl'); -const config = require('../../../../shared/config'); -const helpers = require('../../../../frontend/services/routing/helpers'); const sentry = require('../../../../shared/sentry'); const messages = { - oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.', - encounteredError: 'Encountered the error: ', - whilstTryingToRender: 'whilst trying to render an error page for the error: ', pageNotFound: 'Page not found', resourceNotFound: 'Resource not found', actions: { @@ -45,19 +39,6 @@ const messages = { } }; -const escapeExpression = hbs.Utils.escapeExpression; - -/** - * This is a bare minimum setup, which allows us to render the error page - * It uses the {{asset}} helper, and nothing more - */ -const createHbsEngine = () => { - const engine = hbs.create(); - engine.registerHelper('asset', require('../../../../frontend/helpers/asset')); - - return engine.express4(); -}; - const updateStack = (err) => { let stackbits = err.stack.split(/\n/g); @@ -87,7 +68,7 @@ const updateStack = (err) => { /** * Get an error ready to be shown the the user */ -const prepareError = (err, req, res, next) => { +module.exports.prepareError = (err, req, res, next) => { debug(err); if (Array.isArray(err)) { @@ -201,61 +182,6 @@ const prepareUserMessage = (err, res) => { return userError; }; -const errorFallbackMessage = err => `

${tpl(messages.oopsErrorTemplateHasError)}

-

${tpl(messages.encounteredError)}

-
${escapeExpression(err.message || err)}
-

${tpl(messages.whilstTryingToRender)}

- ${err.statusCode}
${escapeExpression(err.message || err)}
`; - -const themeErrorRenderer = (err, req, res, next) => { - // If the error code is explicitly set to STATIC_FILE_NOT_FOUND, - // Skip trying to render an HTML error, and move on to the basic error renderer - // We do this because customised 404 templates could reference the image that's missing - // A better long term solution might be to do this based on extension - if (err.code === 'STATIC_FILE_NOT_FOUND') { - return next(err); - } - - // Renderer begin - // Format Data - const data = { - message: err.message, - // @deprecated Remove in Ghost 5.0 - code: err.statusCode, - statusCode: err.statusCode, - errorDetails: err.errorDetails || [] - }; - - // Template - // @TODO: very dirty !!!!!! - helpers.templates.setTemplate(req, res); - - // It can be that something went wrong with the theme or otherwise loading handlebars - // This ensures that no matter what res.render will work here - // @TODO: split the error handler for assets, admin & theme to refactor this away - if (_.isEmpty(req.app.engines)) { - res._template = 'error'; - req.app.engine('hbs', createHbsEngine()); - req.app.set('view engine', 'hbs'); - req.app.set('views', config.get('paths').defaultViews); - } - - // @TODO use renderer here?! - // Render Call - featuring an error handler for what happens if rendering fails - res.render(res._template, data, (_err, html) => { - if (!_err) { - return res.send(html); - } - - // re-attach new error e.g. error template has syntax error or misusage - req.err = _err; - - // And then try to explain things to the user... - // Cheat and output the error using handlebars escapeExpression - return res.status(500).send(errorFallbackMessage(_err)); - }); -}; - module.exports.resourceNotFound = (req, res, next) => { next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)})); }; @@ -266,7 +192,7 @@ module.exports.pageNotFound = (req, res, next) => { module.exports.handleJSONResponse = [ // Make sure the error can be served - prepareError, + module.exports.prepareError, // Handle the error in Sentry sentry.errorHandler, // Render the error using JSON format @@ -275,7 +201,7 @@ module.exports.handleJSONResponse = [ module.exports.handleJSONResponseV2 = [ // Make sure the error can be served - prepareError, + module.exports.prepareError, // Handle the error in Sentry sentry.errorHandler, // Render the error using JSON format @@ -284,16 +210,7 @@ module.exports.handleJSONResponseV2 = [ module.exports.handleHTMLResponse = [ // Make sure the error can be served - prepareError, + module.exports.prepareError, // Handle the error in Sentry sentry.errorHandler ]; - -module.exports.handleThemeResponse = [ - // Make sure the error can be served - prepareError, - // Handle the error in Sentry - sentry.errorHandler, - // Render the error using theme template - themeErrorRenderer -];