index.js 3.6 KB

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