Projekt

Obecné

Profil

Stáhnout (6.37 KB) Statistiky
| Větev: | Revize:
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 && headersSent(res)) {
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
      if (status === undefined) {
103
        // fallback to status code on response
104
        status = getResponseStatusCode(res)
105
      } else {
106
        // respect headers from error
107
        headers = getErrorHeaders(err)
108
      }
109

    
110
      // get error message
111
      msg = getErrorMessage(err, status, env)
112
    } else {
113
      // not found
114
      status = 404
115
      msg = 'Cannot ' + req.method + ' ' + encodeUrl(getResourceName(req))
116
    }
117

    
118
    debug('default %s', status)
119

    
120
    // schedule onerror callback
121
    if (err && onerror) {
122
      defer(onerror, err, req, res)
123
    }
124

    
125
    // cannot actually respond
126
    if (headersSent(res)) {
127
      debug('cannot %d after headers sent', status)
128
      req.socket.destroy()
129
      return
130
    }
131

    
132
    // send response
133
    send(req, res, status, headers, msg)
134
  }
135
}
136

    
137
/**
138
 * Get headers from Error object.
139
 *
140
 * @param {Error} err
141
 * @return {object}
142
 * @private
143
 */
144

    
145
function getErrorHeaders (err) {
146
  if (!err.headers || typeof err.headers !== 'object') {
147
    return undefined
148
  }
149

    
150
  var headers = Object.create(null)
151
  var keys = Object.keys(err.headers)
152

    
153
  for (var i = 0; i < keys.length; i++) {
154
    var key = keys[i]
155
    headers[key] = err.headers[key]
156
  }
157

    
158
  return headers
159
}
160

    
161
/**
162
 * Get message from Error object, fallback to status message.
163
 *
164
 * @param {Error} err
165
 * @param {number} status
166
 * @param {string} env
167
 * @return {string}
168
 * @private
169
 */
170

    
171
function getErrorMessage (err, status, env) {
172
  var msg
173

    
174
  if (env !== 'production') {
175
    // use err.stack, which typically includes err.message
176
    msg = err.stack
177

    
178
    // fallback to err.toString() when possible
179
    if (!msg && typeof err.toString === 'function') {
180
      msg = err.toString()
181
    }
182
  }
183

    
184
  return msg || statuses[status]
185
}
186

    
187
/**
188
 * Get status code from Error object.
189
 *
190
 * @param {Error} err
191
 * @return {number}
192
 * @private
193
 */
194

    
195
function getErrorStatusCode (err) {
196
  // check err.status
197
  if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) {
198
    return err.status
199
  }
200

    
201
  // check err.statusCode
202
  if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) {
203
    return err.statusCode
204
  }
205

    
206
  return undefined
207
}
208

    
209
/**
210
 * Get resource name for the request.
211
 *
212
 * This is typically just the original pathname of the request
213
 * but will fallback to "resource" is that cannot be determined.
214
 *
215
 * @param {IncomingMessage} req
216
 * @return {string}
217
 * @private
218
 */
219

    
220
function getResourceName (req) {
221
  try {
222
    return parseUrl.original(req).pathname
223
  } catch (e) {
224
    return 'resource'
225
  }
226
}
227

    
228
/**
229
 * Get status code from response.
230
 *
231
 * @param {OutgoingMessage} res
232
 * @return {number}
233
 * @private
234
 */
235

    
236
function getResponseStatusCode (res) {
237
  var status = res.statusCode
238

    
239
  // default status code to 500 if outside valid range
240
  if (typeof status !== 'number' || status < 400 || status > 599) {
241
    status = 500
242
  }
243

    
244
  return status
245
}
246

    
247
/**
248
 * Determine if the response headers have been sent.
249
 *
250
 * @param {object} res
251
 * @returns {boolean}
252
 * @private
253
 */
254

    
255
function headersSent (res) {
256
  return typeof res.headersSent !== 'boolean'
257
    ? Boolean(res._header)
258
    : res.headersSent
259
}
260

    
261
/**
262
 * Send response.
263
 *
264
 * @param {IncomingMessage} req
265
 * @param {OutgoingMessage} res
266
 * @param {number} status
267
 * @param {object} headers
268
 * @param {string} message
269
 * @private
270
 */
271

    
272
function send (req, res, status, headers, message) {
273
  function write () {
274
    // response body
275
    var body = createHtmlDocument(message)
276

    
277
    // response status
278
    res.statusCode = status
279
    res.statusMessage = statuses[status]
280

    
281
    // response headers
282
    setHeaders(res, headers)
283

    
284
    // security headers
285
    res.setHeader('Content-Security-Policy', "default-src 'none'")
286
    res.setHeader('X-Content-Type-Options', 'nosniff')
287

    
288
    // standard headers
289
    res.setHeader('Content-Type', 'text/html; charset=utf-8')
290
    res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
291

    
292
    if (req.method === 'HEAD') {
293
      res.end()
294
      return
295
    }
296

    
297
    res.end(body, 'utf8')
298
  }
299

    
300
  if (isFinished(req)) {
301
    write()
302
    return
303
  }
304

    
305
  // unpipe everything from the request
306
  unpipe(req)
307

    
308
  // flush the request
309
  onFinished(req, write)
310
  req.resume()
311
}
312

    
313
/**
314
 * Set response headers from an object.
315
 *
316
 * @param {OutgoingMessage} res
317
 * @param {object} headers
318
 * @private
319
 */
320

    
321
function setHeaders (res, headers) {
322
  if (!headers) {
323
    return
324
  }
325

    
326
  var keys = Object.keys(headers)
327
  for (var i = 0; i < keys.length; i++) {
328
    var key = keys[i]
329
    res.setHeader(key, headers[key])
330
  }
331
}
(4-4/5)