Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50655 views
1
/*!
2
* finalhandler
3
* Copyright(c) 2014-2017 Douglas Christopher Wilson
4
* MIT Licensed
5
*/
6
7
'use strict'
8
9
/**
10
* Module dependencies.
11
* @private
12
*/
13
14
var debug = require('debug')('finalhandler')
15
var encodeUrl = require('encodeurl')
16
var escapeHtml = require('escape-html')
17
var onFinished = require('on-finished')
18
var parseUrl = require('parseurl')
19
var statuses = require('statuses')
20
var unpipe = require('unpipe')
21
22
/**
23
* Module variables.
24
* @private
25
*/
26
27
var DOUBLE_SPACE_REGEXP = /\x20{2}/g
28
var NEWLINE_REGEXP = /\n/g
29
30
/* istanbul ignore next */
31
var defer = typeof setImmediate === 'function'
32
? setImmediate
33
: function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }
34
var isFinished = onFinished.isFinished
35
36
/**
37
* Create a minimal HTML document.
38
*
39
* @param {string} message
40
* @private
41
*/
42
43
function createHtmlDocument (message) {
44
var body = escapeHtml(message)
45
.replace(NEWLINE_REGEXP, '<br>')
46
.replace(DOUBLE_SPACE_REGEXP, ' &nbsp;')
47
48
return '<!DOCTYPE html>\n' +
49
'<html lang="en">\n' +
50
'<head>\n' +
51
'<meta charset="utf-8">\n' +
52
'<title>Error</title>\n' +
53
'</head>\n' +
54
'<body>\n' +
55
'<pre>' + body + '</pre>\n' +
56
'</body>\n' +
57
'</html>\n'
58
}
59
60
/**
61
* Module exports.
62
* @public
63
*/
64
65
module.exports = finalhandler
66
67
/**
68
* Create a function to handle the final response.
69
*
70
* @param {Request} req
71
* @param {Response} res
72
* @param {Object} [options]
73
* @return {Function}
74
* @public
75
*/
76
77
function finalhandler (req, res, options) {
78
var opts = options || {}
79
80
// get environment
81
var env = opts.env || process.env.NODE_ENV || 'development'
82
83
// get error callback
84
var onerror = opts.onerror
85
86
return function (err) {
87
var headers
88
var msg
89
var status
90
91
// ignore 404 on in-flight response
92
if (!err && res._header) {
93
debug('cannot 404 after headers sent')
94
return
95
}
96
97
// unhandled error
98
if (err) {
99
// respect status code from error
100
status = getErrorStatusCode(err)
101
102
// respect headers from error
103
if (status !== undefined) {
104
headers = getErrorHeaders(err)
105
}
106
107
// fallback to status code on response
108
if (status === undefined) {
109
status = getResponseStatusCode(res)
110
}
111
112
// get error message
113
msg = getErrorMessage(err, status, env)
114
} else {
115
// not found
116
status = 404
117
msg = 'Cannot ' + req.method + ' ' + encodeUrl(parseUrl.original(req).pathname)
118
}
119
120
debug('default %s', status)
121
122
// schedule onerror callback
123
if (err && onerror) {
124
defer(onerror, err, req, res)
125
}
126
127
// cannot actually respond
128
if (res._header) {
129
debug('cannot %d after headers sent', status)
130
req.socket.destroy()
131
return
132
}
133
134
// send response
135
send(req, res, status, headers, msg)
136
}
137
}
138
139
/**
140
* Get headers from Error object.
141
*
142
* @param {Error} err
143
* @return {object}
144
* @private
145
*/
146
147
function getErrorHeaders (err) {
148
if (!err.headers || typeof err.headers !== 'object') {
149
return undefined
150
}
151
152
var headers = Object.create(null)
153
var keys = Object.keys(err.headers)
154
155
for (var i = 0; i < keys.length; i++) {
156
var key = keys[i]
157
headers[key] = err.headers[key]
158
}
159
160
return headers
161
}
162
163
/**
164
* Get message from Error object, fallback to status message.
165
*
166
* @param {Error} err
167
* @param {number} status
168
* @param {string} env
169
* @return {string}
170
* @private
171
*/
172
173
function getErrorMessage (err, status, env) {
174
var msg
175
176
if (env !== 'production') {
177
// use err.stack, which typically includes err.message
178
msg = err.stack
179
180
// fallback to err.toString() when possible
181
if (!msg && typeof err.toString === 'function') {
182
msg = err.toString()
183
}
184
}
185
186
return msg || statuses[status]
187
}
188
189
/**
190
* Get status code from Error object.
191
*
192
* @param {Error} err
193
* @return {number}
194
* @private
195
*/
196
197
function getErrorStatusCode (err) {
198
// check err.status
199
if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) {
200
return err.status
201
}
202
203
// check err.statusCode
204
if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) {
205
return err.statusCode
206
}
207
208
return undefined
209
}
210
211
/**
212
* Get status code from response.
213
*
214
* @param {OutgoingMessage} res
215
* @return {number}
216
* @private
217
*/
218
219
function getResponseStatusCode (res) {
220
var status = res.statusCode
221
222
// default status code to 500 if outside valid range
223
if (typeof status !== 'number' || status < 400 || status > 599) {
224
status = 500
225
}
226
227
return status
228
}
229
230
/**
231
* Send response.
232
*
233
* @param {IncomingMessage} req
234
* @param {OutgoingMessage} res
235
* @param {number} status
236
* @param {object} headers
237
* @param {string} message
238
* @private
239
*/
240
241
function send (req, res, status, headers, message) {
242
function write () {
243
// response body
244
var body = createHtmlDocument(message)
245
246
// response status
247
res.statusCode = status
248
res.statusMessage = statuses[status]
249
250
// response headers
251
setHeaders(res, headers)
252
253
// security headers
254
res.setHeader('Content-Security-Policy', "default-src 'self'")
255
res.setHeader('X-Content-Type-Options', 'nosniff')
256
257
// standard headers
258
res.setHeader('Content-Type', 'text/html; charset=utf-8')
259
res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
260
261
if (req.method === 'HEAD') {
262
res.end()
263
return
264
}
265
266
res.end(body, 'utf8')
267
}
268
269
if (isFinished(req)) {
270
write()
271
return
272
}
273
274
// unpipe everything from the request
275
unpipe(req)
276
277
// flush the request
278
onFinished(req, write)
279
req.resume()
280
}
281
282
/**
283
* Set response headers from an object.
284
*
285
* @param {OutgoingMessage} res
286
* @param {object} headers
287
* @private
288
*/
289
290
function setHeaders (res, headers) {
291
if (!headers) {
292
return
293
}
294
295
var keys = Object.keys(headers)
296
for (var i = 0; i < keys.length; i++) {
297
var key = keys[i]
298
res.setHeader(key, headers[key])
299
}
300
}
301
302