-
Notifications
You must be signed in to change notification settings - Fork 20
/
index.js
143 lines (120 loc) · 3.56 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
'use strict';
const http = require('http');
const path = require('path');
const fs = require('fs');
const escapeHtml = require('escape-html');
const sendToWormhole = require('stream-wormhole');
const env = process.env.NODE_ENV || 'development';
const isDev = env === 'development';
const templatePath = isDev
? path.join(__dirname, 'templates/dev_error.html')
: path.join(__dirname, 'templates/prod_error.html');
const defaultTemplate = fs.readFileSync(templatePath, 'utf8');
const defaultOptions = {
text,
json,
html,
redirect: null,
template: path.join(__dirname, 'error.html'),
accepts: null,
};
module.exports = function onerror(app, options) {
options = Object.assign({}, defaultOptions, options);
app.context.onerror = function(err) {
// don't do anything if there is no error.
// this allows you to pass `this.onerror`
// to node-style callbacks.
if (err == null) return;
// ignore all pedding request stream
if (this.req) sendToWormhole(this.req);
// wrap non-error object
if (!(err instanceof Error)) {
let errMsg = err;
if (typeof err === 'object') {
try {
errMsg = JSON.stringify(err);
// eslint-disable-next-line no-empty
} catch (e) {}
}
const newError = new Error('non-error thrown: ' + errMsg);
// err maybe an object, try to copy the name, message and stack to the new error instance
if (err) {
if (err.name) newError.name = err.name;
if (err.message) newError.message = err.message;
if (err.stack) newError.stack = err.stack;
if (err.status) newError.status = err.status;
if (err.headers) newError.headers = err.headers;
}
err = newError;
}
const headerSent = this.headerSent || !this.writable;
if (headerSent) err.headerSent = true;
// delegate
this.app.emit('error', err, this);
// nothing we can do here other
// than delegate to the app-level
// handler and log.
if (headerSent) return;
// ENOENT support
if (err.code === 'ENOENT') err.status = 404;
if (typeof err.status !== 'number' || !http.STATUS_CODES[err.status]) {
err.status = 500;
}
this.status = err.status;
this.set(err.headers);
let type = 'text';
if (options.accepts) {
type = options.accepts.call(this, 'html', 'text', 'json');
} else {
type = this.accepts('html', 'text', 'json');
}
type = type || 'text';
if (options.all) {
options.all.call(this, err, this);
} else {
if (options.redirect && type !== 'json') {
this.redirect(options.redirect);
} else {
options[type].call(this, err, this);
this.type = type;
}
}
if (type === 'json') {
this.body = JSON.stringify(this.body);
}
this.res.end(this.body);
};
return app;
};
/**
* default text error handler
* @param {Error} err
*/
function text(err, ctx) {
// unset all headers, and set those specified
ctx.res._headers = {};
ctx.set(err.headers);
ctx.body = (isDev || err.expose) && err.message
? err.message
: http.STATUS_CODES[this.status];
}
/**
* default json error handler
* @param {Error} err
*/
function json(err, ctx) {
const message = (isDev || err.expose) && err.message
? err.message
: http.STATUS_CODES[this.status];
ctx.body = { error: message };
}
/**
* default html error handler
* @param {Error} err
*/
function html(err, ctx) {
ctx.body = defaultTemplate
.replace('{{status}}', escapeHtml(err.status))
.replace('{{stack}}', escapeHtml(err.stack));
ctx.type = 'html';
}