/*! * errorhandler * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014 Jonathan Ong * Copyright(c) 2014-2015 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var accepts = require('accepts') var escapeHtml = require('escape-html'); var fs = require('fs'); var util = require('util') /** * Module variables. */ var doubleSpaceGlobalRegExp = / /g var inspect = util.inspect var newLineGlobalRegExp = /\n/g var toString = Object.prototype.toString /** * Error handler: * * Development error handler, providing stack traces * and error message responses for requests accepting text, html, * or json. * * Text: * * By default, and when _text/plain_ is accepted a simple stack trace * or error message will be returned. * * JSON: * * When _application/json_ is accepted, connect will respond with * an object in the form of `{ "error": error }`. * * HTML: * * When accepted connect will output a nice html stack trace. * * @return {Function} * @api public */ exports = module.exports = function errorHandler(){ // get environment var env = process.env.NODE_ENV || 'development' return function errorHandler(err, req, res, next){ // respect err.status if (err.status) { res.statusCode = err.status } // default status code to 500 if (res.statusCode < 400) { res.statusCode = 500 } // write error to console if (env !== 'test') { console.error(stringify(err)) } // cannot actually respond if (res._header) { return req.socket.destroy() } // negotiate var accept = accepts(req) var type = accept.types('html', 'json', 'text') // Security header for content sniffing res.setHeader('X-Content-Type-Options', 'nosniff') // html if (type === 'html') { fs.readFile(__dirname + '/public/style.css', 'utf8', function(e, style){ if (e) return next(e); fs.readFile(__dirname + '/public/error.html', 'utf8', function(e, html){ if (e) return next(e); var str = stringify(err) var isInspect = !err.stack && String(err) === toString.call(err) var errorHtml = !isInspect ? escapeHtmlBlock(str.split('\n', 1)[0] || 'Error') : 'Error' var stack = !isInspect ? String(str).split('\n').slice(1) : [str] var stackHtml = stack .map(function (v) { return '
  • ' + escapeHtmlBlock(v) + '
  • ' }) .join('') var body = html .replace('{style}', style) .replace('{stack}', stackHtml) .replace('{title}', escapeHtml(exports.title)) .replace('{statusCode}', res.statusCode) .replace(/\{error\}/g, errorHtml) res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(body) }); }); // json } else if (type === 'json') { var error = { message: err.message, stack: err.stack }; for (var prop in err) error[prop] = err[prop]; var json = JSON.stringify({ error: error }); res.setHeader('Content-Type', 'application/json'); res.end(json); // plain text } else { res.setHeader('Content-Type', 'text/plain'); res.end(stringify(err)); } }; }; /** * Template title, framework authors may override this value. */ exports.title = 'Connect'; /** * Escape a block of HTML, preserving whitespace. * @api private */ function escapeHtmlBlock(str) { return escapeHtml(str) .replace(doubleSpaceGlobalRegExp, '  ') .replace(newLineGlobalRegExp, '
    ') } /** * Stringify a value. * @api private */ function stringify(val) { var stack = val.stack if (stack) { return String(stack) } var str = String(val) return str === toString.call(val) ? inspect(val) : str }