123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- /*!
- * 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
- */
- 'use strict'
- /**
- * Module dependencies.
- * @private
- */
- var accepts = require('accepts')
- var escapeHtml = require('escape-html')
- var fs = require('fs')
- var path = require('path')
- var util = require('util')
- /**
- * Module variables.
- * @private
- */
- var DOUBLE_SPACE_REGEXP = /\x20{2}/g
- var NEW_LINE_REGEXP = /\n/g
- var STYLESHEET = fs.readFileSync(path.join(__dirname, '/public/style.css'), 'utf8')
- var TEMPLATE = fs.readFileSync(path.join(__dirname, '/public/error.html'), 'utf8')
- var inspect = util.inspect
- var toString = Object.prototype.toString
- /* istanbul ignore next */
- var defer = typeof setImmediate === 'function'
- ? setImmediate
- : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
- /**
- * 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 (options) {
- // get environment
- var env = process.env.NODE_ENV || 'development'
- // get options
- var opts = options || {}
- // get log option
- var log = opts.log === undefined
- ? env !== 'test'
- : opts.log
- if (typeof log !== 'function' && typeof log !== 'boolean') {
- throw new TypeError('option log must be function or boolean')
- }
- // default logging using console.error
- if (log === true) {
- log = logerror
- }
- return function errorHandler (err, req, res, next) {
- // respect err.statusCode
- if (err.statusCode) {
- res.statusCode = err.statusCode
- }
- // respect err.status
- if (err.status) {
- res.statusCode = err.status
- }
- // default status code to 500
- if (res.statusCode < 400) {
- res.statusCode = 500
- }
- // log the error
- var str = stringify(err)
- if (log) {
- defer(log, err, str, req, res)
- }
- // cannot actually respond
- if (res._header) {
- return req.socket.destroy()
- }
- // negotiate
- var accept = accepts(req)
- var type = accept.type('html', 'json', 'text')
- // Security header for content sniffing
- res.setHeader('X-Content-Type-Options', 'nosniff')
- // html
- if (type === 'html') {
- 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 '<li>' + escapeHtmlBlock(v) + '</li>' })
- .join('')
- var body = TEMPLATE
- .replace('{style}', STYLESHEET)
- .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 }, null, 2)
- res.setHeader('Content-Type', 'application/json; charset=utf-8')
- res.end(json)
- // plain text
- } else {
- res.setHeader('Content-Type', 'text/plain; charset=utf-8')
- res.end(str)
- }
- }
- }
- /**
- * 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(DOUBLE_SPACE_REGEXP, ' ')
- .replace(NEW_LINE_REGEXP, '<br>')
- }
- /**
- * 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
- }
- /**
- * Log error to console.
- * @api private
- */
- function logerror (err, str) {
- console.error(str || err.stack)
- }
|