3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2014 Jonathan Ong
6 * Copyright(c) 2014-2015 Douglas Christopher Wilson
13 * Module dependencies.
17 var accepts = require('accepts')
18 var escapeHtml = require('escape-html');
19 var fs = require('fs');
20 var util = require('util')
27 var doubleSpaceGlobalRegExp = / /g
28 var inspect = util.inspect
29 var newLineGlobalRegExp = /\n/g
30 var toString = Object.prototype.toString
32 /* istanbul ignore next */
33 var defer = typeof setImmediate === 'function'
35 : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
40 * Development error handler, providing stack traces
41 * and error message responses for requests accepting text, html,
46 * By default, and when _text/plain_ is accepted a simple stack trace
47 * or error message will be returned.
51 * When _application/json_ is accepted, connect will respond with
52 * an object in the form of `{ "error": error }`.
56 * When accepted connect will output a nice html stack trace.
62 exports = module.exports = function errorHandler(options) {
64 var env = process.env.NODE_ENV || 'development'
67 var opts = options || {}
70 var log = opts.log === undefined
74 if (typeof log !== 'function' && typeof log !== 'boolean') {
75 throw new TypeError('option log must be function or boolean')
78 // default logging using console.error
83 return function errorHandler(err, req, res, next){
84 // respect err.statusCode
86 res.statusCode = err.statusCode
91 res.statusCode = err.status
94 // default status code to 500
95 if (res.statusCode < 400) {
100 var str = stringify(err)
102 defer(log, err, str, req, res)
105 // cannot actually respond
107 return req.socket.destroy()
111 var accept = accepts(req)
112 var type = accept.type('html', 'json', 'text')
114 // Security header for content sniffing
115 res.setHeader('X-Content-Type-Options', 'nosniff')
118 if (type === 'html') {
119 fs.readFile(__dirname + '/public/style.css', 'utf8', function(e, style){
120 if (e) return next(e);
121 fs.readFile(__dirname + '/public/error.html', 'utf8', function(e, html){
122 if (e) return next(e);
123 var isInspect = !err.stack && String(err) === toString.call(err)
124 var errorHtml = !isInspect
125 ? escapeHtmlBlock(str.split('\n', 1)[0] || 'Error')
127 var stack = !isInspect
128 ? String(str).split('\n').slice(1)
130 var stackHtml = stack
131 .map(function (v) { return '<li>' + escapeHtmlBlock(v) + '</li>' })
134 .replace('{style}', style)
135 .replace('{stack}', stackHtml)
136 .replace('{title}', escapeHtml(exports.title))
137 .replace('{statusCode}', res.statusCode)
138 .replace(/\{error\}/g, errorHtml)
139 res.setHeader('Content-Type', 'text/html; charset=utf-8')
144 } else if (type === 'json') {
145 var error = { message: err.message, stack: err.stack };
146 for (var prop in err) error[prop] = err[prop];
147 var json = JSON.stringify({ error: error });
148 res.setHeader('Content-Type', 'application/json; charset=utf-8')
152 res.setHeader('Content-Type', 'text/plain; charset=utf-8')
159 * Template title, framework authors may override this value.
162 exports.title = 'Connect';
165 * Escape a block of HTML, preserving whitespace.
169 function escapeHtmlBlock(str) {
170 return escapeHtml(str)
171 .replace(doubleSpaceGlobalRegExp, ' ')
172 .replace(newLineGlobalRegExp, '<br>')
180 function stringify(val) {
181 var stack = val.stack
187 var str = String(val)
189 return str === toString.call(val)
195 * Log error to console.
199 function logerror(err, str) {