UNPKG

4.57 kBJavaScriptView Raw
1/*!
2 * errorhandler
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
7 * MIT Licensed
8 */
9
10'use strict'
11
12/**
13 * Module dependencies.
14 * @private
15 */
16
17var accepts = require('accepts')
18var escapeHtml = require('escape-html')
19var fs = require('fs')
20var path = require('path')
21var util = require('util')
22
23/**
24 * Module variables.
25 * @private
26 */
27
28var DOUBLE_SPACE_REGEXP = /\x20{2}/g
29var NEW_LINE_REGEXP = /\n/g
30var STYLESHEET = fs.readFileSync(path.join(__dirname, '/public/style.css'), 'utf8')
31var TEMPLATE = fs.readFileSync(path.join(__dirname, '/public/error.html'), 'utf8')
32var inspect = util.inspect
33var toString = Object.prototype.toString
34
35/* istanbul ignore next */
36var defer = typeof setImmediate === 'function'
37 ? setImmediate
38 : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
39
40/**
41 * Error handler:
42 *
43 * Development error handler, providing stack traces
44 * and error message responses for requests accepting text, html,
45 * or json.
46 *
47 * Text:
48 *
49 * By default, and when _text/plain_ is accepted a simple stack trace
50 * or error message will be returned.
51 *
52 * JSON:
53 *
54 * When _application/json_ is accepted, connect will respond with
55 * an object in the form of `{ "error": error }`.
56 *
57 * HTML:
58 *
59 * When accepted connect will output a nice html stack trace.
60 *
61 * @return {Function}
62 * @api public
63 */
64
65exports = module.exports = function errorHandler (options) {
66 // get environment
67 var env = process.env.NODE_ENV || 'development'
68
69 // get options
70 var opts = options || {}
71
72 // get log option
73 var log = opts.log === undefined
74 ? env !== 'test'
75 : opts.log
76
77 if (typeof log !== 'function' && typeof log !== 'boolean') {
78 throw new TypeError('option log must be function or boolean')
79 }
80
81 // default logging using console.error
82 if (log === true) {
83 log = logerror
84 }
85
86 return function errorHandler (err, req, res, next) {
87 // respect err.statusCode
88 if (err.statusCode) {
89 res.statusCode = err.statusCode
90 }
91
92 // respect err.status
93 if (err.status) {
94 res.statusCode = err.status
95 }
96
97 // default status code to 500
98 if (res.statusCode < 400) {
99 res.statusCode = 500
100 }
101
102 // log the error
103 var str = stringify(err)
104 if (log) {
105 defer(log, err, str, req, res)
106 }
107
108 // cannot actually respond
109 if (res._header) {
110 return req.socket.destroy()
111 }
112
113 // negotiate
114 var accept = accepts(req)
115 var type = accept.type('html', 'json', 'text')
116
117 // Security header for content sniffing
118 res.setHeader('X-Content-Type-Options', 'nosniff')
119
120 // html
121 if (type === 'html') {
122 var isInspect = !err.stack && String(err) === toString.call(err)
123 var errorHtml = !isInspect
124 ? escapeHtmlBlock(str.split('\n', 1)[0] || 'Error')
125 : 'Error'
126 var stack = !isInspect
127 ? String(str).split('\n').slice(1)
128 : [str]
129 var stackHtml = stack
130 .map(function (v) { return '<li>' + escapeHtmlBlock(v) + '</li>' })
131 .join('')
132 var body = TEMPLATE
133 .replace('{style}', STYLESHEET)
134 .replace('{stack}', stackHtml)
135 .replace('{title}', escapeHtml(exports.title))
136 .replace('{statusCode}', res.statusCode)
137 .replace(/\{error\}/g, errorHtml)
138 res.setHeader('Content-Type', 'text/html; charset=utf-8')
139 res.end(body)
140 // json
141 } else if (type === 'json') {
142 var error = { message: err.message, stack: err.stack }
143 for (var prop in err) error[prop] = err[prop]
144 var json = JSON.stringify({ error: error }, null, 2)
145 res.setHeader('Content-Type', 'application/json; charset=utf-8')
146 res.end(json)
147 // plain text
148 } else {
149 res.setHeader('Content-Type', 'text/plain; charset=utf-8')
150 res.end(str)
151 }
152 }
153}
154
155/**
156 * Template title, framework authors may override this value.
157 */
158
159exports.title = 'Connect'
160
161/**
162 * Escape a block of HTML, preserving whitespace.
163 * @api private
164 */
165
166function escapeHtmlBlock (str) {
167 return escapeHtml(str)
168 .replace(DOUBLE_SPACE_REGEXP, ' &nbsp;')
169 .replace(NEW_LINE_REGEXP, '<br>')
170}
171
172/**
173 * Stringify a value.
174 * @api private
175 */
176
177function stringify (val) {
178 var stack = val.stack
179
180 if (stack) {
181 return String(stack)
182 }
183
184 var str = String(val)
185
186 return str === toString.call(val)
187 ? inspect(val)
188 : str
189}
190
191/**
192 * Log error to console.
193 * @api private
194 */
195
196function logerror (err, str) {
197 console.error(str || err.stack)
198}