You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

171 lines
3.3 KiB

7 years ago
  1. /*!
  2. * finalhandler
  3. * Copyright(c) 2014 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var debug = require('debug')('finalhandler')
  10. var escapeHtml = require('escape-html')
  11. var http = require('http')
  12. var onFinished = require('on-finished')
  13. /**
  14. * Variables.
  15. */
  16. /* istanbul ignore next */
  17. var defer = typeof setImmediate === 'function'
  18. ? setImmediate
  19. : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
  20. var isFinished = onFinished.isFinished
  21. /**
  22. * Module exports.
  23. */
  24. module.exports = finalhandler
  25. /**
  26. * Final handler:
  27. *
  28. * @param {Request} req
  29. * @param {Response} res
  30. * @param {Object} [options]
  31. * @return {Function}
  32. * @api public
  33. */
  34. function finalhandler(req, res, options) {
  35. options = options || {}
  36. // get environment
  37. var env = options.env || process.env.NODE_ENV || 'development'
  38. // get error callback
  39. var onerror = options.onerror
  40. return function (err) {
  41. var msg
  42. // ignore 404 on in-flight response
  43. if (!err && res._header) {
  44. debug('cannot 404 after headers sent')
  45. return
  46. }
  47. // unhandled error
  48. if (err) {
  49. // default status code to 500
  50. if (!res.statusCode || res.statusCode < 400) {
  51. res.statusCode = 500
  52. }
  53. // respect err.status
  54. if (err.status) {
  55. res.statusCode = err.status
  56. }
  57. // production gets a basic error message
  58. var msg = env === 'production'
  59. ? http.STATUS_CODES[res.statusCode]
  60. : err.stack || err.toString()
  61. msg = escapeHtml(msg)
  62. .replace(/\n/g, '<br>')
  63. .replace(/ /g, ' &nbsp;') + '\n'
  64. } else {
  65. res.statusCode = 404
  66. msg = 'Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl || req.url) + '\n'
  67. }
  68. debug('default %s', res.statusCode)
  69. // schedule onerror callback
  70. if (err && onerror) {
  71. defer(onerror, err, req, res)
  72. }
  73. // cannot actually respond
  74. if (res._header) {
  75. return req.socket.destroy()
  76. }
  77. send(req, res, res.statusCode, msg)
  78. }
  79. }
  80. /**
  81. * Send response.
  82. *
  83. * @param {IncomingMessage} req
  84. * @param {OutgoingMessage} res
  85. * @param {number} status
  86. * @param {string} body
  87. * @api private
  88. */
  89. function send(req, res, status, body) {
  90. function write() {
  91. res.statusCode = status
  92. // security header for content sniffing
  93. res.setHeader('X-Content-Type-Options', 'nosniff')
  94. // standard headers
  95. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  96. res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
  97. if (req.method === 'HEAD') {
  98. res.end()
  99. return
  100. }
  101. res.end(body, 'utf8')
  102. }
  103. if (isFinished(req)) {
  104. write()
  105. return
  106. }
  107. // unpipe everything from the request
  108. unpipe(req)
  109. // flush the request
  110. onFinished(req, write)
  111. req.resume()
  112. }
  113. /**
  114. * Unpipe everything from a stream.
  115. *
  116. * @param {Object} stream
  117. * @api private
  118. */
  119. /* istanbul ignore next: implementation differs between versions */
  120. function unpipe(stream) {
  121. if (typeof stream.unpipe === 'function') {
  122. // new-style
  123. stream.unpipe()
  124. return
  125. }
  126. // Node.js 0.8 hack
  127. var listener
  128. var listeners = stream.listeners('close')
  129. for (var i = 0; i < listeners.length; i++) {
  130. listener = listeners[i]
  131. if (listener.name !== 'cleanup' && listener.name !== 'onclose') {
  132. continue
  133. }
  134. // invoke the listener
  135. listener.call(stream)
  136. }
  137. }