3 * Copyright(c) 2014 Douglas Christopher Wilson
11 var debug = require('debug')('finalhandler')
12 var escapeHtml = require('escape-html')
13 var http = require('http')
14 var onFinished = require('on-finished')
20 /* istanbul ignore next */
21 var defer = typeof setImmediate === 'function'
23 : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) }
24 var isFinished = onFinished.isFinished
30 module.exports = finalhandler
35 * @param {Request} req
36 * @param {Response} res
37 * @param {Object} [options]
42 function finalhandler(req, res, options) {
43 options = options || {}
46 var env = options.env || process.env.NODE_ENV || 'development'
49 var onerror = options.onerror
51 return function (err) {
54 // ignore 404 on in-flight response
55 if (!err && res._header) {
56 debug('cannot 404 after headers sent')
62 // default status code to 500
63 if (!res.statusCode || res.statusCode < 400) {
69 res.statusCode = err.status
72 // production gets a basic error message
73 var msg = env === 'production'
74 ? http.STATUS_CODES[res.statusCode]
75 : err.stack || err.toString()
77 .replace(/\n/g, '<br>')
78 .replace(/ /g, ' ') + '\n'
81 msg = 'Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl || req.url) + '\n'
84 debug('default %s', res.statusCode)
86 // schedule onerror callback
88 defer(onerror, err, req, res)
91 // cannot actually respond
93 return req.socket.destroy()
96 send(req, res, res.statusCode, msg)
103 * @param {IncomingMessage} req
104 * @param {OutgoingMessage} res
105 * @param {number} status
106 * @param {string} body
110 function send(req, res, status, body) {
112 res.statusCode = status
114 // security header for content sniffing
115 res.setHeader('X-Content-Type-Options', 'nosniff')
118 res.setHeader('Content-Type', 'text/html; charset=utf-8')
119 res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
121 if (req.method === 'HEAD') {
126 res.end(body, 'utf8')
129 if (isFinished(req)) {
134 // unpipe everything from the request
138 onFinished(req, write)
143 * Unpipe everything from a stream.
145 * @param {Object} stream
149 /* istanbul ignore next: implementation differs between versions */
150 function unpipe(stream) {
151 if (typeof stream.unpipe === 'function') {
159 var listeners = stream.listeners('close')
161 for (var i = 0; i < listeners.length; i++) {
162 listener = listeners[i]
164 if (listener.name !== 'cleanup' && listener.name !== 'onclose') {
168 // invoke the listener
169 listener.call(stream)