123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143 |
- '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';
- }
|