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