index.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. /*!
  2. * finalhandler
  3. * Copyright(c) 2014-2016 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. 'use strict'
  7. /**
  8. * Module dependencies.
  9. * @private
  10. */
  11. var debug = require('debug')('finalhandler')
  12. var escapeHtml = require('escape-html')
  13. var onFinished = require('on-finished')
  14. var statuses = require('statuses')
  15. var unpipe = require('unpipe')
  16. /**
  17. * Module variables.
  18. * @private
  19. */
  20. var DOUBLE_SPACE_REGEXP = /\x20{2}/g
  21. var NEWLINE_REGEXP = /\n/g
  22. /* istanbul ignore next */
  23. var defer = typeof setImmediate === 'function'
  24. ? setImmediate
  25. : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
  26. var isFinished = onFinished.isFinished
  27. /**
  28. * Module exports.
  29. * @public
  30. */
  31. module.exports = finalhandler
  32. /**
  33. * Create a function to handle the final response.
  34. *
  35. * @param {Request} req
  36. * @param {Response} res
  37. * @param {Object} [options]
  38. * @return {Function}
  39. * @public
  40. */
  41. function finalhandler (req, res, options) {
  42. var opts = options || {}
  43. // get environment
  44. var env = opts.env || process.env.NODE_ENV || 'development'
  45. // get error callback
  46. var onerror = opts.onerror
  47. return function (err) {
  48. var headers
  49. var status
  50. // ignore 404 on in-flight response
  51. if (!err && res._header) {
  52. debug('cannot 404 after headers sent')
  53. return
  54. }
  55. // unhandled error
  56. if (err) {
  57. // respect status code from error
  58. status = getErrorStatusCode(err)
  59. // respect headers from error
  60. if (status !== undefined) {
  61. headers = getErrorHeaders(err)
  62. }
  63. // fallback to status code on response
  64. if (status === undefined) {
  65. status = getResponseStatusCode(res)
  66. }
  67. // production gets a basic error message
  68. var msg = env === 'production'
  69. ? statuses[status]
  70. : err.stack || err.toString()
  71. msg = escapeHtml(msg)
  72. .replace(NEWLINE_REGEXP, '<br>')
  73. .replace(DOUBLE_SPACE_REGEXP, ' &nbsp;') + '\n'
  74. } else {
  75. status = 404
  76. msg = 'Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl || req.url) + '\n'
  77. }
  78. debug('default %s', status)
  79. // schedule onerror callback
  80. if (err && onerror) {
  81. defer(onerror, err, req, res)
  82. }
  83. // cannot actually respond
  84. if (res._header) {
  85. debug('cannot %d after headers sent', status)
  86. req.socket.destroy()
  87. return
  88. }
  89. // send response
  90. send(req, res, status, headers, msg)
  91. }
  92. }
  93. /**
  94. * Get headers from Error object.
  95. *
  96. * @param {Error} err
  97. * @return {object}
  98. * @private
  99. */
  100. function getErrorHeaders (err) {
  101. if (!err.headers || typeof err.headers !== 'object') {
  102. return undefined
  103. }
  104. var headers = Object.create(null)
  105. var keys = Object.keys(err.headers)
  106. for (var i = 0; i < keys.length; i++) {
  107. var key = keys[i]
  108. headers[key] = err.headers[key]
  109. }
  110. return headers
  111. }
  112. /**
  113. * Get status code from Error object.
  114. *
  115. * @param {Error} err
  116. * @return {number}
  117. * @private
  118. */
  119. function getErrorStatusCode (err) {
  120. // check err.status
  121. if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) {
  122. return err.status
  123. }
  124. // check err.statusCode
  125. if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) {
  126. return err.statusCode
  127. }
  128. return undefined
  129. }
  130. /**
  131. * Get status code from response.
  132. *
  133. * @param {OutgoingMessage} res
  134. * @return {number}
  135. * @private
  136. */
  137. function getResponseStatusCode (res) {
  138. var status = res.statusCode
  139. // default status code to 500 if outside valid range
  140. if (typeof status !== 'number' || status < 400 || status > 599) {
  141. status = 500
  142. }
  143. return status
  144. }
  145. /**
  146. * Send response.
  147. *
  148. * @param {IncomingMessage} req
  149. * @param {OutgoingMessage} res
  150. * @param {number} status
  151. * @param {object} headers
  152. * @param {string} body
  153. * @private
  154. */
  155. function send (req, res, status, headers, body) {
  156. function write () {
  157. // response status
  158. res.statusCode = status
  159. res.statusMessage = statuses[status]
  160. // response headers
  161. setHeaders(res, headers)
  162. // security header for content sniffing
  163. res.setHeader('X-Content-Type-Options', 'nosniff')
  164. // standard headers
  165. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  166. res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
  167. if (req.method === 'HEAD') {
  168. res.end()
  169. return
  170. }
  171. res.end(body, 'utf8')
  172. }
  173. if (isFinished(req)) {
  174. write()
  175. return
  176. }
  177. // unpipe everything from the request
  178. unpipe(req)
  179. // flush the request
  180. onFinished(req, write)
  181. req.resume()
  182. }
  183. /**
  184. * Set response headers from an object.
  185. *
  186. * @param {OutgoingMessage} res
  187. * @param {object} headers
  188. * @private
  189. */
  190. function setHeaders (res, headers) {
  191. if (!headers) {
  192. return
  193. }
  194. var keys = Object.keys(headers)
  195. for (var i = 0; i < keys.length; i++) {
  196. var key = keys[i]
  197. res.setHeader(key, headers[key])
  198. }
  199. }