opusdev/vector-similarity-api
1
1/*!2 * finalhandler3 * Copyright(c) 2014-2022 Douglas Christopher Wilson4 * MIT Licensed5 */6 7'use strict'8 9/**10 * Module dependencies.11 * @private12 */13 14var debug = require('debug')('finalhandler')15var encodeUrl = require('encodeurl')16var escapeHtml = require('escape-html')17var onFinished = require('on-finished')18var parseUrl = require('parseurl')19var statuses = require('statuses')20var unpipe = require('unpipe')21 22/**23 * Module variables.24 * @private25 */26 27var DOUBLE_SPACE_REGEXP = /\x20{2}/g28var NEWLINE_REGEXP = /\n/g29 30/* istanbul ignore next */31var defer = typeof setImmediate === 'function'32 ? setImmediate33 : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) }34var isFinished = onFinished.isFinished35 36/**37 * Create a minimal HTML document.38 *39 * @param {string} message40 * @private41 */42 43function createHtmlDocument (message) {44 var body = escapeHtml(message)45 .replace(NEWLINE_REGEXP, '<br>')46 .replace(DOUBLE_SPACE_REGEXP, ' ')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 * @public63 */64 65module.exports = finalhandler66 67/**68 * Create a function to handle the final response.69 *70 * @param {Request} req71 * @param {Response} res72 * @param {Object} [options]73 * @return {Function}74 * @public75 */76 77function finalhandler (req, res, options) {78 var opts = options || {}79 80 // get environment81 var env = opts.env || process.env.NODE_ENV || 'development'82 83 // get error callback84 var onerror = opts.onerror85 86 return function (err) {87 var headers88 var msg89 var status90 91 // ignore 404 on in-flight response92 if (!err && headersSent(res)) {93 debug('cannot 404 after headers sent')94 return95 }96 97 // unhandled error98 if (err) {99 // respect status code from error100 status = getErrorStatusCode(err)101 102 if (status === undefined) {103 // fallback to status code on response104 status = getResponseStatusCode(res)105 } else {106 // respect headers from error107 headers = getErrorHeaders(err)108 }109 110 // get error message111 msg = getErrorMessage(err, status, env)112 } else {113 // not found114 status = 404115 msg = 'Cannot ' + req.method + ' ' + encodeUrl(getResourceName(req))116 }117 118 debug('default %s', status)119 120 // schedule onerror callback121 if (err && onerror) {122 defer(onerror, err, req, res)123 }124 125 // cannot actually respond126 if (headersSent(res)) {127 debug('cannot %d after headers sent', status)128 if (req.socket) {129 req.socket.destroy()130 }131 return132 }133 134 // send response135 send(req, res, status, headers, msg)136 }137}138 139/**140 * Get headers from Error object.141 *142 * @param {Error} err143 * @return {object}144 * @private145 */146 147function getErrorHeaders (err) {148 if (!err.headers || typeof err.headers !== 'object') {149 return undefined150 }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 headers161}162 163/**164 * Get message from Error object, fallback to status message.165 *166 * @param {Error} err167 * @param {number} status168 * @param {string} env169 * @return {string}170 * @private171 */172 173function getErrorMessage (err, status, env) {174 var msg175 176 if (env !== 'production') {177 // use err.stack, which typically includes err.message178 msg = err.stack179 180 // fallback to err.toString() when possible181 if (!msg && typeof err.toString === 'function') {182 msg = err.toString()183 }184 }185 186 return msg || statuses.message[status]187}188 189/**190 * Get status code from Error object.191 *192 * @param {Error} err193 * @return {number}194 * @private195 */196 197function getErrorStatusCode (err) {198 // check err.status199 if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) {200 return err.status201 }202 203 // check err.statusCode204 if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) {205 return err.statusCode206 }207 208 return undefined209}210 211/**212 * Get resource name for the request.213 *214 * This is typically just the original pathname of the request215 * but will fallback to "resource" is that cannot be determined.216 *217 * @param {IncomingMessage} req218 * @return {string}219 * @private220 */221 222function getResourceName (req) {223 try {224 return parseUrl.original(req).pathname225 } catch (e) {226 return 'resource'227 }228}229 230/**231 * Get status code from response.232 *233 * @param {OutgoingMessage} res234 * @return {number}235 * @private236 */237 238function getResponseStatusCode (res) {239 var status = res.statusCode240 241 // default status code to 500 if outside valid range242 if (typeof status !== 'number' || status < 400 || status > 599) {243 status = 500244 }245 246 return status247}248 249/**250 * Determine if the response headers have been sent.251 *252 * @param {object} res253 * @returns {boolean}254 * @private255 */256 257function headersSent (res) {258 return typeof res.headersSent !== 'boolean'259 ? Boolean(res._header)260 : res.headersSent261}262 263/**264 * Send response.265 *266 * @param {IncomingMessage} req267 * @param {OutgoingMessage} res268 * @param {number} status269 * @param {object} headers270 * @param {string} message271 * @private272 */273 274function send (req, res, status, headers, message) {275 function write () {276 // response body277 var body = createHtmlDocument(message)278 279 // response status280 res.statusCode = status281 282 if (req.httpVersionMajor < 2) {283 res.statusMessage = statuses.message[status]284 }285 286 // remove any content headers287 res.removeHeader('Content-Encoding')288 res.removeHeader('Content-Language')289 res.removeHeader('Content-Range')290 291 // response headers292 setHeaders(res, headers)293 294 // security headers295 res.setHeader('Content-Security-Policy', "default-src 'none'")296 res.setHeader('X-Content-Type-Options', 'nosniff')297 298 // standard headers299 res.setHeader('Content-Type', 'text/html; charset=utf-8')300 res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))301 302 if (req.method === 'HEAD') {303 res.end()304 return305 }306 307 res.end(body, 'utf8')308 }309 310 if (isFinished(req)) {311 write()312 return313 }314 315 // unpipe everything from the request316 unpipe(req)317 318 // flush the request319 onFinished(req, write)320 req.resume()321}322 323/**324 * Set response headers from an object.325 *326 * @param {OutgoingMessage} res327 * @param {object} headers328 * @private329 */330 331function setHeaders (res, headers) {332 if (!headers) {333 return334 }335 336 var keys = Object.keys(headers)337 for (var i = 0; i < keys.length; i++) {338 var key = keys[i]339 res.setHeader(key, headers[key])340 }341}342 