1272
node_modules/fastify/lib/configValidator.js
generated
vendored
Normal file
1272
node_modules/fastify/lib/configValidator.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
402
node_modules/fastify/lib/contentTypeParser.js
generated
vendored
Normal file
402
node_modules/fastify/lib/contentTypeParser.js
generated
vendored
Normal file
@@ -0,0 +1,402 @@
|
||||
'use strict'
|
||||
|
||||
const { AsyncResource } = require('node:async_hooks')
|
||||
const { FifoMap: Fifo } = require('toad-cache')
|
||||
const { parse: secureJsonParse } = require('secure-json-parse')
|
||||
const {
|
||||
kDefaultJsonParse,
|
||||
kContentTypeParser,
|
||||
kBodyLimit,
|
||||
kRequestPayloadStream,
|
||||
kState,
|
||||
kTestInternals,
|
||||
kReplyIsError,
|
||||
kRouteContext
|
||||
} = require('./symbols')
|
||||
|
||||
const {
|
||||
FST_ERR_CTP_INVALID_TYPE,
|
||||
FST_ERR_CTP_EMPTY_TYPE,
|
||||
FST_ERR_CTP_ALREADY_PRESENT,
|
||||
FST_ERR_CTP_INVALID_HANDLER,
|
||||
FST_ERR_CTP_INVALID_PARSE_TYPE,
|
||||
FST_ERR_CTP_BODY_TOO_LARGE,
|
||||
FST_ERR_CTP_INVALID_MEDIA_TYPE,
|
||||
FST_ERR_CTP_INVALID_CONTENT_LENGTH,
|
||||
FST_ERR_CTP_EMPTY_JSON_BODY,
|
||||
FST_ERR_CTP_INSTANCE_ALREADY_STARTED,
|
||||
FST_ERR_CTP_INVALID_JSON_BODY
|
||||
} = require('./errors')
|
||||
const { FSTSEC001 } = require('./warnings')
|
||||
|
||||
function ContentTypeParser (bodyLimit, onProtoPoisoning, onConstructorPoisoning) {
|
||||
this[kDefaultJsonParse] = getDefaultJsonParser(onProtoPoisoning, onConstructorPoisoning)
|
||||
// using a map instead of a plain object to avoid prototype hijack attacks
|
||||
this.customParsers = new Map()
|
||||
this.customParsers.set('application/json', new Parser(true, false, bodyLimit, this[kDefaultJsonParse]))
|
||||
this.customParsers.set('text/plain', new Parser(true, false, bodyLimit, defaultPlainTextParser))
|
||||
this.parserList = ['application/json', 'text/plain']
|
||||
this.parserRegExpList = []
|
||||
this.cache = new Fifo(100)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.add = function (contentType, opts, parserFn) {
|
||||
const contentTypeIsString = typeof contentType === 'string'
|
||||
|
||||
if (contentTypeIsString) {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
if (contentType.length === 0) throw new FST_ERR_CTP_EMPTY_TYPE()
|
||||
} else if (!(contentType instanceof RegExp)) {
|
||||
throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
}
|
||||
|
||||
if (typeof parserFn !== 'function') {
|
||||
throw new FST_ERR_CTP_INVALID_HANDLER()
|
||||
}
|
||||
|
||||
if (this.existingParser(contentType)) {
|
||||
throw new FST_ERR_CTP_ALREADY_PRESENT(contentType)
|
||||
}
|
||||
|
||||
if (opts.parseAs !== undefined) {
|
||||
if (opts.parseAs !== 'string' && opts.parseAs !== 'buffer') {
|
||||
throw new FST_ERR_CTP_INVALID_PARSE_TYPE(opts.parseAs)
|
||||
}
|
||||
}
|
||||
|
||||
const parser = new Parser(
|
||||
opts.parseAs === 'string',
|
||||
opts.parseAs === 'buffer',
|
||||
opts.bodyLimit,
|
||||
parserFn
|
||||
)
|
||||
|
||||
if (contentType === '*') {
|
||||
this.customParsers.set('', parser)
|
||||
} else {
|
||||
if (contentTypeIsString) {
|
||||
this.parserList.unshift(contentType)
|
||||
this.customParsers.set(contentType, parser)
|
||||
} else {
|
||||
validateRegExp(contentType)
|
||||
this.parserRegExpList.unshift(contentType)
|
||||
this.customParsers.set(contentType.toString(), parser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.hasParser = function (contentType) {
|
||||
if (typeof contentType === 'string') {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
} else {
|
||||
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
contentType = contentType.toString()
|
||||
}
|
||||
|
||||
return this.customParsers.has(contentType)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.existingParser = function (contentType) {
|
||||
if (contentType === 'application/json' && this.customParsers.has(contentType)) {
|
||||
return this.customParsers.get(contentType).fn !== this[kDefaultJsonParse]
|
||||
}
|
||||
if (contentType === 'text/plain' && this.customParsers.has(contentType)) {
|
||||
return this.customParsers.get(contentType).fn !== defaultPlainTextParser
|
||||
}
|
||||
|
||||
return this.hasParser(contentType)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.getParser = function (contentType) {
|
||||
let parser = this.customParsers.get(contentType)
|
||||
if (parser !== undefined) return parser
|
||||
parser = this.cache.get(contentType)
|
||||
if (parser !== undefined) return parser
|
||||
|
||||
const caseInsensitiveContentType = contentType.toLowerCase()
|
||||
for (let i = 0; i !== this.parserList.length; ++i) {
|
||||
const parserListItem = this.parserList[i]
|
||||
if (
|
||||
caseInsensitiveContentType.slice(0, parserListItem.length) === parserListItem &&
|
||||
(
|
||||
caseInsensitiveContentType.length === parserListItem.length ||
|
||||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 59 /* `;` */ ||
|
||||
caseInsensitiveContentType.charCodeAt(parserListItem.length) === 32 /* ` ` */
|
||||
)
|
||||
) {
|
||||
parser = this.customParsers.get(parserListItem)
|
||||
this.cache.set(contentType, parser)
|
||||
return parser
|
||||
}
|
||||
}
|
||||
|
||||
for (let j = 0; j !== this.parserRegExpList.length; ++j) {
|
||||
const parserRegExp = this.parserRegExpList[j]
|
||||
if (parserRegExp.test(contentType)) {
|
||||
parser = this.customParsers.get(parserRegExp.toString())
|
||||
this.cache.set(contentType, parser)
|
||||
return parser
|
||||
}
|
||||
}
|
||||
|
||||
return this.customParsers.get('')
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.removeAll = function () {
|
||||
this.customParsers = new Map()
|
||||
this.parserRegExpList = []
|
||||
this.parserList = []
|
||||
this.cache = new Fifo(100)
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.remove = function (contentType) {
|
||||
let parsers
|
||||
|
||||
if (typeof contentType === 'string') {
|
||||
contentType = contentType.trim().toLowerCase()
|
||||
parsers = this.parserList
|
||||
} else {
|
||||
if (!(contentType instanceof RegExp)) throw new FST_ERR_CTP_INVALID_TYPE()
|
||||
contentType = contentType.toString()
|
||||
parsers = this.parserRegExpList
|
||||
}
|
||||
|
||||
const removed = this.customParsers.delete(contentType)
|
||||
const idx = parsers.findIndex(ct => ct.toString() === contentType)
|
||||
|
||||
if (idx > -1) {
|
||||
parsers.splice(idx, 1)
|
||||
}
|
||||
|
||||
return removed || idx > -1
|
||||
}
|
||||
|
||||
ContentTypeParser.prototype.run = function (contentType, handler, request, reply) {
|
||||
const parser = this.getParser(contentType)
|
||||
|
||||
if (parser === undefined) {
|
||||
if (request.is404 === true) {
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(new FST_ERR_CTP_INVALID_MEDIA_TYPE(contentType || undefined))
|
||||
return
|
||||
}
|
||||
|
||||
const resource = new AsyncResource('content-type-parser:run', request)
|
||||
const done = resource.bind(onDone)
|
||||
|
||||
if (parser.asString === true || parser.asBuffer === true) {
|
||||
rawBody(
|
||||
request,
|
||||
reply,
|
||||
reply[kRouteContext]._parserOptions,
|
||||
parser,
|
||||
done
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const result = parser.fn(request, request[kRequestPayloadStream], done)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(body => { done(null, body) }, done)
|
||||
}
|
||||
|
||||
function onDone (error, body) {
|
||||
resource.emitDestroy()
|
||||
if (error != null) {
|
||||
// We must close the connection as the client may
|
||||
// send more data
|
||||
reply.header('connection', 'close')
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(error)
|
||||
return
|
||||
}
|
||||
request.body = body
|
||||
handler(request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function rawBody (request, reply, options, parser, done) {
|
||||
const asString = parser.asString === true
|
||||
const limit = options.limit === null ? parser.bodyLimit : options.limit
|
||||
const contentLength = Number(request.headers['content-length'])
|
||||
|
||||
if (contentLength > limit) {
|
||||
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
let receivedLength = 0
|
||||
let body = asString ? '' : []
|
||||
const payload = request[kRequestPayloadStream] || request.raw
|
||||
|
||||
if (asString) {
|
||||
payload.setEncoding('utf8')
|
||||
}
|
||||
|
||||
payload.on('data', onData)
|
||||
payload.on('end', onEnd)
|
||||
payload.on('error', onEnd)
|
||||
payload.resume()
|
||||
|
||||
function onData (chunk) {
|
||||
receivedLength += asString ? Buffer.byteLength(chunk) : chunk.length
|
||||
const { receivedEncodedLength = 0 } = payload
|
||||
// The resulting body length must not exceed bodyLimit (see "zip bomb").
|
||||
// The case when encoded length is larger than received length is rather theoretical,
|
||||
// unless the stream returned by preParsing hook is broken and reports wrong value.
|
||||
if (receivedLength > limit || receivedEncodedLength > limit) {
|
||||
payload.removeListener('data', onData)
|
||||
payload.removeListener('end', onEnd)
|
||||
payload.removeListener('error', onEnd)
|
||||
done(new FST_ERR_CTP_BODY_TOO_LARGE(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (asString) {
|
||||
body += chunk
|
||||
} else {
|
||||
body.push(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
function onEnd (err) {
|
||||
payload.removeListener('data', onData)
|
||||
payload.removeListener('end', onEnd)
|
||||
payload.removeListener('error', onEnd)
|
||||
|
||||
if (err != null) {
|
||||
if (!(typeof err.statusCode === 'number' && err.statusCode >= 400)) {
|
||||
err.statusCode = 400
|
||||
}
|
||||
done(err, undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (!Number.isNaN(contentLength) && (payload.receivedEncodedLength || receivedLength) !== contentLength) {
|
||||
done(new FST_ERR_CTP_INVALID_CONTENT_LENGTH(), undefined)
|
||||
return
|
||||
}
|
||||
|
||||
if (!asString) {
|
||||
body = Buffer.concat(body)
|
||||
}
|
||||
|
||||
const result = parser.fn(request, body, done)
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(body => { done(null, body) }, done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultJsonParser (onProtoPoisoning, onConstructorPoisoning) {
|
||||
const parseOptions = { protoAction: onProtoPoisoning, constructorAction: onConstructorPoisoning }
|
||||
|
||||
return defaultJsonParser
|
||||
|
||||
function defaultJsonParser (req, body, done) {
|
||||
if (body.length === 0) {
|
||||
done(new FST_ERR_CTP_EMPTY_JSON_BODY(), undefined)
|
||||
return
|
||||
}
|
||||
try {
|
||||
done(null, secureJsonParse(body, parseOptions))
|
||||
} catch {
|
||||
done(new FST_ERR_CTP_INVALID_JSON_BODY(), undefined)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function defaultPlainTextParser (req, body, done) {
|
||||
done(null, body)
|
||||
}
|
||||
|
||||
function Parser (asString, asBuffer, bodyLimit, fn) {
|
||||
this.asString = asString
|
||||
this.asBuffer = asBuffer
|
||||
this.bodyLimit = bodyLimit
|
||||
this.fn = fn
|
||||
}
|
||||
|
||||
function buildContentTypeParser (c) {
|
||||
const contentTypeParser = new ContentTypeParser()
|
||||
contentTypeParser[kDefaultJsonParse] = c[kDefaultJsonParse]
|
||||
contentTypeParser.customParsers = new Map(c.customParsers.entries())
|
||||
contentTypeParser.parserList = c.parserList.slice()
|
||||
contentTypeParser.parserRegExpList = c.parserRegExpList.slice()
|
||||
return contentTypeParser
|
||||
}
|
||||
|
||||
function addContentTypeParser (contentType, opts, parser) {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('addContentTypeParser')
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
parser = opts
|
||||
opts = {}
|
||||
}
|
||||
|
||||
if (!opts) opts = {}
|
||||
if (!opts.bodyLimit) opts.bodyLimit = this[kBodyLimit]
|
||||
|
||||
if (Array.isArray(contentType)) {
|
||||
contentType.forEach((type) => this[kContentTypeParser].add(type, opts, parser))
|
||||
} else {
|
||||
this[kContentTypeParser].add(contentType, opts, parser)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
function hasContentTypeParser (contentType) {
|
||||
return this[kContentTypeParser].hasParser(contentType)
|
||||
}
|
||||
|
||||
function removeContentTypeParser (contentType) {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeContentTypeParser')
|
||||
}
|
||||
|
||||
if (Array.isArray(contentType)) {
|
||||
for (const type of contentType) {
|
||||
this[kContentTypeParser].remove(type)
|
||||
}
|
||||
} else {
|
||||
this[kContentTypeParser].remove(contentType)
|
||||
}
|
||||
}
|
||||
|
||||
function removeAllContentTypeParsers () {
|
||||
if (this[kState].started) {
|
||||
throw new FST_ERR_CTP_INSTANCE_ALREADY_STARTED('removeAllContentTypeParsers')
|
||||
}
|
||||
|
||||
this[kContentTypeParser].removeAll()
|
||||
}
|
||||
|
||||
function validateRegExp (regexp) {
|
||||
// RegExp should either start with ^ or include ;?
|
||||
// It can ensure the user is properly detect the essence
|
||||
// MIME types.
|
||||
if (regexp.source[0] !== '^' && regexp.source.includes(';?') === false) {
|
||||
FSTSEC001(regexp.source)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContentTypeParser
|
||||
module.exports.helpers = {
|
||||
buildContentTypeParser,
|
||||
addContentTypeParser,
|
||||
hasContentTypeParser,
|
||||
removeContentTypeParser,
|
||||
removeAllContentTypeParsers
|
||||
}
|
||||
module.exports.defaultParsers = {
|
||||
getDefaultJsonParser,
|
||||
defaultTextParser: defaultPlainTextParser
|
||||
}
|
||||
module.exports[kTestInternals] = { rawBody }
|
||||
95
node_modules/fastify/lib/context.js
generated
vendored
Normal file
95
node_modules/fastify/lib/context.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kFourOhFourContext,
|
||||
kReplySerializerDefault,
|
||||
kSchemaErrorFormatter,
|
||||
kErrorHandler,
|
||||
kChildLoggerFactory,
|
||||
kOptions,
|
||||
kReply,
|
||||
kRequest,
|
||||
kBodyLimit,
|
||||
kLogLevel,
|
||||
kContentTypeParser,
|
||||
kRouteByFastify,
|
||||
kRequestCacheValidateFns,
|
||||
kReplyCacheSerializeFns
|
||||
} = require('./symbols.js')
|
||||
|
||||
// Object that holds the context of every request
|
||||
// Every route holds an instance of this object.
|
||||
function Context ({
|
||||
schema,
|
||||
handler,
|
||||
config,
|
||||
requestIdLogLabel,
|
||||
childLoggerFactory,
|
||||
errorHandler,
|
||||
bodyLimit,
|
||||
logLevel,
|
||||
logSerializers,
|
||||
attachValidation,
|
||||
validatorCompiler,
|
||||
serializerCompiler,
|
||||
replySerializer,
|
||||
schemaErrorFormatter,
|
||||
exposeHeadRoute,
|
||||
prefixTrailingSlash,
|
||||
server,
|
||||
isFastify
|
||||
}) {
|
||||
this.schema = schema
|
||||
this.handler = handler
|
||||
this.Reply = server[kReply]
|
||||
this.Request = server[kRequest]
|
||||
this.contentTypeParser = server[kContentTypeParser]
|
||||
this.onRequest = null
|
||||
this.onSend = null
|
||||
this.onError = null
|
||||
this.onTimeout = null
|
||||
this.preHandler = null
|
||||
this.onResponse = null
|
||||
this.preSerialization = null
|
||||
this.onRequestAbort = null
|
||||
this.config = config
|
||||
this.errorHandler = errorHandler || server[kErrorHandler]
|
||||
this.requestIdLogLabel = requestIdLogLabel || server[kOptions].requestIdLogLabel
|
||||
this.childLoggerFactory = childLoggerFactory || server[kChildLoggerFactory]
|
||||
this._middie = null
|
||||
this._parserOptions = {
|
||||
limit: bodyLimit || server[kBodyLimit]
|
||||
}
|
||||
this.exposeHeadRoute = exposeHeadRoute
|
||||
this.prefixTrailingSlash = prefixTrailingSlash
|
||||
this.logLevel = logLevel || server[kLogLevel]
|
||||
this.logSerializers = logSerializers
|
||||
this[kFourOhFourContext] = null
|
||||
this.attachValidation = attachValidation
|
||||
this[kReplySerializerDefault] = replySerializer
|
||||
this.schemaErrorFormatter =
|
||||
schemaErrorFormatter ||
|
||||
server[kSchemaErrorFormatter] ||
|
||||
defaultSchemaErrorFormatter
|
||||
this[kRouteByFastify] = isFastify
|
||||
|
||||
this[kRequestCacheValidateFns] = null
|
||||
this[kReplyCacheSerializeFns] = null
|
||||
this.validatorCompiler = validatorCompiler || null
|
||||
this.serializerCompiler = serializerCompiler || null
|
||||
|
||||
this.server = server
|
||||
}
|
||||
|
||||
function defaultSchemaErrorFormatter (errors, dataVar) {
|
||||
let text = ''
|
||||
const separator = ', '
|
||||
|
||||
for (let i = 0; i !== errors.length; ++i) {
|
||||
const e = errors[i]
|
||||
text += dataVar + (e.instancePath || '') + ' ' + e.message + separator
|
||||
}
|
||||
return new Error(text.slice(0, -separator.length))
|
||||
}
|
||||
|
||||
module.exports = Context
|
||||
152
node_modules/fastify/lib/decorate.js
generated
vendored
Normal file
152
node_modules/fastify/lib/decorate.js
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kReply,
|
||||
kRequest,
|
||||
kState,
|
||||
kHasBeenDecorated
|
||||
} = require('./symbols.js')
|
||||
|
||||
const {
|
||||
FST_ERR_DEC_ALREADY_PRESENT,
|
||||
FST_ERR_DEC_MISSING_DEPENDENCY,
|
||||
FST_ERR_DEC_AFTER_START,
|
||||
FST_ERR_DEC_REFERENCE_TYPE,
|
||||
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE,
|
||||
FST_ERR_DEC_UNDECLARED
|
||||
} = require('./errors')
|
||||
|
||||
function decorate (instance, name, fn, dependencies) {
|
||||
if (Object.hasOwn(instance, name)) {
|
||||
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
||||
}
|
||||
|
||||
checkDependencies(instance, name, dependencies)
|
||||
|
||||
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
Object.defineProperty(instance, name, {
|
||||
get: fn.getter,
|
||||
set: fn.setter
|
||||
})
|
||||
} else {
|
||||
instance[name] = fn
|
||||
}
|
||||
}
|
||||
|
||||
function getInstanceDecorator (name) {
|
||||
if (!checkExistence(this, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'instance')
|
||||
}
|
||||
|
||||
if (typeof this[name] === 'function') {
|
||||
return this[name].bind(this)
|
||||
}
|
||||
|
||||
return this[name]
|
||||
}
|
||||
|
||||
function decorateConstructor (konstructor, name, fn, dependencies) {
|
||||
const instance = konstructor.prototype
|
||||
if (Object.hasOwn(instance, name) || hasKey(konstructor, name)) {
|
||||
throw new FST_ERR_DEC_ALREADY_PRESENT(name)
|
||||
}
|
||||
|
||||
konstructor[kHasBeenDecorated] = true
|
||||
checkDependencies(konstructor, name, dependencies)
|
||||
|
||||
if (fn && (typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
Object.defineProperty(instance, name, {
|
||||
get: fn.getter,
|
||||
set: fn.setter
|
||||
})
|
||||
} else if (typeof fn === 'function') {
|
||||
instance[name] = fn
|
||||
} else {
|
||||
konstructor.props.push({ key: name, value: fn })
|
||||
}
|
||||
}
|
||||
|
||||
function checkReferenceType (name, fn) {
|
||||
if (typeof fn === 'object' && fn && !(typeof fn.getter === 'function' || typeof fn.setter === 'function')) {
|
||||
throw new FST_ERR_DEC_REFERENCE_TYPE(name, typeof fn)
|
||||
}
|
||||
}
|
||||
|
||||
function decorateFastify (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
decorate(this, name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function checkExistence (instance, name) {
|
||||
if (name) {
|
||||
return name in instance || (instance.prototype && name in instance.prototype) || hasKey(instance, name)
|
||||
}
|
||||
|
||||
return instance in this
|
||||
}
|
||||
|
||||
function hasKey (fn, name) {
|
||||
if (fn.props) {
|
||||
return fn.props.find(({ key }) => key === name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function checkRequestExistence (name) {
|
||||
if (name && hasKey(this[kRequest], name)) return true
|
||||
return checkExistence(this[kRequest].prototype, name)
|
||||
}
|
||||
|
||||
function checkReplyExistence (name) {
|
||||
if (name && hasKey(this[kReply], name)) return true
|
||||
return checkExistence(this[kReply].prototype, name)
|
||||
}
|
||||
|
||||
function checkDependencies (instance, name, deps) {
|
||||
if (deps === undefined || deps === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!Array.isArray(deps)) {
|
||||
throw new FST_ERR_DEC_DEPENDENCY_INVALID_TYPE(name)
|
||||
}
|
||||
|
||||
for (let i = 0; i !== deps.length; ++i) {
|
||||
if (!checkExistence(instance, deps[i])) {
|
||||
throw new FST_ERR_DEC_MISSING_DEPENDENCY(deps[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function decorateReply (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
checkReferenceType(name, fn)
|
||||
decorateConstructor(this[kReply], name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function decorateRequest (name, fn, dependencies) {
|
||||
assertNotStarted(this, name)
|
||||
checkReferenceType(name, fn)
|
||||
decorateConstructor(this[kRequest], name, fn, dependencies)
|
||||
return this
|
||||
}
|
||||
|
||||
function assertNotStarted (instance, name) {
|
||||
if (instance[kState].started) {
|
||||
throw new FST_ERR_DEC_AFTER_START(name)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
add: decorateFastify,
|
||||
exist: checkExistence,
|
||||
existRequest: checkRequestExistence,
|
||||
existReply: checkReplyExistence,
|
||||
dependencies: checkDependencies,
|
||||
decorateReply,
|
||||
decorateRequest,
|
||||
getInstanceDecorator,
|
||||
hasKey
|
||||
}
|
||||
176
node_modules/fastify/lib/error-handler.js
generated
vendored
Normal file
176
node_modules/fastify/lib/error-handler.js
generated
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
'use strict'
|
||||
|
||||
const statusCodes = require('node:http').STATUS_CODES
|
||||
const wrapThenable = require('./wrapThenable')
|
||||
const {
|
||||
kReplyHeaders,
|
||||
kReplyNextErrorHandler,
|
||||
kReplyIsRunningOnErrorHook,
|
||||
kReplyHasStatusCode,
|
||||
kRouteContext,
|
||||
kDisableRequestLogging
|
||||
} = require('./symbols.js')
|
||||
|
||||
const {
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
||||
FST_ERR_FAILED_ERROR_SERIALIZATION
|
||||
} = require('./errors')
|
||||
|
||||
const { getSchemaSerializer } = require('./schemas')
|
||||
|
||||
const serializeError = require('./error-serializer')
|
||||
|
||||
const rootErrorHandler = {
|
||||
func: defaultErrorHandler,
|
||||
toJSON () {
|
||||
return this.func.name.toString() + '()'
|
||||
}
|
||||
}
|
||||
|
||||
function handleError (reply, error, cb) {
|
||||
reply[kReplyIsRunningOnErrorHook] = false
|
||||
|
||||
const context = reply[kRouteContext]
|
||||
if (reply[kReplyNextErrorHandler] === false) {
|
||||
fallbackErrorHandler(error, reply, function (reply, payload) {
|
||||
try {
|
||||
reply.raw.writeHead(reply.raw.statusCode, reply[kReplyHeaders])
|
||||
} catch (error) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.warn(
|
||||
{ req: reply.request, res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
reply.raw.writeHead(reply.raw.statusCode)
|
||||
}
|
||||
reply.raw.end(payload)
|
||||
})
|
||||
return
|
||||
}
|
||||
const errorHandler = reply[kReplyNextErrorHandler] || context.errorHandler
|
||||
|
||||
// In case the error handler throws, we set the next errorHandler so we can error again
|
||||
reply[kReplyNextErrorHandler] = Object.getPrototypeOf(errorHandler)
|
||||
|
||||
// we need to remove content-type to allow content-type guessing for serialization
|
||||
delete reply[kReplyHeaders]['content-type']
|
||||
delete reply[kReplyHeaders]['content-length']
|
||||
|
||||
const func = errorHandler.func
|
||||
|
||||
if (!func) {
|
||||
reply[kReplyNextErrorHandler] = false
|
||||
fallbackErrorHandler(error, reply, cb)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = func(error, reply.request, reply)
|
||||
if (result !== undefined) {
|
||||
if (result !== null && typeof result.then === 'function') {
|
||||
wrapThenable(result, reply)
|
||||
} else {
|
||||
reply.send(result)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
reply.send(err)
|
||||
}
|
||||
}
|
||||
|
||||
function defaultErrorHandler (error, request, reply) {
|
||||
setErrorHeaders(error, reply)
|
||||
if (!reply[kReplyHasStatusCode] || reply.statusCode === 200) {
|
||||
const statusCode = error.statusCode || error.status
|
||||
reply.code(statusCode >= 400 ? statusCode : 500)
|
||||
}
|
||||
if (reply.statusCode < 500) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.info(
|
||||
{ res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
reply.log.error(
|
||||
{ req: request, res: reply, err: error },
|
||||
error?.message
|
||||
)
|
||||
}
|
||||
}
|
||||
reply.send(error)
|
||||
}
|
||||
|
||||
function fallbackErrorHandler (error, reply, cb) {
|
||||
const res = reply.raw
|
||||
const statusCode = reply.statusCode
|
||||
reply[kReplyHeaders]['content-type'] = reply[kReplyHeaders]['content-type'] ?? 'application/json; charset=utf-8'
|
||||
let payload
|
||||
try {
|
||||
const serializerFn = getSchemaSerializer(reply[kRouteContext], statusCode, reply[kReplyHeaders]['content-type'])
|
||||
if (serializerFn === false) {
|
||||
payload = serializeError({
|
||||
error: statusCodes[statusCode + ''],
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
statusCode
|
||||
})
|
||||
} else {
|
||||
payload = serializerFn(Object.create(error, {
|
||||
error: { value: statusCodes[statusCode + ''] },
|
||||
message: { value: error.message },
|
||||
statusCode: { value: statusCode }
|
||||
}))
|
||||
}
|
||||
} catch (err) {
|
||||
if (!reply.log[kDisableRequestLogging]) {
|
||||
// error is always FST_ERR_SCH_SERIALIZATION_BUILD because this is called from route/compileSchemasForSerialization
|
||||
reply.log.error({ err, statusCode: res.statusCode }, 'The serializer for the given status code failed')
|
||||
}
|
||||
reply.code(500)
|
||||
payload = serializeError(new FST_ERR_FAILED_ERROR_SERIALIZATION(err.message, error.message))
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
|
||||
payload = serializeError(new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload))
|
||||
}
|
||||
|
||||
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
||||
|
||||
cb(reply, payload)
|
||||
}
|
||||
|
||||
function buildErrorHandler (parent = rootErrorHandler, func) {
|
||||
if (!func) {
|
||||
return parent
|
||||
}
|
||||
|
||||
const errorHandler = Object.create(parent)
|
||||
errorHandler.func = func
|
||||
return errorHandler
|
||||
}
|
||||
|
||||
function setErrorHeaders (error, reply) {
|
||||
const res = reply.raw
|
||||
let statusCode = res.statusCode
|
||||
statusCode = (statusCode >= 400) ? statusCode : 500
|
||||
// treat undefined and null as same
|
||||
if (error != null) {
|
||||
if (error.headers !== undefined) {
|
||||
reply.headers(error.headers)
|
||||
}
|
||||
if (error.status >= 400) {
|
||||
statusCode = error.status
|
||||
} else if (error.statusCode >= 400) {
|
||||
statusCode = error.statusCode
|
||||
}
|
||||
}
|
||||
res.statusCode = statusCode
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildErrorHandler,
|
||||
handleError
|
||||
}
|
||||
120
node_modules/fastify/lib/error-serializer.js
generated
vendored
Normal file
120
node_modules/fastify/lib/error-serializer.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// This file is autogenerated by build/build-error-serializer.js, do not edit
|
||||
/* c8 ignore start */
|
||||
|
||||
'use strict'
|
||||
|
||||
const Serializer = require('fast-json-stringify/lib/serializer')
|
||||
const serializerState = {"mode":"standalone"}
|
||||
const serializer = Serializer.restoreFromState(serializerState)
|
||||
|
||||
const validator = null
|
||||
|
||||
|
||||
module.exports = function anonymous(validator,serializer
|
||||
) {
|
||||
|
||||
const JSON_STR_BEGIN_OBJECT = '{'
|
||||
const JSON_STR_END_OBJECT = '}'
|
||||
const JSON_STR_BEGIN_ARRAY = '['
|
||||
const JSON_STR_END_ARRAY = ']'
|
||||
const JSON_STR_COMMA = ','
|
||||
const JSON_STR_COLONS = ':'
|
||||
const JSON_STR_QUOTE = '"'
|
||||
const JSON_STR_EMPTY_OBJECT = JSON_STR_BEGIN_OBJECT + JSON_STR_END_OBJECT
|
||||
const JSON_STR_EMPTY_ARRAY = JSON_STR_BEGIN_ARRAY + JSON_STR_END_ARRAY
|
||||
const JSON_STR_EMPTY_STRING = JSON_STR_QUOTE + JSON_STR_QUOTE
|
||||
const JSON_STR_NULL = 'null'
|
||||
|
||||
|
||||
|
||||
// #
|
||||
function anonymous0 (input) {
|
||||
const obj = (input && typeof input.toJSON === 'function')
|
||||
? input.toJSON()
|
||||
: input
|
||||
|
||||
if (obj === null) return JSON_STR_EMPTY_OBJECT
|
||||
|
||||
let value
|
||||
let json = JSON_STR_BEGIN_OBJECT
|
||||
let addComma = false
|
||||
|
||||
value = obj["statusCode"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"statusCode\":"
|
||||
json += serializer.asNumber(value)
|
||||
}
|
||||
|
||||
value = obj["code"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"code\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
value = obj["error"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"error\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
value = obj["message"]
|
||||
if (value !== undefined) {
|
||||
!addComma && (addComma = true) || (json += JSON_STR_COMMA)
|
||||
json += "\"message\":"
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
if (value === null) {
|
||||
json += JSON_STR_EMPTY_STRING
|
||||
} else if (value instanceof Date) {
|
||||
json += JSON_STR_QUOTE + value.toISOString() + JSON_STR_QUOTE
|
||||
} else if (value instanceof RegExp) {
|
||||
json += serializer.asString(value.source)
|
||||
} else {
|
||||
json += serializer.asString(value.toString())
|
||||
}
|
||||
} else {
|
||||
json += serializer.asString(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return json + JSON_STR_END_OBJECT
|
||||
|
||||
}
|
||||
|
||||
const main = anonymous0
|
||||
return main
|
||||
|
||||
}(validator, serializer)
|
||||
/* c8 ignore stop */
|
||||
505
node_modules/fastify/lib/errors.js
generated
vendored
Normal file
505
node_modules/fastify/lib/errors.js
generated
vendored
Normal file
@@ -0,0 +1,505 @@
|
||||
'use strict'
|
||||
|
||||
const createError = require('@fastify/error')
|
||||
|
||||
const codes = {
|
||||
/**
|
||||
* Basic
|
||||
*/
|
||||
FST_ERR_NOT_FOUND: createError(
|
||||
'FST_ERR_NOT_FOUND',
|
||||
'Not Found',
|
||||
404
|
||||
),
|
||||
FST_ERR_OPTIONS_NOT_OBJ: createError(
|
||||
'FST_ERR_OPTIONS_NOT_OBJ',
|
||||
'Options must be an object',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_QSP_NOT_FN: createError(
|
||||
'FST_ERR_QSP_NOT_FN',
|
||||
"querystringParser option should be a function, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN: createError(
|
||||
'FST_ERR_SCHEMA_CONTROLLER_BUCKET_OPT_NOT_FN',
|
||||
"schemaController.bucket option should be a function, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN: createError(
|
||||
'FST_ERR_SCHEMA_ERROR_FORMATTER_NOT_FN',
|
||||
"schemaErrorFormatter option should be a non async function. Instead got '%s'.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ: createError(
|
||||
'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_OBJ',
|
||||
"ajv.customOptions option should be an object, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR: createError(
|
||||
'FST_ERR_AJV_CUSTOM_OPTIONS_OPT_NOT_ARR',
|
||||
"ajv.plugins option should be an array, instead got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_VALIDATION: createError(
|
||||
'FST_ERR_VALIDATION',
|
||||
'%s',
|
||||
400
|
||||
),
|
||||
FST_ERR_LISTEN_OPTIONS_INVALID: createError(
|
||||
'FST_ERR_LISTEN_OPTIONS_INVALID',
|
||||
"Invalid listen options: '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ERROR_HANDLER_NOT_FN: createError(
|
||||
'FST_ERR_ERROR_HANDLER_NOT_FN',
|
||||
'Error Handler must be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ERROR_HANDLER_ALREADY_SET: createError(
|
||||
'FST_ERR_ERROR_HANDLER_ALREADY_SET',
|
||||
"Error Handler already set in this scope. Set 'allowErrorHandlerOverride: true' to allow overriding.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* ContentTypeParser
|
||||
*/
|
||||
FST_ERR_CTP_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_CTP_ALREADY_PRESENT',
|
||||
"Content type parser '%s' already present."
|
||||
),
|
||||
FST_ERR_CTP_INVALID_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_TYPE',
|
||||
'The content type should be a string or a RegExp',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_EMPTY_TYPE: createError(
|
||||
'FST_ERR_CTP_EMPTY_TYPE',
|
||||
'The content type cannot be an empty string',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_HANDLER: createError(
|
||||
'FST_ERR_CTP_INVALID_HANDLER',
|
||||
'The content type handler should be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_PARSE_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_PARSE_TYPE',
|
||||
"The body parser can only parse your data as 'string' or 'buffer', you asked '%s' which is not supported.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_CTP_BODY_TOO_LARGE: createError(
|
||||
'FST_ERR_CTP_BODY_TOO_LARGE',
|
||||
'Request body is too large',
|
||||
413,
|
||||
RangeError
|
||||
),
|
||||
FST_ERR_CTP_INVALID_MEDIA_TYPE: createError(
|
||||
'FST_ERR_CTP_INVALID_MEDIA_TYPE',
|
||||
'Unsupported Media Type: %s',
|
||||
415
|
||||
),
|
||||
FST_ERR_CTP_INVALID_CONTENT_LENGTH: createError(
|
||||
'FST_ERR_CTP_INVALID_CONTENT_LENGTH',
|
||||
'Request body size did not match Content-Length',
|
||||
400,
|
||||
RangeError
|
||||
),
|
||||
FST_ERR_CTP_EMPTY_JSON_BODY: createError(
|
||||
'FST_ERR_CTP_EMPTY_JSON_BODY',
|
||||
"Body cannot be empty when content-type is set to 'application/json'",
|
||||
400
|
||||
),
|
||||
FST_ERR_CTP_INVALID_JSON_BODY: createError(
|
||||
'FST_ERR_CTP_INVALID_JSON_BODY',
|
||||
"Body is not valid JSON but content-type is set to 'application/json'",
|
||||
400
|
||||
),
|
||||
FST_ERR_CTP_INSTANCE_ALREADY_STARTED: createError(
|
||||
'FST_ERR_CTP_INSTANCE_ALREADY_STARTED',
|
||||
'Cannot call "%s" when fastify instance is already started!',
|
||||
400
|
||||
),
|
||||
|
||||
/**
|
||||
* decorate
|
||||
*/
|
||||
FST_ERR_DEC_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_DEC_ALREADY_PRESENT',
|
||||
"The decorator '%s' has already been added!"
|
||||
),
|
||||
FST_ERR_DEC_DEPENDENCY_INVALID_TYPE: createError(
|
||||
'FST_ERR_DEC_DEPENDENCY_INVALID_TYPE',
|
||||
"The dependencies of decorator '%s' must be of type Array.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_DEC_MISSING_DEPENDENCY: createError(
|
||||
'FST_ERR_DEC_MISSING_DEPENDENCY',
|
||||
"The decorator is missing dependency '%s'."
|
||||
),
|
||||
FST_ERR_DEC_AFTER_START: createError(
|
||||
'FST_ERR_DEC_AFTER_START',
|
||||
"The decorator '%s' has been added after start!"
|
||||
),
|
||||
FST_ERR_DEC_REFERENCE_TYPE: createError(
|
||||
'FST_ERR_DEC_REFERENCE_TYPE',
|
||||
"The decorator '%s' of type '%s' is a reference type. Use the { getter, setter } interface instead."
|
||||
),
|
||||
FST_ERR_DEC_UNDECLARED: createError(
|
||||
'FST_ERR_DEC_UNDECLARED',
|
||||
"No decorator '%s' has been declared on %s."
|
||||
),
|
||||
|
||||
/**
|
||||
* hooks
|
||||
*/
|
||||
FST_ERR_HOOK_INVALID_TYPE: createError(
|
||||
'FST_ERR_HOOK_INVALID_TYPE',
|
||||
'The hook name must be a string',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_INVALID_HANDLER: createError(
|
||||
'FST_ERR_HOOK_INVALID_HANDLER',
|
||||
'%s hook should be a function, instead got %s',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_INVALID_ASYNC_HANDLER: createError(
|
||||
'FST_ERR_HOOK_INVALID_ASYNC_HANDLER',
|
||||
'Async function has too many arguments. Async hooks should not use the \'done\' argument.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_HOOK_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_HOOK_NOT_SUPPORTED',
|
||||
'%s hook not supported!',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* Middlewares
|
||||
*/
|
||||
FST_ERR_MISSING_MIDDLEWARE: createError(
|
||||
'FST_ERR_MISSING_MIDDLEWARE',
|
||||
'You must register a plugin for handling middlewares, visit fastify.dev/docs/latest/Reference/Middleware/ for more info.',
|
||||
500
|
||||
),
|
||||
|
||||
FST_ERR_HOOK_TIMEOUT: createError(
|
||||
'FST_ERR_HOOK_TIMEOUT',
|
||||
"A callback for '%s' hook%s timed out. You may have forgotten to call 'done' function or to resolve a Promise"
|
||||
),
|
||||
|
||||
/**
|
||||
* logger
|
||||
*/
|
||||
FST_ERR_LOG_INVALID_DESTINATION: createError(
|
||||
'FST_ERR_LOG_INVALID_DESTINATION',
|
||||
'Cannot specify both logger.stream and logger.file options'
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER',
|
||||
"Invalid logger object provided. The logger instance should have these functions(s): '%s'.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER_INSTANCE: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER_INSTANCE',
|
||||
'loggerInstance only accepts a logger instance.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_INVALID_LOGGER_CONFIG: createError(
|
||||
'FST_ERR_LOG_INVALID_LOGGER_CONFIG',
|
||||
'logger options only accepts a configuration object.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED: createError(
|
||||
'FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED',
|
||||
'You cannot provide both logger and loggerInstance. Please provide only one.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* reply
|
||||
*/
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE: createError(
|
||||
'FST_ERR_REP_INVALID_PAYLOAD_TYPE',
|
||||
"Attempted to send payload of invalid type '%s'. Expected a string or Buffer.",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_REP_RESPONSE_BODY_CONSUMED: createError(
|
||||
'FST_ERR_REP_RESPONSE_BODY_CONSUMED',
|
||||
'Response.body is already consumed.'
|
||||
),
|
||||
FST_ERR_REP_READABLE_STREAM_LOCKED: createError(
|
||||
'FST_ERR_REP_READABLE_STREAM_LOCKED',
|
||||
'ReadableStream was locked. You should call releaseLock() method on reader before sending.'
|
||||
),
|
||||
FST_ERR_REP_ALREADY_SENT: createError(
|
||||
'FST_ERR_REP_ALREADY_SENT',
|
||||
'Reply was already sent, did you forget to "return reply" in "%s" (%s)?'
|
||||
),
|
||||
FST_ERR_REP_SENT_VALUE: createError(
|
||||
'FST_ERR_REP_SENT_VALUE',
|
||||
'The only possible value for reply.sent is true.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_SEND_INSIDE_ONERR: createError(
|
||||
'FST_ERR_SEND_INSIDE_ONERR',
|
||||
'You cannot use `send` inside the `onError` hook'
|
||||
),
|
||||
FST_ERR_SEND_UNDEFINED_ERR: createError(
|
||||
'FST_ERR_SEND_UNDEFINED_ERR',
|
||||
'Undefined error has occurred'
|
||||
),
|
||||
FST_ERR_BAD_STATUS_CODE: createError(
|
||||
'FST_ERR_BAD_STATUS_CODE',
|
||||
'Called reply with an invalid status code: %s'
|
||||
),
|
||||
FST_ERR_BAD_TRAILER_NAME: createError(
|
||||
'FST_ERR_BAD_TRAILER_NAME',
|
||||
'Called reply.trailer with an invalid header name: %s'
|
||||
),
|
||||
FST_ERR_BAD_TRAILER_VALUE: createError(
|
||||
'FST_ERR_BAD_TRAILER_VALUE',
|
||||
"Called reply.trailer('%s', fn) with an invalid type: %s. Expected a function."
|
||||
),
|
||||
FST_ERR_FAILED_ERROR_SERIALIZATION: createError(
|
||||
'FST_ERR_FAILED_ERROR_SERIALIZATION',
|
||||
'Failed to serialize an error. Error: %s. Original error: %s'
|
||||
),
|
||||
FST_ERR_MISSING_SERIALIZATION_FN: createError(
|
||||
'FST_ERR_MISSING_SERIALIZATION_FN',
|
||||
'Missing serialization function. Key "%s"'
|
||||
),
|
||||
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN: createError(
|
||||
'FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN',
|
||||
'Missing serialization function. Key "%s:%s"'
|
||||
),
|
||||
FST_ERR_REQ_INVALID_VALIDATION_INVOCATION: createError(
|
||||
'FST_ERR_REQ_INVALID_VALIDATION_INVOCATION',
|
||||
'Invalid validation invocation. Missing validation function for HTTP part "%s" nor schema provided.'
|
||||
),
|
||||
|
||||
/**
|
||||
* schemas
|
||||
*/
|
||||
FST_ERR_SCH_MISSING_ID: createError(
|
||||
'FST_ERR_SCH_MISSING_ID',
|
||||
'Missing schema $id property'
|
||||
),
|
||||
FST_ERR_SCH_ALREADY_PRESENT: createError(
|
||||
'FST_ERR_SCH_ALREADY_PRESENT',
|
||||
"Schema with id '%s' already declared!"
|
||||
),
|
||||
FST_ERR_SCH_CONTENT_MISSING_SCHEMA: createError(
|
||||
'FST_ERR_SCH_CONTENT_MISSING_SCHEMA',
|
||||
"Schema is missing for the content type '%s'"
|
||||
),
|
||||
FST_ERR_SCH_DUPLICATE: createError(
|
||||
'FST_ERR_SCH_DUPLICATE',
|
||||
"Schema with '%s' already present!"
|
||||
),
|
||||
FST_ERR_SCH_VALIDATION_BUILD: createError(
|
||||
'FST_ERR_SCH_VALIDATION_BUILD',
|
||||
'Failed building the validation schema for %s: %s, due to error %s'
|
||||
),
|
||||
FST_ERR_SCH_SERIALIZATION_BUILD: createError(
|
||||
'FST_ERR_SCH_SERIALIZATION_BUILD',
|
||||
'Failed building the serialization schema for %s: %s, due to error %s'
|
||||
),
|
||||
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX: createError(
|
||||
'FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX',
|
||||
'response schemas should be nested under a valid status code, e.g { 2xx: { type: "object" } }'
|
||||
),
|
||||
|
||||
/**
|
||||
* initialConfig
|
||||
*/
|
||||
FST_ERR_INIT_OPTS_INVALID: createError(
|
||||
'FST_ERR_INIT_OPTS_INVALID',
|
||||
"Invalid initialization options: '%s'"
|
||||
),
|
||||
FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE: createError(
|
||||
'FST_ERR_FORCE_CLOSE_CONNECTIONS_IDLE_NOT_AVAILABLE',
|
||||
"Cannot set forceCloseConnections to 'idle' as your HTTP server does not support closeIdleConnections method"
|
||||
),
|
||||
|
||||
/**
|
||||
* router
|
||||
*/
|
||||
FST_ERR_DUPLICATED_ROUTE: createError(
|
||||
'FST_ERR_DUPLICATED_ROUTE',
|
||||
"Method '%s' already declared for route '%s'"
|
||||
),
|
||||
FST_ERR_BAD_URL: createError(
|
||||
'FST_ERR_BAD_URL',
|
||||
"'%s' is not a valid url component",
|
||||
400,
|
||||
URIError
|
||||
),
|
||||
FST_ERR_ASYNC_CONSTRAINT: createError(
|
||||
'FST_ERR_ASYNC_CONSTRAINT',
|
||||
'Unexpected error from async constraint',
|
||||
500
|
||||
),
|
||||
FST_ERR_INVALID_URL: createError(
|
||||
'FST_ERR_INVALID_URL',
|
||||
"URL must be a string. Received '%s'",
|
||||
400,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_OPTIONS_NOT_OBJ: createError(
|
||||
'FST_ERR_ROUTE_OPTIONS_NOT_OBJ',
|
||||
'Options for "%s:%s" route must be an object',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_DUPLICATED_HANDLER: createError(
|
||||
'FST_ERR_ROUTE_DUPLICATED_HANDLER',
|
||||
'Duplicate handler for "%s:%s" route is not allowed!',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_HANDLER_NOT_FN: createError(
|
||||
'FST_ERR_ROUTE_HANDLER_NOT_FN',
|
||||
'Error Handler for %s:%s route, if defined, must be a function',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_MISSING_HANDLER: createError(
|
||||
'FST_ERR_ROUTE_MISSING_HANDLER',
|
||||
'Missing handler function for "%s:%s" route.',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_METHOD_INVALID: createError(
|
||||
'FST_ERR_ROUTE_METHOD_INVALID',
|
||||
'Provided method is invalid!',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_METHOD_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_ROUTE_METHOD_NOT_SUPPORTED',
|
||||
'%s method is not supported.',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED: createError(
|
||||
'FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED',
|
||||
'Body validation schema for %s:%s route is not supported!',
|
||||
500
|
||||
),
|
||||
FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT: createError(
|
||||
'FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT',
|
||||
"'bodyLimit' option must be an integer > 0. Got '%s'",
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_ROUTE_REWRITE_NOT_STR: createError(
|
||||
'FST_ERR_ROUTE_REWRITE_NOT_STR',
|
||||
'Rewrite url for "%s" needs to be of type "string" but received "%s"',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* again listen when close server
|
||||
*/
|
||||
FST_ERR_REOPENED_CLOSE_SERVER: createError(
|
||||
'FST_ERR_REOPENED_CLOSE_SERVER',
|
||||
'Fastify has already been closed and cannot be reopened'
|
||||
),
|
||||
FST_ERR_REOPENED_SERVER: createError(
|
||||
'FST_ERR_REOPENED_SERVER',
|
||||
'Fastify is already listening'
|
||||
),
|
||||
FST_ERR_INSTANCE_ALREADY_LISTENING: createError(
|
||||
'FST_ERR_INSTANCE_ALREADY_LISTENING',
|
||||
'Fastify instance is already listening. %s'
|
||||
),
|
||||
|
||||
/**
|
||||
* plugin
|
||||
*/
|
||||
FST_ERR_PLUGIN_VERSION_MISMATCH: createError(
|
||||
'FST_ERR_PLUGIN_VERSION_MISMATCH',
|
||||
"fastify-plugin: %s - expected '%s' fastify version, '%s' is installed"
|
||||
),
|
||||
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE: createError(
|
||||
'FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE',
|
||||
"The decorator '%s'%s is not present in %s"
|
||||
),
|
||||
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER: createError(
|
||||
'FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER',
|
||||
'The %s plugin being registered mixes async and callback styles. Async plugin should not mix async and callback style.',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
|
||||
/**
|
||||
* Avvio Errors
|
||||
*/
|
||||
FST_ERR_PLUGIN_CALLBACK_NOT_FN: createError(
|
||||
'FST_ERR_PLUGIN_CALLBACK_NOT_FN',
|
||||
'fastify-plugin: %s',
|
||||
500,
|
||||
TypeError
|
||||
),
|
||||
FST_ERR_PLUGIN_NOT_VALID: createError(
|
||||
'FST_ERR_PLUGIN_NOT_VALID',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_ROOT_PLG_BOOTED: createError(
|
||||
'FST_ERR_ROOT_PLG_BOOTED',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_PARENT_PLUGIN_BOOTED: createError(
|
||||
'FST_ERR_PARENT_PLUGIN_BOOTED',
|
||||
'fastify-plugin: %s'
|
||||
),
|
||||
FST_ERR_PLUGIN_TIMEOUT: createError(
|
||||
'FST_ERR_PLUGIN_TIMEOUT',
|
||||
'fastify-plugin: %s'
|
||||
)
|
||||
}
|
||||
|
||||
function appendStackTrace (oldErr, newErr) {
|
||||
newErr.cause = oldErr
|
||||
|
||||
return newErr
|
||||
}
|
||||
|
||||
module.exports = codes
|
||||
module.exports.appendStackTrace = appendStackTrace
|
||||
module.exports.AVVIO_ERRORS_MAP = {
|
||||
AVV_ERR_CALLBACK_NOT_FN: codes.FST_ERR_PLUGIN_CALLBACK_NOT_FN,
|
||||
AVV_ERR_PLUGIN_NOT_VALID: codes.FST_ERR_PLUGIN_NOT_VALID,
|
||||
AVV_ERR_ROOT_PLG_BOOTED: codes.FST_ERR_ROOT_PLG_BOOTED,
|
||||
AVV_ERR_PARENT_PLG_LOADED: codes.FST_ERR_PARENT_PLUGIN_BOOTED,
|
||||
AVV_ERR_READY_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT,
|
||||
AVV_ERR_PLUGIN_EXEC_TIMEOUT: codes.FST_ERR_PLUGIN_TIMEOUT
|
||||
}
|
||||
187
node_modules/fastify/lib/fourOhFour.js
generated
vendored
Normal file
187
node_modules/fastify/lib/fourOhFour.js
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
'use strict'
|
||||
|
||||
const FindMyWay = require('find-my-way')
|
||||
|
||||
const Reply = require('./reply')
|
||||
const Request = require('./request')
|
||||
const Context = require('./context')
|
||||
const {
|
||||
kRoutePrefix,
|
||||
kCanSetNotFoundHandler,
|
||||
kFourOhFourLevelInstance,
|
||||
kFourOhFourContext,
|
||||
kHooks,
|
||||
kErrorHandler
|
||||
} = require('./symbols.js')
|
||||
const { lifecycleHooks } = require('./hooks')
|
||||
const { buildErrorHandler } = require('./error-handler.js')
|
||||
const {
|
||||
FST_ERR_NOT_FOUND
|
||||
} = require('./errors')
|
||||
const { createChildLogger } = require('./logger-factory')
|
||||
const { getGenReqId } = require('./reqIdGenFactory.js')
|
||||
|
||||
/**
|
||||
* Each fastify instance have a:
|
||||
* kFourOhFourLevelInstance: point to a fastify instance that has the 404 handler set
|
||||
* kCanSetNotFoundHandler: bool to track if the 404 handler has already been set
|
||||
* kFourOhFour: the singleton instance of this 404 module
|
||||
* kFourOhFourContext: the context in the reply object where the handler will be executed
|
||||
*/
|
||||
function fourOhFour (options) {
|
||||
const { logger, disableRequestLogging } = options
|
||||
|
||||
// 404 router, used for handling encapsulated 404 handlers
|
||||
const router = FindMyWay({ onBadUrl: createOnBadUrl(), defaultRoute: fourOhFourFallBack })
|
||||
let _onBadUrlHandler = null
|
||||
|
||||
return { router, setNotFoundHandler, setContext, arrange404 }
|
||||
|
||||
function arrange404 (instance) {
|
||||
// Change the pointer of the fastify instance to itself, so register + prefix can add new 404 handler
|
||||
instance[kFourOhFourLevelInstance] = instance
|
||||
instance[kCanSetNotFoundHandler] = true
|
||||
// we need to bind instance for the context
|
||||
router.onBadUrl = router.onBadUrl.bind(instance)
|
||||
router.defaultRoute = router.defaultRoute.bind(instance)
|
||||
}
|
||||
|
||||
function basic404 (request, reply) {
|
||||
const { url, method } = request.raw
|
||||
const message = `Route ${method}:${url} not found`
|
||||
if (!disableRequestLogging) {
|
||||
request.log.info(message)
|
||||
}
|
||||
reply.code(404).send({
|
||||
message,
|
||||
error: 'Not Found',
|
||||
statusCode: 404
|
||||
})
|
||||
}
|
||||
|
||||
function createOnBadUrl () {
|
||||
return function onBadUrl (path, req, res) {
|
||||
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
||||
const id = getGenReqId(fourOhFourContext.server, req)
|
||||
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
||||
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
_onBadUrlHandler(request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function setContext (instance, context) {
|
||||
const _404Context = Object.assign({}, instance[kFourOhFourContext])
|
||||
_404Context.onSend = context.onSend
|
||||
context[kFourOhFourContext] = _404Context
|
||||
}
|
||||
|
||||
function setNotFoundHandler (opts, handler, avvio, routeHandler) {
|
||||
// First initialization of the fastify root instance
|
||||
if (this[kCanSetNotFoundHandler] === undefined) {
|
||||
this[kCanSetNotFoundHandler] = true
|
||||
}
|
||||
if (this[kFourOhFourContext] === undefined) {
|
||||
this[kFourOhFourContext] = null
|
||||
}
|
||||
|
||||
const _fastify = this
|
||||
const prefix = this[kRoutePrefix] || '/'
|
||||
|
||||
if (this[kCanSetNotFoundHandler] === false) {
|
||||
throw new Error(`Not found handler already set for Fastify instance with prefix: '${prefix}'`)
|
||||
}
|
||||
|
||||
if (typeof opts === 'object') {
|
||||
if (opts.preHandler) {
|
||||
if (Array.isArray(opts.preHandler)) {
|
||||
opts.preHandler = opts.preHandler.map(hook => hook.bind(_fastify))
|
||||
} else {
|
||||
opts.preHandler = opts.preHandler.bind(_fastify)
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.preValidation) {
|
||||
if (Array.isArray(opts.preValidation)) {
|
||||
opts.preValidation = opts.preValidation.map(hook => hook.bind(_fastify))
|
||||
} else {
|
||||
opts.preValidation = opts.preValidation.bind(_fastify)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
handler = opts
|
||||
opts = undefined
|
||||
}
|
||||
opts = opts || {}
|
||||
|
||||
if (handler) {
|
||||
this[kFourOhFourLevelInstance][kCanSetNotFoundHandler] = false
|
||||
handler = handler.bind(this)
|
||||
// update onBadUrl handler
|
||||
_onBadUrlHandler = handler
|
||||
} else {
|
||||
handler = basic404
|
||||
// update onBadUrl handler
|
||||
_onBadUrlHandler = basic404
|
||||
}
|
||||
|
||||
this.after((notHandledErr, done) => {
|
||||
_setNotFoundHandler.call(this, prefix, opts, handler, avvio, routeHandler)
|
||||
done(notHandledErr)
|
||||
})
|
||||
}
|
||||
|
||||
function _setNotFoundHandler (prefix, opts, handler, avvio, routeHandler) {
|
||||
const context = new Context({
|
||||
schema: opts.schema,
|
||||
handler,
|
||||
config: opts.config || {},
|
||||
server: this
|
||||
})
|
||||
|
||||
avvio.once('preReady', () => {
|
||||
const context = this[kFourOhFourContext]
|
||||
for (const hook of lifecycleHooks) {
|
||||
const toSet = this[kHooks][hook]
|
||||
.concat(opts[hook] || [])
|
||||
.map(h => h.bind(this))
|
||||
context[hook] = toSet.length ? toSet : null
|
||||
}
|
||||
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
|
||||
})
|
||||
|
||||
if (this[kFourOhFourContext] !== null && prefix === '/') {
|
||||
Object.assign(this[kFourOhFourContext], context) // Replace the default 404 handler
|
||||
return
|
||||
}
|
||||
|
||||
this[kFourOhFourLevelInstance][kFourOhFourContext] = context
|
||||
|
||||
router.all(prefix + (prefix.endsWith('/') ? '*' : '/*'), routeHandler, context)
|
||||
router.all(prefix, routeHandler, context)
|
||||
}
|
||||
|
||||
function fourOhFourFallBack (req, res) {
|
||||
// if this happen, we have a very bad bug
|
||||
// we might want to do some hard debugging
|
||||
// here, let's print out as much info as
|
||||
// we can
|
||||
const fourOhFourContext = this[kFourOhFourLevelInstance][kFourOhFourContext]
|
||||
const id = getGenReqId(fourOhFourContext.server, req)
|
||||
const childLogger = createChildLogger(fourOhFourContext, logger, req, id)
|
||||
|
||||
childLogger.info({ req }, 'incoming request')
|
||||
|
||||
const request = new Request(id, null, req, null, childLogger, fourOhFourContext)
|
||||
const reply = new Reply(res, request, childLogger)
|
||||
|
||||
request.log.warn('the default handler for 404 did not catch this, this is likely a fastify bug, please report it')
|
||||
request.log.warn(router.prettyPrint())
|
||||
reply.code(404).send(new FST_ERR_NOT_FOUND())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fourOhFour
|
||||
182
node_modules/fastify/lib/handleRequest.js
generated
vendored
Normal file
182
node_modules/fastify/lib/handleRequest.js
generated
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
'use strict'
|
||||
|
||||
const diagnostics = require('node:diagnostics_channel')
|
||||
const { validate: validateSchema } = require('./validation')
|
||||
const { preValidationHookRunner, preHandlerHookRunner } = require('./hooks')
|
||||
const wrapThenable = require('./wrapThenable')
|
||||
const {
|
||||
kReplyIsError,
|
||||
kRouteContext,
|
||||
kFourOhFourContext,
|
||||
kSupportedHTTPMethods
|
||||
} = require('./symbols')
|
||||
|
||||
const channels = diagnostics.tracingChannel('fastify.request.handler')
|
||||
|
||||
function handleRequest (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
const method = request.method
|
||||
|
||||
if (this[kSupportedHTTPMethods].bodyless.has(method)) {
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (this[kSupportedHTTPMethods].bodywith.has(method)) {
|
||||
const headers = request.headers
|
||||
const contentType = headers['content-type']
|
||||
|
||||
if (contentType === undefined) {
|
||||
const contentLength = headers['content-length']
|
||||
const transferEncoding = headers['transfer-encoding']
|
||||
const isEmptyBody = transferEncoding === undefined &&
|
||||
(contentLength === undefined || contentLength === '0')
|
||||
|
||||
if (isEmptyBody) {
|
||||
// Request has no body to parse
|
||||
handler(request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRouteContext].contentTypeParser.run('', handler, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRouteContext].contentTypeParser.run(contentType, handler, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
// Return 404 instead of 405 see https://github.com/fastify/fastify/pull/862 for discussion
|
||||
handler(request, reply)
|
||||
}
|
||||
|
||||
function handler (request, reply) {
|
||||
try {
|
||||
if (request[kRouteContext].preValidation !== null) {
|
||||
preValidationHookRunner(
|
||||
request[kRouteContext].preValidation,
|
||||
request,
|
||||
reply,
|
||||
preValidationCallback
|
||||
)
|
||||
} else {
|
||||
preValidationCallback(null, request, reply)
|
||||
}
|
||||
} catch (err) {
|
||||
preValidationCallback(err, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function preValidationCallback (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
const validationErr = validateSchema(reply[kRouteContext], request)
|
||||
const isAsync = (validationErr && typeof validationErr.then === 'function') || false
|
||||
|
||||
if (isAsync) {
|
||||
const cb = validationCompleted.bind(null, request, reply)
|
||||
validationErr.then(cb, cb)
|
||||
} else {
|
||||
validationCompleted(request, reply, validationErr)
|
||||
}
|
||||
}
|
||||
|
||||
function validationCompleted (request, reply, validationErr) {
|
||||
if (validationErr) {
|
||||
if (reply[kRouteContext].attachValidation === false) {
|
||||
reply.send(validationErr)
|
||||
return
|
||||
}
|
||||
|
||||
reply.request.validationError = validationErr
|
||||
}
|
||||
|
||||
// preHandler hook
|
||||
if (request[kRouteContext].preHandler !== null) {
|
||||
preHandlerHookRunner(
|
||||
request[kRouteContext].preHandler,
|
||||
request,
|
||||
reply,
|
||||
preHandlerCallback
|
||||
)
|
||||
} else {
|
||||
preHandlerCallback(null, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function preHandlerCallback (err, request, reply) {
|
||||
if (reply.sent) return
|
||||
|
||||
const context = request[kRouteContext]
|
||||
|
||||
if (!channels.hasSubscribers || context[kFourOhFourContext] === null) {
|
||||
preHandlerCallbackInner(err, request, reply)
|
||||
} else {
|
||||
const store = {
|
||||
request,
|
||||
reply,
|
||||
async: false,
|
||||
route: {
|
||||
url: context.config.url,
|
||||
method: context.config.method
|
||||
}
|
||||
}
|
||||
channels.start.runStores(store, preHandlerCallbackInner, undefined, err, request, reply, store)
|
||||
}
|
||||
}
|
||||
|
||||
function preHandlerCallbackInner (err, request, reply, store) {
|
||||
const context = request[kRouteContext]
|
||||
|
||||
try {
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
if (store) {
|
||||
store.error = err
|
||||
channels.error.publish(store)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
|
||||
try {
|
||||
result = context.handler(request, reply)
|
||||
} catch (err) {
|
||||
if (store) {
|
||||
store.error = err
|
||||
channels.error.publish(store)
|
||||
}
|
||||
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (result !== undefined) {
|
||||
if (result !== null && typeof result.then === 'function') {
|
||||
wrapThenable(result, reply, store)
|
||||
} else {
|
||||
reply.send(result)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (store) channels.end.publish(store)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = handleRequest
|
||||
module.exports[Symbol.for('internals')] = { handler, preHandlerCallback }
|
||||
33
node_modules/fastify/lib/headRoute.js
generated
vendored
Normal file
33
node_modules/fastify/lib/headRoute.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
'use strict'
|
||||
function headRouteOnSendHandler (req, reply, payload, done) {
|
||||
// If payload is undefined
|
||||
if (payload === undefined) {
|
||||
reply.header('content-length', '0')
|
||||
done(null, null)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof payload.resume === 'function') {
|
||||
payload.on('error', (err) => {
|
||||
reply.log.error({ err }, 'Error on Stream found for HEAD route')
|
||||
})
|
||||
payload.resume()
|
||||
done(null, null)
|
||||
return
|
||||
}
|
||||
|
||||
const size = '' + Buffer.byteLength(payload)
|
||||
|
||||
reply.header('content-length', size)
|
||||
|
||||
done(null, null)
|
||||
}
|
||||
|
||||
function parseHeadOnSendHandlers (onSendHandlers) {
|
||||
if (onSendHandlers == null) return headRouteOnSendHandler
|
||||
return Array.isArray(onSendHandlers) ? [...onSendHandlers, headRouteOnSendHandler] : [onSendHandlers, headRouteOnSendHandler]
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseHeadOnSendHandlers
|
||||
}
|
||||
429
node_modules/fastify/lib/hooks.js
generated
vendored
Normal file
429
node_modules/fastify/lib/hooks.js
generated
vendored
Normal file
@@ -0,0 +1,429 @@
|
||||
'use strict'
|
||||
|
||||
const applicationHooks = [
|
||||
'onRoute',
|
||||
'onRegister',
|
||||
'onReady',
|
||||
'onListen',
|
||||
'preClose',
|
||||
'onClose'
|
||||
]
|
||||
const lifecycleHooks = [
|
||||
'onTimeout',
|
||||
'onRequest',
|
||||
'preParsing',
|
||||
'preValidation',
|
||||
'preSerialization',
|
||||
'preHandler',
|
||||
'onSend',
|
||||
'onResponse',
|
||||
'onError',
|
||||
'onRequestAbort'
|
||||
]
|
||||
const supportedHooks = lifecycleHooks.concat(applicationHooks)
|
||||
const {
|
||||
FST_ERR_HOOK_INVALID_TYPE,
|
||||
FST_ERR_HOOK_INVALID_HANDLER,
|
||||
FST_ERR_SEND_UNDEFINED_ERR,
|
||||
FST_ERR_HOOK_TIMEOUT,
|
||||
FST_ERR_HOOK_NOT_SUPPORTED,
|
||||
AVVIO_ERRORS_MAP,
|
||||
appendStackTrace
|
||||
} = require('./errors')
|
||||
|
||||
const {
|
||||
kChildren,
|
||||
kHooks,
|
||||
kRequestPayloadStream
|
||||
} = require('./symbols')
|
||||
|
||||
function Hooks () {
|
||||
this.onRequest = []
|
||||
this.preParsing = []
|
||||
this.preValidation = []
|
||||
this.preSerialization = []
|
||||
this.preHandler = []
|
||||
this.onResponse = []
|
||||
this.onSend = []
|
||||
this.onError = []
|
||||
this.onRoute = []
|
||||
this.onRegister = []
|
||||
this.onReady = []
|
||||
this.onListen = []
|
||||
this.onTimeout = []
|
||||
this.onRequestAbort = []
|
||||
this.preClose = []
|
||||
}
|
||||
|
||||
Hooks.prototype = Object.create(null)
|
||||
|
||||
Hooks.prototype.validate = function (hook, fn) {
|
||||
if (typeof hook !== 'string') throw new FST_ERR_HOOK_INVALID_TYPE()
|
||||
if (Array.isArray(this[hook]) === false) {
|
||||
throw new FST_ERR_HOOK_NOT_SUPPORTED(hook)
|
||||
}
|
||||
if (typeof fn !== 'function') throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(fn))
|
||||
}
|
||||
|
||||
Hooks.prototype.add = function (hook, fn) {
|
||||
this.validate(hook, fn)
|
||||
this[hook].push(fn)
|
||||
}
|
||||
|
||||
function buildHooks (h) {
|
||||
const hooks = new Hooks()
|
||||
hooks.onRequest = h.onRequest.slice()
|
||||
hooks.preParsing = h.preParsing.slice()
|
||||
hooks.preValidation = h.preValidation.slice()
|
||||
hooks.preSerialization = h.preSerialization.slice()
|
||||
hooks.preHandler = h.preHandler.slice()
|
||||
hooks.onSend = h.onSend.slice()
|
||||
hooks.onResponse = h.onResponse.slice()
|
||||
hooks.onError = h.onError.slice()
|
||||
hooks.onRoute = h.onRoute.slice()
|
||||
hooks.onRegister = h.onRegister.slice()
|
||||
hooks.onTimeout = h.onTimeout.slice()
|
||||
hooks.onRequestAbort = h.onRequestAbort.slice()
|
||||
hooks.onReady = []
|
||||
hooks.onListen = []
|
||||
hooks.preClose = []
|
||||
return hooks
|
||||
}
|
||||
|
||||
function hookRunnerApplication (hookName, boot, server, cb) {
|
||||
const hooks = server[kHooks][hookName]
|
||||
let i = 0
|
||||
let c = 0
|
||||
|
||||
next()
|
||||
|
||||
function exit (err) {
|
||||
const hookFnName = hooks[i - 1]?.name
|
||||
const hookFnFragment = hookFnName ? ` "${hookFnName}"` : ''
|
||||
|
||||
if (err) {
|
||||
if (err.code === 'AVV_ERR_READY_TIMEOUT') {
|
||||
err = appendStackTrace(err, new FST_ERR_HOOK_TIMEOUT(hookName, hookFnFragment))
|
||||
} else {
|
||||
err = AVVIO_ERRORS_MAP[err.code] != null
|
||||
? appendStackTrace(err, new AVVIO_ERRORS_MAP[err.code](err.message))
|
||||
: err
|
||||
}
|
||||
|
||||
cb(err)
|
||||
return
|
||||
}
|
||||
cb()
|
||||
}
|
||||
|
||||
function next (err) {
|
||||
if (err) {
|
||||
exit(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (i === hooks.length && c === server[kChildren].length) {
|
||||
if (i === 0 && c === 0) { // speed up start
|
||||
exit()
|
||||
} else {
|
||||
// This is the last function executed for every fastify instance
|
||||
boot(function manageTimeout (err, done) {
|
||||
// this callback is needed by fastify to provide an hook interface without the error
|
||||
// as first parameter and managing it on behalf the user
|
||||
exit(err)
|
||||
|
||||
// this callback is needed by avvio to continue the loading of the next `register` plugins
|
||||
done(err)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (i === hooks.length && c < server[kChildren].length) {
|
||||
const child = server[kChildren][c++]
|
||||
hookRunnerApplication(hookName, boot, child, next)
|
||||
return
|
||||
}
|
||||
|
||||
boot(wrap(hooks[i++], server))
|
||||
next()
|
||||
}
|
||||
|
||||
function wrap (fn, server) {
|
||||
return function (err, done) {
|
||||
if (err) {
|
||||
done(err)
|
||||
return
|
||||
}
|
||||
|
||||
if (fn.length === 1) {
|
||||
try {
|
||||
fn.call(server, done)
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const ret = fn.call(server)
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.then(done, done)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
|
||||
done(err) // auto done
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onListenHookRunner (server) {
|
||||
const hooks = server[kHooks].onListen
|
||||
const hooksLen = hooks.length
|
||||
|
||||
let i = 0
|
||||
let c = 0
|
||||
|
||||
next()
|
||||
|
||||
function next (err) {
|
||||
err && server.log.error(err)
|
||||
|
||||
if (
|
||||
i === hooksLen
|
||||
) {
|
||||
while (c < server[kChildren].length) {
|
||||
const child = server[kChildren][c++]
|
||||
onListenHookRunner(child)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
wrap(hooks[i++], server, next)
|
||||
}
|
||||
|
||||
async function wrap (fn, server, done) {
|
||||
if (fn.length === 1) {
|
||||
try {
|
||||
fn.call(server, done)
|
||||
} catch (e) {
|
||||
done(e)
|
||||
}
|
||||
return
|
||||
}
|
||||
try {
|
||||
const ret = fn.call(server)
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
ret.then(done, done)
|
||||
return
|
||||
}
|
||||
done()
|
||||
} catch (error) {
|
||||
done(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hookRunnerGenerator (iterator) {
|
||||
return function hookRunner (functions, request, reply, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err) {
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = iterator(functions[i++], request, reply, next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve () {
|
||||
next()
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
function onResponseHookIterator (fn, request, reply, next) {
|
||||
return fn(request, reply, next)
|
||||
}
|
||||
|
||||
const onResponseHookRunner = hookRunnerGenerator(onResponseHookIterator)
|
||||
const preValidationHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const preHandlerHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const onTimeoutHookRunner = hookRunnerGenerator(hookIterator)
|
||||
const onRequestHookRunner = hookRunnerGenerator(hookIterator)
|
||||
|
||||
function onSendHookRunner (functions, request, reply, payload, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err, newPayload) {
|
||||
if (err) {
|
||||
cb(err, request, reply, payload)
|
||||
return
|
||||
}
|
||||
|
||||
if (newPayload !== undefined) {
|
||||
payload = newPayload
|
||||
}
|
||||
|
||||
if (i === functions.length) {
|
||||
cb(null, request, reply, payload)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, reply, payload, next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve (newPayload) {
|
||||
next(null, newPayload)
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply, payload)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
const preSerializationHookRunner = onSendHookRunner
|
||||
|
||||
function preParsingHookRunner (functions, request, reply, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err, newPayload) {
|
||||
if (reply.sent) {
|
||||
return
|
||||
}
|
||||
|
||||
if (newPayload !== undefined) {
|
||||
request[kRequestPayloadStream] = newPayload
|
||||
}
|
||||
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, reply, request[kRequestPayloadStream], next)
|
||||
} catch (error) {
|
||||
cb(error, request, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve (newPayload) {
|
||||
next(null, newPayload)
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request, reply)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
function onRequestAbortHookRunner (functions, request, cb) {
|
||||
let i = 0
|
||||
|
||||
function next (err) {
|
||||
if (err || i === functions.length) {
|
||||
cb(err, request)
|
||||
return
|
||||
}
|
||||
|
||||
let result
|
||||
try {
|
||||
result = functions[i++](request, next)
|
||||
} catch (error) {
|
||||
cb(error, request)
|
||||
return
|
||||
}
|
||||
if (result && typeof result.then === 'function') {
|
||||
result.then(handleResolve, handleReject)
|
||||
}
|
||||
}
|
||||
|
||||
function handleResolve () {
|
||||
next()
|
||||
}
|
||||
|
||||
function handleReject (err) {
|
||||
if (!err) {
|
||||
err = new FST_ERR_SEND_UNDEFINED_ERR()
|
||||
}
|
||||
|
||||
cb(err, request)
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
function hookIterator (fn, request, reply, next) {
|
||||
if (reply.sent === true) return undefined
|
||||
return fn(request, reply, next)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
Hooks,
|
||||
buildHooks,
|
||||
hookRunnerGenerator,
|
||||
preParsingHookRunner,
|
||||
onResponseHookRunner,
|
||||
onSendHookRunner,
|
||||
preSerializationHookRunner,
|
||||
onRequestAbortHookRunner,
|
||||
hookIterator,
|
||||
hookRunnerApplication,
|
||||
onListenHookRunner,
|
||||
preHandlerHookRunner,
|
||||
preValidationHookRunner,
|
||||
onRequestHookRunner,
|
||||
onTimeoutHookRunner,
|
||||
lifecycleHooks,
|
||||
supportedHooks
|
||||
}
|
||||
37
node_modules/fastify/lib/initialConfigValidation.js
generated
vendored
Normal file
37
node_modules/fastify/lib/initialConfigValidation.js
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const validate = require('./configValidator')
|
||||
const deepClone = require('rfdc')({ circles: true, proto: false })
|
||||
const { FST_ERR_INIT_OPTS_INVALID } = require('./errors')
|
||||
|
||||
function validateInitialConfig (options) {
|
||||
const opts = deepClone(options)
|
||||
|
||||
if (!validate(opts)) {
|
||||
const error = new FST_ERR_INIT_OPTS_INVALID(JSON.stringify(validate.errors.map(e => e.message)))
|
||||
error.errors = validate.errors
|
||||
throw error
|
||||
}
|
||||
|
||||
return deepFreezeObject(opts)
|
||||
}
|
||||
|
||||
function deepFreezeObject (object) {
|
||||
const properties = Object.getOwnPropertyNames(object)
|
||||
|
||||
for (const name of properties) {
|
||||
const value = object[name]
|
||||
|
||||
if (ArrayBuffer.isView(value) && !(value instanceof DataView)) {
|
||||
continue
|
||||
}
|
||||
|
||||
object[name] = value && typeof value === 'object' ? deepFreezeObject(value) : value
|
||||
}
|
||||
|
||||
return Object.freeze(object)
|
||||
}
|
||||
|
||||
module.exports = validateInitialConfig
|
||||
module.exports.defaultInitOptions = validate.defaultInitOptions
|
||||
module.exports.utils = { deepFreezeObject }
|
||||
136
node_modules/fastify/lib/logger-factory.js
generated
vendored
Normal file
136
node_modules/fastify/lib/logger-factory.js
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED,
|
||||
FST_ERR_LOG_INVALID_LOGGER_CONFIG,
|
||||
FST_ERR_LOG_INVALID_LOGGER_INSTANCE,
|
||||
FST_ERR_LOG_INVALID_LOGGER
|
||||
} = require('./errors')
|
||||
|
||||
/**
|
||||
* Utility for creating a child logger with the appropriate bindings, logger factory
|
||||
* and validation.
|
||||
* @param {object} context
|
||||
* @param {import('../fastify').FastifyBaseLogger} logger
|
||||
* @param {import('../fastify').RawRequestDefaultExpression<any>} req
|
||||
* @param {string} reqId
|
||||
* @param {import('../types/logger.js').ChildLoggerOptions?} loggerOpts
|
||||
*
|
||||
* @returns {object} New logger instance, inheriting all parent bindings,
|
||||
* with child bindings added.
|
||||
*/
|
||||
function createChildLogger (context, logger, req, reqId, loggerOpts) {
|
||||
const loggerBindings = {
|
||||
[context.requestIdLogLabel]: reqId
|
||||
}
|
||||
const child = context.childLoggerFactory.call(context.server, logger, loggerBindings, loggerOpts || {}, req)
|
||||
|
||||
// Optimization: bypass validation if the factory is our own default factory
|
||||
if (context.childLoggerFactory !== defaultChildLoggerFactory) {
|
||||
validateLogger(child, true) // throw if the child is not a valid logger
|
||||
}
|
||||
|
||||
return child
|
||||
}
|
||||
|
||||
/** Default factory to create child logger instance
|
||||
*
|
||||
* @param {import('../fastify.js').FastifyBaseLogger} logger
|
||||
* @param {import('../types/logger.js').Bindings} bindings
|
||||
* @param {import('../types/logger.js').ChildLoggerOptions} opts
|
||||
*
|
||||
* @returns {import('../types/logger.js').FastifyBaseLogger}
|
||||
*/
|
||||
function defaultChildLoggerFactory (logger, bindings, opts) {
|
||||
return logger.child(bindings, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a provided logger object meets the requirements
|
||||
* of a Fastify compatible logger.
|
||||
*
|
||||
* @param {object} logger Object to validate.
|
||||
* @param {boolean?} strict `true` if the object must be a logger (always throw if any methods missing)
|
||||
*
|
||||
* @returns {boolean} `true` when the logger meets the requirements.
|
||||
*
|
||||
* @throws {FST_ERR_LOG_INVALID_LOGGER} When the logger object is
|
||||
* missing required methods.
|
||||
*/
|
||||
function validateLogger (logger, strict) {
|
||||
const methods = ['info', 'error', 'debug', 'fatal', 'warn', 'trace', 'child']
|
||||
const missingMethods = logger
|
||||
? methods.filter(method => !logger[method] || typeof logger[method] !== 'function')
|
||||
: methods
|
||||
|
||||
if (!missingMethods.length) {
|
||||
return true
|
||||
} else if ((missingMethods.length === methods.length) && !strict) {
|
||||
return false
|
||||
} else {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER(missingMethods.join(','))
|
||||
}
|
||||
}
|
||||
|
||||
function createLogger (options) {
|
||||
if (options.logger && options.loggerInstance) {
|
||||
throw new FST_ERR_LOG_LOGGER_AND_LOGGER_INSTANCE_PROVIDED()
|
||||
}
|
||||
|
||||
if (!options.loggerInstance && !options.logger) {
|
||||
const nullLogger = require('abstract-logging')
|
||||
const logger = nullLogger
|
||||
logger.child = () => logger
|
||||
return { logger, hasLogger: false }
|
||||
}
|
||||
|
||||
const { createPinoLogger, serializers } = require('./logger-pino.js')
|
||||
|
||||
// check if the logger instance has all required properties
|
||||
if (validateLogger(options.loggerInstance)) {
|
||||
const logger = createPinoLogger({
|
||||
logger: options.loggerInstance,
|
||||
serializers: Object.assign({}, serializers, options.loggerInstance.serializers)
|
||||
})
|
||||
return { logger, hasLogger: true }
|
||||
}
|
||||
|
||||
// if a logger instance is passed to logger, throw an exception
|
||||
if (validateLogger(options.logger)) {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER_CONFIG()
|
||||
}
|
||||
|
||||
if (options.loggerInstance) {
|
||||
throw FST_ERR_LOG_INVALID_LOGGER_INSTANCE()
|
||||
}
|
||||
|
||||
const localLoggerOptions = {}
|
||||
if (Object.prototype.toString.call(options.logger) === '[object Object]') {
|
||||
Reflect.ownKeys(options.logger).forEach(prop => {
|
||||
Object.defineProperty(localLoggerOptions, prop, {
|
||||
value: options.logger[prop],
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
configurable: true
|
||||
})
|
||||
})
|
||||
}
|
||||
localLoggerOptions.level = localLoggerOptions.level || 'info'
|
||||
localLoggerOptions.serializers = Object.assign({}, serializers, localLoggerOptions.serializers)
|
||||
options.logger = localLoggerOptions
|
||||
const logger = createPinoLogger(options.logger)
|
||||
return { logger, hasLogger: true }
|
||||
}
|
||||
|
||||
function now () {
|
||||
const ts = process.hrtime()
|
||||
return (ts[0] * 1e3) + (ts[1] / 1e6)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createChildLogger,
|
||||
defaultChildLoggerFactory,
|
||||
createLogger,
|
||||
validateLogger,
|
||||
now
|
||||
}
|
||||
68
node_modules/fastify/lib/logger-pino.js
generated
vendored
Normal file
68
node_modules/fastify/lib/logger-pino.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* Code imported from `pino-http`
|
||||
* Repo: https://github.com/pinojs/pino-http
|
||||
* License: MIT (https://raw.githubusercontent.com/pinojs/pino-http/master/LICENSE)
|
||||
*/
|
||||
|
||||
const pino = require('pino')
|
||||
const { serializersSym } = pino.symbols
|
||||
const {
|
||||
FST_ERR_LOG_INVALID_DESTINATION
|
||||
} = require('./errors')
|
||||
|
||||
function createPinoLogger (opts) {
|
||||
if (opts.stream && opts.file) {
|
||||
throw new FST_ERR_LOG_INVALID_DESTINATION()
|
||||
} else if (opts.file) {
|
||||
// we do not have stream
|
||||
opts.stream = pino.destination(opts.file)
|
||||
delete opts.file
|
||||
}
|
||||
|
||||
const prevLogger = opts.logger
|
||||
const prevGenReqId = opts.genReqId
|
||||
let logger = null
|
||||
|
||||
if (prevLogger) {
|
||||
opts.logger = undefined
|
||||
opts.genReqId = undefined
|
||||
// we need to tap into pino internals because in v5 it supports
|
||||
// adding serializers in child loggers
|
||||
if (prevLogger[serializersSym]) {
|
||||
opts.serializers = Object.assign({}, opts.serializers, prevLogger[serializersSym])
|
||||
}
|
||||
logger = prevLogger.child({}, opts)
|
||||
opts.logger = prevLogger
|
||||
opts.genReqId = prevGenReqId
|
||||
} else {
|
||||
logger = pino(opts, opts.stream)
|
||||
}
|
||||
|
||||
return logger
|
||||
}
|
||||
|
||||
const serializers = {
|
||||
req: function asReqValue (req) {
|
||||
return {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
version: req.headers && req.headers['accept-version'],
|
||||
host: req.host,
|
||||
remoteAddress: req.ip,
|
||||
remotePort: req.socket ? req.socket.remotePort : undefined
|
||||
}
|
||||
},
|
||||
err: pino.stdSerializers.err,
|
||||
res: function asResValue (reply) {
|
||||
return {
|
||||
statusCode: reply.statusCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
serializers,
|
||||
createPinoLogger
|
||||
}
|
||||
10
node_modules/fastify/lib/noop-set.js
generated
vendored
Normal file
10
node_modules/fastify/lib/noop-set.js
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict'
|
||||
|
||||
module.exports = function noopSet () {
|
||||
return {
|
||||
[Symbol.iterator]: function * () {},
|
||||
add () {},
|
||||
delete () {},
|
||||
has () { return true }
|
||||
}
|
||||
}
|
||||
90
node_modules/fastify/lib/pluginOverride.js
generated
vendored
Normal file
90
node_modules/fastify/lib/pluginOverride.js
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kAvvioBoot,
|
||||
kChildren,
|
||||
kRoutePrefix,
|
||||
kLogLevel,
|
||||
kLogSerializers,
|
||||
kHooks,
|
||||
kSchemaController,
|
||||
kContentTypeParser,
|
||||
kReply,
|
||||
kRequest,
|
||||
kFourOhFour,
|
||||
kPluginNameChain,
|
||||
kErrorHandlerAlreadySet
|
||||
} = require('./symbols.js')
|
||||
|
||||
const Reply = require('./reply')
|
||||
const Request = require('./request')
|
||||
const SchemaController = require('./schema-controller')
|
||||
const ContentTypeParser = require('./contentTypeParser')
|
||||
const { buildHooks } = require('./hooks')
|
||||
const pluginUtils = require('./pluginUtils')
|
||||
|
||||
// Function that runs the encapsulation magic.
|
||||
// Everything that need to be encapsulated must be handled in this function.
|
||||
module.exports = function override (old, fn, opts) {
|
||||
const shouldSkipOverride = pluginUtils.registerPlugin.call(old, fn)
|
||||
|
||||
const fnName = pluginUtils.getPluginName(fn) || pluginUtils.getFuncPreview(fn)
|
||||
if (shouldSkipOverride) {
|
||||
// after every plugin registration we will enter a new name
|
||||
old[kPluginNameChain].push(fnName)
|
||||
return old
|
||||
}
|
||||
|
||||
const instance = Object.create(old)
|
||||
old[kChildren].push(instance)
|
||||
instance.ready = old[kAvvioBoot].bind(instance)
|
||||
instance[kChildren] = []
|
||||
|
||||
instance[kReply] = Reply.buildReply(instance[kReply])
|
||||
instance[kRequest] = Request.buildRequest(instance[kRequest])
|
||||
|
||||
instance[kContentTypeParser] = ContentTypeParser.helpers.buildContentTypeParser(instance[kContentTypeParser])
|
||||
instance[kHooks] = buildHooks(instance[kHooks])
|
||||
instance[kRoutePrefix] = buildRoutePrefix(instance[kRoutePrefix], opts.prefix)
|
||||
instance[kLogLevel] = opts.logLevel || instance[kLogLevel]
|
||||
instance[kSchemaController] = SchemaController.buildSchemaController(old[kSchemaController])
|
||||
instance.getSchema = instance[kSchemaController].getSchema.bind(instance[kSchemaController])
|
||||
instance.getSchemas = instance[kSchemaController].getSchemas.bind(instance[kSchemaController])
|
||||
|
||||
// Track the registered and loaded plugins since the root instance.
|
||||
// It does not track the current encapsulated plugin.
|
||||
instance[pluginUtils.kRegisteredPlugins] = Object.create(instance[pluginUtils.kRegisteredPlugins])
|
||||
|
||||
// Track the plugin chain since the root instance.
|
||||
// When an non-encapsulated plugin is added, the chain will be updated.
|
||||
instance[kPluginNameChain] = [fnName]
|
||||
instance[kErrorHandlerAlreadySet] = false
|
||||
|
||||
if (instance[kLogSerializers] || opts.logSerializers) {
|
||||
instance[kLogSerializers] = Object.assign(Object.create(instance[kLogSerializers]), opts.logSerializers)
|
||||
}
|
||||
|
||||
if (opts.prefix) {
|
||||
instance[kFourOhFour].arrange404(instance)
|
||||
}
|
||||
|
||||
for (const hook of instance[kHooks].onRegister) hook.call(old, instance, opts)
|
||||
|
||||
return instance
|
||||
}
|
||||
|
||||
function buildRoutePrefix (instancePrefix, pluginPrefix) {
|
||||
if (!pluginPrefix) {
|
||||
return instancePrefix
|
||||
}
|
||||
|
||||
// Ensure that there is a '/' between the prefixes
|
||||
if (instancePrefix.endsWith('/') && pluginPrefix[0] === '/') {
|
||||
// Remove the extra '/' to avoid: '/first//second'
|
||||
pluginPrefix = pluginPrefix.slice(1)
|
||||
} else if (pluginPrefix[0] !== '/') {
|
||||
pluginPrefix = '/' + pluginPrefix
|
||||
}
|
||||
|
||||
return instancePrefix + pluginPrefix
|
||||
}
|
||||
169
node_modules/fastify/lib/pluginUtils.js
generated
vendored
Normal file
169
node_modules/fastify/lib/pluginUtils.js
generated
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
'use strict'
|
||||
|
||||
const semver = require('semver')
|
||||
const assert = require('node:assert')
|
||||
const kRegisteredPlugins = Symbol.for('registered-plugin')
|
||||
const {
|
||||
kTestInternals
|
||||
} = require('./symbols.js')
|
||||
const { exist, existReply, existRequest } = require('./decorate')
|
||||
const {
|
||||
FST_ERR_PLUGIN_VERSION_MISMATCH,
|
||||
FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE,
|
||||
FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER
|
||||
} = require('./errors')
|
||||
|
||||
const rcRegex = /-(?:rc|pre|alpha).+$/u
|
||||
|
||||
function getMeta (fn) {
|
||||
return fn[Symbol.for('plugin-meta')]
|
||||
}
|
||||
|
||||
function getPluginName (func) {
|
||||
const display = getDisplayName(func)
|
||||
if (display) {
|
||||
return display
|
||||
}
|
||||
|
||||
// let's see if this is a file, and in that case use that
|
||||
// this is common for plugins
|
||||
const cache = require.cache
|
||||
// cache is undefined inside SEA
|
||||
if (cache) {
|
||||
const keys = Object.keys(cache)
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i]
|
||||
if (cache[key].exports === func) {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if not maybe it's a named function, so use that
|
||||
if (func.name) {
|
||||
return func.name
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function getFuncPreview (func) {
|
||||
// takes the first two lines of the function if nothing else works
|
||||
return func.toString().split('\n', 2).map(s => s.trim()).join(' -- ')
|
||||
}
|
||||
|
||||
function getDisplayName (fn) {
|
||||
return fn[Symbol.for('fastify.display-name')]
|
||||
}
|
||||
|
||||
function shouldSkipOverride (fn) {
|
||||
return !!fn[Symbol.for('skip-override')]
|
||||
}
|
||||
|
||||
function checkDependencies (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const dependencies = meta.dependencies
|
||||
if (!dependencies) return
|
||||
assert(Array.isArray(dependencies), 'The dependencies should be an array of strings')
|
||||
|
||||
dependencies.forEach(dependency => {
|
||||
assert(
|
||||
this[kRegisteredPlugins].indexOf(dependency) > -1,
|
||||
`The dependency '${dependency}' of plugin '${meta.name}' is not registered`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function checkDecorators (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const { decorators, name } = meta
|
||||
if (!decorators) return
|
||||
|
||||
if (decorators.fastify) _checkDecorators(this, 'Fastify', decorators.fastify, name)
|
||||
if (decorators.reply) _checkDecorators(this, 'Reply', decorators.reply, name)
|
||||
if (decorators.request) _checkDecorators(this, 'Request', decorators.request, name)
|
||||
}
|
||||
|
||||
const checks = {
|
||||
Fastify: exist,
|
||||
Request: existRequest,
|
||||
Reply: existReply
|
||||
}
|
||||
|
||||
function _checkDecorators (that, instance, decorators, name) {
|
||||
assert(Array.isArray(decorators), 'The decorators should be an array of strings')
|
||||
|
||||
decorators.forEach(decorator => {
|
||||
const withPluginName = typeof name === 'string' ? ` required by '${name}'` : ''
|
||||
if (!checks[instance].call(that, decorator)) {
|
||||
throw new FST_ERR_PLUGIN_NOT_PRESENT_IN_INSTANCE(decorator, withPluginName, instance)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function checkVersion (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (meta?.fastify == null) return
|
||||
|
||||
const requiredVersion = meta.fastify
|
||||
|
||||
const fastifyRc = rcRegex.test(this.version)
|
||||
if (fastifyRc === true && semver.gt(this.version, semver.coerce(requiredVersion)) === true) {
|
||||
// A Fastify release candidate phase is taking place. In order to reduce
|
||||
// the effort needed to test plugins with the RC, we allow plugins targeting
|
||||
// the prior Fastify release to be loaded.
|
||||
return
|
||||
}
|
||||
if (requiredVersion && semver.satisfies(this.version, requiredVersion, { includePrerelease: fastifyRc }) === false) {
|
||||
// We are not in a release candidate phase. Thus, we must honor the semver
|
||||
// ranges defined by the plugin's metadata. Which is to say, if the plugin
|
||||
// expects an older version of Fastify than the _current_ version, we will
|
||||
// throw an error.
|
||||
throw new FST_ERR_PLUGIN_VERSION_MISMATCH(meta.name, requiredVersion, this.version)
|
||||
}
|
||||
}
|
||||
|
||||
function registerPluginName (fn) {
|
||||
const meta = getMeta(fn)
|
||||
if (!meta) return
|
||||
|
||||
const name = meta.name
|
||||
if (!name) return
|
||||
this[kRegisteredPlugins].push(name)
|
||||
return name
|
||||
}
|
||||
|
||||
function checkPluginHealthiness (fn, pluginName) {
|
||||
if (fn.constructor.name === 'AsyncFunction' && fn.length === 3) {
|
||||
throw new FST_ERR_PLUGIN_INVALID_ASYNC_HANDLER(pluginName)
|
||||
}
|
||||
}
|
||||
|
||||
function registerPlugin (fn) {
|
||||
const pluginName = registerPluginName.call(this, fn) || getPluginName(fn)
|
||||
checkPluginHealthiness.call(this, fn, pluginName)
|
||||
checkVersion.call(this, fn)
|
||||
checkDecorators.call(this, fn)
|
||||
checkDependencies.call(this, fn)
|
||||
return shouldSkipOverride(fn)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getPluginName,
|
||||
getFuncPreview,
|
||||
kRegisteredPlugins,
|
||||
getDisplayName,
|
||||
registerPlugin
|
||||
}
|
||||
|
||||
module.exports[kTestInternals] = {
|
||||
shouldSkipOverride,
|
||||
getMeta,
|
||||
checkDecorators,
|
||||
checkDependencies
|
||||
}
|
||||
23
node_modules/fastify/lib/promise.js
generated
vendored
Normal file
23
node_modules/fastify/lib/promise.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
'use strict'
|
||||
|
||||
const { kTestInternals } = require('./symbols')
|
||||
|
||||
function withResolvers () {
|
||||
let res, rej
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
res = resolve
|
||||
rej = reject
|
||||
})
|
||||
return { promise, resolve: res, reject: rej }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
// TODO(20.x): remove when node@20 is not supported
|
||||
withResolvers: typeof Promise.withResolvers === 'function'
|
||||
? Promise.withResolvers.bind(Promise) // Promise.withResolvers must bind to itself
|
||||
/* c8 ignore next */
|
||||
: withResolvers, // Tested using the kTestInternals
|
||||
[kTestInternals]: {
|
||||
withResolvers
|
||||
}
|
||||
}
|
||||
942
node_modules/fastify/lib/reply.js
generated
vendored
Normal file
942
node_modules/fastify/lib/reply.js
generated
vendored
Normal file
@@ -0,0 +1,942 @@
|
||||
'use strict'
|
||||
|
||||
const eos = require('node:stream').finished
|
||||
const Readable = require('node:stream').Readable
|
||||
|
||||
const {
|
||||
kFourOhFourContext,
|
||||
kReplyErrorHandlerCalled,
|
||||
kReplyHijacked,
|
||||
kReplyStartTime,
|
||||
kReplyEndTime,
|
||||
kReplySerializer,
|
||||
kReplySerializerDefault,
|
||||
kReplyIsError,
|
||||
kReplyHeaders,
|
||||
kReplyTrailers,
|
||||
kReplyHasStatusCode,
|
||||
kReplyIsRunningOnErrorHook,
|
||||
kReplyNextErrorHandler,
|
||||
kDisableRequestLogging,
|
||||
kSchemaResponse,
|
||||
kReplyCacheSerializeFns,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kRouteContext
|
||||
} = require('./symbols.js')
|
||||
const {
|
||||
onSendHookRunner,
|
||||
onResponseHookRunner,
|
||||
preHandlerHookRunner,
|
||||
preSerializationHookRunner
|
||||
} = require('./hooks')
|
||||
|
||||
const internals = require('./handleRequest')[Symbol.for('internals')]
|
||||
const loggerUtils = require('./logger-factory')
|
||||
const now = loggerUtils.now
|
||||
const { handleError } = require('./error-handler')
|
||||
const { getSchemaSerializer } = require('./schemas')
|
||||
|
||||
const CONTENT_TYPE = {
|
||||
JSON: 'application/json; charset=utf-8',
|
||||
PLAIN: 'text/plain; charset=utf-8',
|
||||
OCTET: 'application/octet-stream'
|
||||
}
|
||||
const {
|
||||
FST_ERR_REP_INVALID_PAYLOAD_TYPE,
|
||||
FST_ERR_REP_RESPONSE_BODY_CONSUMED,
|
||||
FST_ERR_REP_READABLE_STREAM_LOCKED,
|
||||
FST_ERR_REP_ALREADY_SENT,
|
||||
FST_ERR_SEND_INSIDE_ONERR,
|
||||
FST_ERR_BAD_STATUS_CODE,
|
||||
FST_ERR_BAD_TRAILER_NAME,
|
||||
FST_ERR_BAD_TRAILER_VALUE,
|
||||
FST_ERR_MISSING_SERIALIZATION_FN,
|
||||
FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN,
|
||||
FST_ERR_DEC_UNDECLARED
|
||||
} = require('./errors')
|
||||
const decorators = require('./decorate')
|
||||
|
||||
const toString = Object.prototype.toString
|
||||
|
||||
function Reply (res, request, log) {
|
||||
this.raw = res
|
||||
this[kReplySerializer] = null
|
||||
this[kReplyErrorHandlerCalled] = false
|
||||
this[kReplyIsError] = false
|
||||
this[kReplyIsRunningOnErrorHook] = false
|
||||
this.request = request
|
||||
this[kReplyHeaders] = {}
|
||||
this[kReplyTrailers] = null
|
||||
this[kReplyHasStatusCode] = false
|
||||
this[kReplyStartTime] = undefined
|
||||
this.log = log
|
||||
}
|
||||
Reply.props = []
|
||||
|
||||
Object.defineProperties(Reply.prototype, {
|
||||
[kRouteContext]: {
|
||||
get () {
|
||||
return this.request[kRouteContext]
|
||||
}
|
||||
},
|
||||
elapsedTime: {
|
||||
get () {
|
||||
if (this[kReplyStartTime] === undefined) {
|
||||
return 0
|
||||
}
|
||||
return (this[kReplyEndTime] || now()) - this[kReplyStartTime]
|
||||
}
|
||||
},
|
||||
server: {
|
||||
get () {
|
||||
return this.request[kRouteContext].server
|
||||
}
|
||||
},
|
||||
sent: {
|
||||
enumerable: true,
|
||||
get () {
|
||||
// We are checking whether reply was hijacked or the response has ended.
|
||||
return (this[kReplyHijacked] || this.raw.writableEnded) === true
|
||||
}
|
||||
},
|
||||
statusCode: {
|
||||
get () {
|
||||
return this.raw.statusCode
|
||||
},
|
||||
set (value) {
|
||||
this.code(value)
|
||||
}
|
||||
},
|
||||
routeOptions: {
|
||||
get () {
|
||||
return this.request.routeOptions
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reply.prototype.writeEarlyHints = function (hints, callback) {
|
||||
this.raw.writeEarlyHints(hints, callback)
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.hijack = function () {
|
||||
this[kReplyHijacked] = true
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.send = function (payload) {
|
||||
if (this[kReplyIsRunningOnErrorHook]) {
|
||||
throw new FST_ERR_SEND_INSIDE_ONERR()
|
||||
}
|
||||
|
||||
if (this.sent === true) {
|
||||
this.log.warn({ err: new FST_ERR_REP_ALREADY_SENT(this.request.url, this.request.method) })
|
||||
return this
|
||||
}
|
||||
|
||||
if (this[kReplyIsError] || payload instanceof Error) {
|
||||
this[kReplyIsError] = false
|
||||
onErrorHook(this, payload, onSendHook)
|
||||
return this
|
||||
}
|
||||
|
||||
if (payload === undefined) {
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
|
||||
const contentType = this.getHeader('content-type')
|
||||
const hasContentType = contentType !== undefined
|
||||
|
||||
if (payload !== null) {
|
||||
if (
|
||||
// node:stream
|
||||
typeof payload.pipe === 'function' ||
|
||||
// node:stream/web
|
||||
typeof payload.getReader === 'function' ||
|
||||
// Response
|
||||
toString.call(payload) === '[object Response]'
|
||||
) {
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
|
||||
if (payload.buffer instanceof ArrayBuffer) {
|
||||
if (!hasContentType) {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.OCTET
|
||||
}
|
||||
const payloadToSend = Buffer.isBuffer(payload) ? payload : Buffer.from(payload.buffer, payload.byteOffset, payload.byteLength)
|
||||
onSendHook(this, payloadToSend)
|
||||
return this
|
||||
}
|
||||
|
||||
if (!hasContentType && typeof payload === 'string') {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.PLAIN
|
||||
onSendHook(this, payload)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
if (this[kReplySerializer] !== null) {
|
||||
if (typeof payload !== 'string') {
|
||||
preSerializationHook(this, payload)
|
||||
return this
|
||||
}
|
||||
payload = this[kReplySerializer](payload)
|
||||
|
||||
// The indexOf below also matches custom json mimetypes such as 'application/hal+json' or 'application/ld+json'
|
||||
} else if (!hasContentType || contentType.indexOf('json') !== -1) {
|
||||
if (!hasContentType) {
|
||||
this[kReplyHeaders]['content-type'] = CONTENT_TYPE.JSON
|
||||
} else if (contentType.indexOf('charset') === -1) {
|
||||
// If user doesn't set charset, we will set charset to utf-8
|
||||
const customContentType = contentType.trim()
|
||||
if (customContentType.endsWith(';')) {
|
||||
// custom content-type is ended with ';'
|
||||
this[kReplyHeaders]['content-type'] = `${customContentType} charset=utf-8`
|
||||
} else {
|
||||
this[kReplyHeaders]['content-type'] = `${customContentType}; charset=utf-8`
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string') {
|
||||
preSerializationHook(this, payload)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
onSendHook(this, payload)
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.getHeader = function (key) {
|
||||
key = key.toLowerCase()
|
||||
const value = this[kReplyHeaders][key]
|
||||
return value !== undefined ? value : this.raw.getHeader(key)
|
||||
}
|
||||
|
||||
Reply.prototype.getHeaders = function () {
|
||||
return {
|
||||
...this.raw.getHeaders(),
|
||||
...this[kReplyHeaders]
|
||||
}
|
||||
}
|
||||
|
||||
Reply.prototype.hasHeader = function (key) {
|
||||
key = key.toLowerCase()
|
||||
|
||||
return this[kReplyHeaders][key] !== undefined || this.raw.hasHeader(key)
|
||||
}
|
||||
|
||||
Reply.prototype.removeHeader = function (key) {
|
||||
// Node.js does not like headers with keys set to undefined,
|
||||
// so we have to delete the key.
|
||||
delete this[kReplyHeaders][key.toLowerCase()]
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.header = function (key, value = '') {
|
||||
key = key.toLowerCase()
|
||||
|
||||
if (this[kReplyHeaders][key] && key === 'set-cookie') {
|
||||
// https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.2
|
||||
if (typeof this[kReplyHeaders][key] === 'string') {
|
||||
this[kReplyHeaders][key] = [this[kReplyHeaders][key]]
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
Array.prototype.push.apply(this[kReplyHeaders][key], value)
|
||||
} else {
|
||||
this[kReplyHeaders][key].push(value)
|
||||
}
|
||||
} else {
|
||||
this[kReplyHeaders][key] = value
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.headers = function (headers) {
|
||||
const keys = Object.keys(headers)
|
||||
for (let i = 0; i !== keys.length; ++i) {
|
||||
const key = keys[i]
|
||||
this.header(key, headers[key])
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Trailer#directives
|
||||
// https://datatracker.ietf.org/doc/html/rfc7230.html#chunked.trailer.part
|
||||
const INVALID_TRAILERS = new Set([
|
||||
'transfer-encoding',
|
||||
'content-length',
|
||||
'host',
|
||||
'cache-control',
|
||||
'max-forwards',
|
||||
'te',
|
||||
'authorization',
|
||||
'set-cookie',
|
||||
'content-encoding',
|
||||
'content-type',
|
||||
'content-range',
|
||||
'trailer'
|
||||
])
|
||||
|
||||
Reply.prototype.trailer = function (key, fn) {
|
||||
key = key.toLowerCase()
|
||||
if (INVALID_TRAILERS.has(key)) {
|
||||
throw new FST_ERR_BAD_TRAILER_NAME(key)
|
||||
}
|
||||
if (typeof fn !== 'function') {
|
||||
throw new FST_ERR_BAD_TRAILER_VALUE(key, typeof fn)
|
||||
}
|
||||
if (this[kReplyTrailers] === null) this[kReplyTrailers] = {}
|
||||
this[kReplyTrailers][key] = fn
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.hasTrailer = function (key) {
|
||||
return this[kReplyTrailers]?.[key.toLowerCase()] !== undefined
|
||||
}
|
||||
|
||||
Reply.prototype.removeTrailer = function (key) {
|
||||
if (this[kReplyTrailers] === null) return this
|
||||
this[kReplyTrailers][key.toLowerCase()] = undefined
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.code = function (code) {
|
||||
const statusCode = +code
|
||||
if (!(statusCode >= 100 && statusCode <= 599)) {
|
||||
throw new FST_ERR_BAD_STATUS_CODE(code || String(code))
|
||||
}
|
||||
|
||||
this.raw.statusCode = statusCode
|
||||
this[kReplyHasStatusCode] = true
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.status = Reply.prototype.code
|
||||
|
||||
Reply.prototype.getSerializationFunction = function (schemaOrStatus, contentType) {
|
||||
let serialize
|
||||
|
||||
if (typeof schemaOrStatus === 'string' || typeof schemaOrStatus === 'number') {
|
||||
if (typeof contentType === 'string') {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]?.[contentType]
|
||||
} else {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[schemaOrStatus]
|
||||
}
|
||||
} else if (typeof schemaOrStatus === 'object') {
|
||||
serialize = this[kRouteContext][kReplyCacheSerializeFns]?.get(schemaOrStatus)
|
||||
}
|
||||
|
||||
return serialize
|
||||
}
|
||||
|
||||
Reply.prototype.compileSerializationSchema = function (schema, httpStatus = null, contentType = null) {
|
||||
const { request } = this
|
||||
const { method, url } = request
|
||||
|
||||
// Check if serialize function already compiled
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) {
|
||||
return this[kRouteContext][kReplyCacheSerializeFns].get(schema)
|
||||
}
|
||||
|
||||
const serializerCompiler = this[kRouteContext].serializerCompiler ||
|
||||
this.server[kSchemaController].serializerCompiler ||
|
||||
(
|
||||
// We compile the schemas if no custom serializerCompiler is provided
|
||||
// nor set
|
||||
this.server[kSchemaController].setupSerializer(this.server[kOptions]) ||
|
||||
this.server[kSchemaController].serializerCompiler
|
||||
)
|
||||
|
||||
const serializeFn = serializerCompiler({
|
||||
schema,
|
||||
method,
|
||||
url,
|
||||
httpStatus,
|
||||
contentType
|
||||
})
|
||||
|
||||
// We create a WeakMap to compile the schema only once
|
||||
// Its done lazily to avoid add overhead by creating the WeakMap
|
||||
// if it is not used
|
||||
// TODO: Explore a central cache for all the schemas shared across
|
||||
// encapsulated contexts
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns] == null) {
|
||||
this[kRouteContext][kReplyCacheSerializeFns] = new WeakMap()
|
||||
}
|
||||
|
||||
this[kRouteContext][kReplyCacheSerializeFns].set(schema, serializeFn)
|
||||
|
||||
return serializeFn
|
||||
}
|
||||
|
||||
Reply.prototype.serializeInput = function (input, schema, httpStatus, contentType) {
|
||||
const possibleContentType = httpStatus
|
||||
let serialize
|
||||
httpStatus = typeof schema === 'string' || typeof schema === 'number'
|
||||
? schema
|
||||
: httpStatus
|
||||
|
||||
contentType = httpStatus && possibleContentType !== httpStatus
|
||||
? possibleContentType
|
||||
: contentType
|
||||
|
||||
if (httpStatus != null) {
|
||||
if (contentType != null) {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]?.[contentType]
|
||||
} else {
|
||||
serialize = this[kRouteContext][kSchemaResponse]?.[httpStatus]
|
||||
}
|
||||
|
||||
if (serialize == null) {
|
||||
if (contentType) throw new FST_ERR_MISSING_CONTENTTYPE_SERIALIZATION_FN(httpStatus, contentType)
|
||||
throw new FST_ERR_MISSING_SERIALIZATION_FN(httpStatus)
|
||||
}
|
||||
} else {
|
||||
// Check if serialize function already compiled
|
||||
if (this[kRouteContext][kReplyCacheSerializeFns]?.has(schema)) {
|
||||
serialize = this[kRouteContext][kReplyCacheSerializeFns].get(schema)
|
||||
} else {
|
||||
serialize = this.compileSerializationSchema(schema, httpStatus, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
return serialize(input)
|
||||
}
|
||||
|
||||
Reply.prototype.serialize = function (payload) {
|
||||
if (this[kReplySerializer] !== null) {
|
||||
return this[kReplySerializer](payload)
|
||||
} else {
|
||||
if (this[kRouteContext] && this[kRouteContext][kReplySerializerDefault]) {
|
||||
return this[kRouteContext][kReplySerializerDefault](payload, this.raw.statusCode)
|
||||
} else {
|
||||
return serialize(this[kRouteContext], payload, this.raw.statusCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reply.prototype.serializer = function (fn) {
|
||||
this[kReplySerializer] = fn
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.type = function (type) {
|
||||
this[kReplyHeaders]['content-type'] = type
|
||||
return this
|
||||
}
|
||||
|
||||
Reply.prototype.redirect = function (url, code) {
|
||||
if (!code) {
|
||||
code = this[kReplyHasStatusCode] ? this.raw.statusCode : 302
|
||||
}
|
||||
|
||||
return this.header('location', url).code(code).send()
|
||||
}
|
||||
|
||||
Reply.prototype.callNotFound = function () {
|
||||
notFound(this)
|
||||
return this
|
||||
}
|
||||
|
||||
// Make reply a thenable, so it could be used with async/await.
|
||||
// See
|
||||
// - https://github.com/fastify/fastify/issues/1864 for the discussions
|
||||
// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then for the signature
|
||||
Reply.prototype.then = function (fulfilled, rejected) {
|
||||
if (this.sent) {
|
||||
fulfilled()
|
||||
return
|
||||
}
|
||||
|
||||
eos(this.raw, (err) => {
|
||||
// We must not treat ERR_STREAM_PREMATURE_CLOSE as
|
||||
// an error because it is created by eos, not by the stream.
|
||||
if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
if (rejected) {
|
||||
rejected(err)
|
||||
} else {
|
||||
this.log && this.log.warn('unhandled rejection on reply.then')
|
||||
}
|
||||
} else {
|
||||
fulfilled()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reply.prototype.getDecorator = function (name) {
|
||||
if (!decorators.hasKey(this, name) && !decorators.exist(this, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'reply')
|
||||
}
|
||||
|
||||
const decorator = this[name]
|
||||
if (typeof decorator === 'function') {
|
||||
return decorator.bind(this)
|
||||
}
|
||||
|
||||
return decorator
|
||||
}
|
||||
|
||||
function preSerializationHook (reply, payload) {
|
||||
if (reply[kRouteContext].preSerialization !== null) {
|
||||
preSerializationHookRunner(
|
||||
reply[kRouteContext].preSerialization,
|
||||
reply.request,
|
||||
reply,
|
||||
payload,
|
||||
preSerializationHookEnd
|
||||
)
|
||||
} else {
|
||||
preSerializationHookEnd(null, undefined, reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function preSerializationHookEnd (err, _request, reply, payload) {
|
||||
if (err != null) {
|
||||
onErrorHook(reply, err)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
if (reply[kReplySerializer] !== null) {
|
||||
payload = reply[kReplySerializer](payload)
|
||||
} else if (reply[kRouteContext] && reply[kRouteContext][kReplySerializerDefault]) {
|
||||
payload = reply[kRouteContext][kReplySerializerDefault](payload, reply.raw.statusCode)
|
||||
} else {
|
||||
payload = serialize(reply[kRouteContext], payload, reply.raw.statusCode, reply[kReplyHeaders]['content-type'])
|
||||
}
|
||||
} catch (e) {
|
||||
wrapSerializationError(e, reply)
|
||||
onErrorHook(reply, e)
|
||||
return
|
||||
}
|
||||
|
||||
onSendHook(reply, payload)
|
||||
}
|
||||
|
||||
function wrapSerializationError (error, reply) {
|
||||
error.serialization = reply[kRouteContext].config
|
||||
}
|
||||
|
||||
function onSendHook (reply, payload) {
|
||||
if (reply[kRouteContext].onSend !== null) {
|
||||
onSendHookRunner(
|
||||
reply[kRouteContext].onSend,
|
||||
reply.request,
|
||||
reply,
|
||||
payload,
|
||||
wrapOnSendEnd
|
||||
)
|
||||
} else {
|
||||
onSendEnd(reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function wrapOnSendEnd (err, request, reply, payload) {
|
||||
if (err != null) {
|
||||
onErrorHook(reply, err)
|
||||
} else {
|
||||
onSendEnd(reply, payload)
|
||||
}
|
||||
}
|
||||
|
||||
function safeWriteHead (reply, statusCode) {
|
||||
const res = reply.raw
|
||||
try {
|
||||
res.writeHead(statusCode, reply[kReplyHeaders])
|
||||
} catch (err) {
|
||||
if (err.code === 'ERR_HTTP_HEADERS_SENT') {
|
||||
reply.log.warn(`Reply was already sent, did you forget to "return reply" in the "${reply.request.raw.url}" (${reply.request.raw.method}) route?`)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function onSendEnd (reply, payload) {
|
||||
const res = reply.raw
|
||||
const req = reply.request
|
||||
|
||||
// we check if we need to update the trailers header and set it
|
||||
if (reply[kReplyTrailers] !== null) {
|
||||
const trailerHeaders = Object.keys(reply[kReplyTrailers])
|
||||
let header = ''
|
||||
for (const trailerName of trailerHeaders) {
|
||||
if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
|
||||
header += ' '
|
||||
header += trailerName
|
||||
}
|
||||
// it must be chunked for trailer to work
|
||||
reply.header('Transfer-Encoding', 'chunked')
|
||||
reply.header('Trailer', header.trim())
|
||||
}
|
||||
|
||||
// since Response contain status code, headers and body,
|
||||
// we need to update the status, add the headers and use it's body as payload
|
||||
// before continuing
|
||||
if (toString.call(payload) === '[object Response]') {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status
|
||||
if (typeof payload.status === 'number') {
|
||||
reply.code(payload.status)
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/headers
|
||||
if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') {
|
||||
for (const [headerName, headerValue] of payload.headers) {
|
||||
reply.header(headerName, headerValue)
|
||||
}
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/body
|
||||
if (payload.body !== null) {
|
||||
if (payload.bodyUsed) {
|
||||
throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED()
|
||||
}
|
||||
}
|
||||
// Keep going, body is either null or ReadableStream
|
||||
payload = payload.body
|
||||
}
|
||||
const statusCode = res.statusCode
|
||||
|
||||
if (payload === undefined || payload === null) {
|
||||
// according to https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2
|
||||
// we cannot send a content-length for 304 and 204, and all status code
|
||||
// < 200
|
||||
// A sender MUST NOT send a Content-Length header field in any message
|
||||
// that contains a Transfer-Encoding header field.
|
||||
// For HEAD we don't overwrite the `content-length`
|
||||
if (statusCode >= 200 && statusCode !== 204 && statusCode !== 304 && req.method !== 'HEAD' && reply[kReplyTrailers] === null) {
|
||||
reply[kReplyHeaders]['content-length'] = '0'
|
||||
}
|
||||
|
||||
safeWriteHead(reply, statusCode)
|
||||
sendTrailer(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if ((statusCode >= 100 && statusCode < 200) || statusCode === 204) {
|
||||
// Responses without a content body must not send content-type
|
||||
// or content-length headers.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6.
|
||||
reply.removeHeader('content-type')
|
||||
reply.removeHeader('content-length')
|
||||
safeWriteHead(reply, statusCode)
|
||||
sendTrailer(undefined, res, reply)
|
||||
if (typeof payload.resume === 'function') {
|
||||
payload.on('error', noop)
|
||||
payload.resume()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// node:stream
|
||||
if (typeof payload.pipe === 'function') {
|
||||
sendStream(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
// node:stream/web
|
||||
if (typeof payload.getReader === 'function') {
|
||||
sendWebStream(payload, res, reply)
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) {
|
||||
throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload)
|
||||
}
|
||||
|
||||
if (reply[kReplyTrailers] === null) {
|
||||
const contentLength = reply[kReplyHeaders]['content-length']
|
||||
if (!contentLength ||
|
||||
(req.raw.method !== 'HEAD' &&
|
||||
Number(contentLength) !== Buffer.byteLength(payload)
|
||||
)
|
||||
) {
|
||||
reply[kReplyHeaders]['content-length'] = '' + Buffer.byteLength(payload)
|
||||
}
|
||||
}
|
||||
|
||||
safeWriteHead(reply, statusCode)
|
||||
// write payload first
|
||||
res.write(payload)
|
||||
// then send trailers
|
||||
sendTrailer(payload, res, reply)
|
||||
}
|
||||
|
||||
function logStreamError (logger, err, res) {
|
||||
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') {
|
||||
if (!logger[kDisableRequestLogging]) {
|
||||
logger.info({ res }, 'stream closed prematurely')
|
||||
}
|
||||
} else {
|
||||
logger.warn({ err }, 'response terminated with an error with headers already sent')
|
||||
}
|
||||
}
|
||||
|
||||
function sendWebStream (payload, res, reply) {
|
||||
if (payload.locked) {
|
||||
throw FST_ERR_REP_READABLE_STREAM_LOCKED()
|
||||
}
|
||||
const nodeStream = Readable.fromWeb(payload)
|
||||
sendStream(nodeStream, res, reply)
|
||||
}
|
||||
|
||||
function sendStream (payload, res, reply) {
|
||||
let sourceOpen = true
|
||||
let errorLogged = false
|
||||
|
||||
// set trailer when stream ended
|
||||
sendStreamTrailer(payload, res, reply)
|
||||
|
||||
eos(payload, { readable: true, writable: false }, function (err) {
|
||||
sourceOpen = false
|
||||
if (err != null) {
|
||||
if (res.headersSent || reply.request.raw.aborted === true) {
|
||||
if (!errorLogged) {
|
||||
errorLogged = true
|
||||
logStreamError(reply.log, err, reply)
|
||||
}
|
||||
res.destroy()
|
||||
} else {
|
||||
onErrorHook(reply, err)
|
||||
}
|
||||
}
|
||||
// there is nothing to do if there is not an error
|
||||
})
|
||||
|
||||
eos(res, function (err) {
|
||||
if (sourceOpen) {
|
||||
if (err != null && res.headersSent && !errorLogged) {
|
||||
errorLogged = true
|
||||
logStreamError(reply.log, err, res)
|
||||
}
|
||||
if (typeof payload.destroy === 'function') {
|
||||
payload.destroy()
|
||||
} else if (typeof payload.close === 'function') {
|
||||
payload.close(noop)
|
||||
} else if (typeof payload.abort === 'function') {
|
||||
payload.abort()
|
||||
} else {
|
||||
reply.log.warn('stream payload does not end properly')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// streams will error asynchronously, and we want to handle that error
|
||||
// appropriately, e.g. a 404 for a missing file. So we cannot use
|
||||
// writeHead, and we need to resort to setHeader, which will trigger
|
||||
// a writeHead when there is data to send.
|
||||
if (!res.headersSent) {
|
||||
for (const key in reply[kReplyHeaders]) {
|
||||
res.setHeader(key, reply[kReplyHeaders][key])
|
||||
}
|
||||
} else {
|
||||
reply.log.warn('response will send, but you shouldn\'t use res.writeHead in stream mode')
|
||||
}
|
||||
payload.pipe(res)
|
||||
}
|
||||
|
||||
function sendTrailer (payload, res, reply) {
|
||||
if (reply[kReplyTrailers] === null) {
|
||||
// when no trailer, we close the stream
|
||||
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
return
|
||||
}
|
||||
const trailerHeaders = Object.keys(reply[kReplyTrailers])
|
||||
const trailers = {}
|
||||
let handled = 0
|
||||
let skipped = true
|
||||
function send () {
|
||||
// add trailers when all handler handled
|
||||
/* istanbul ignore else */
|
||||
if (handled === 0) {
|
||||
res.addTrailers(trailers)
|
||||
// we need to properly close the stream
|
||||
// after trailers sent
|
||||
res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
}
|
||||
}
|
||||
|
||||
for (const trailerName of trailerHeaders) {
|
||||
if (typeof reply[kReplyTrailers][trailerName] !== 'function') continue
|
||||
skipped = false
|
||||
handled--
|
||||
|
||||
function cb (err, value) {
|
||||
// TODO: we may protect multiple callback calls
|
||||
// or mixing async-await with callback
|
||||
handled++
|
||||
|
||||
// we can safely ignore error for trailer
|
||||
// since it does affect the client
|
||||
// we log in here only for debug usage
|
||||
if (err) reply.log.debug(err)
|
||||
else trailers[trailerName] = value
|
||||
|
||||
// we push the check to the end of event
|
||||
// loop, so the registration continue to
|
||||
// process.
|
||||
process.nextTick(send)
|
||||
}
|
||||
|
||||
const result = reply[kReplyTrailers][trailerName](reply, payload, cb)
|
||||
if (typeof result === 'object' && typeof result.then === 'function') {
|
||||
result.then((v) => cb(null, v), cb)
|
||||
}
|
||||
}
|
||||
|
||||
// when all trailers are skipped
|
||||
// we need to close the stream
|
||||
if (skipped) res.end(null, null, null) // avoid ArgumentsAdaptorTrampoline from V8
|
||||
}
|
||||
|
||||
function sendStreamTrailer (payload, res, reply) {
|
||||
if (reply[kReplyTrailers] === null) return
|
||||
payload.on('end', () => sendTrailer(null, res, reply))
|
||||
}
|
||||
|
||||
function onErrorHook (reply, error, cb) {
|
||||
if (reply[kRouteContext].onError !== null && !reply[kReplyNextErrorHandler]) {
|
||||
reply[kReplyIsRunningOnErrorHook] = true
|
||||
onSendHookRunner(
|
||||
reply[kRouteContext].onError,
|
||||
reply.request,
|
||||
reply,
|
||||
error,
|
||||
() => handleError(reply, error, cb)
|
||||
)
|
||||
} else {
|
||||
handleError(reply, error, cb)
|
||||
}
|
||||
}
|
||||
|
||||
function setupResponseListeners (reply) {
|
||||
reply[kReplyStartTime] = now()
|
||||
|
||||
const onResFinished = err => {
|
||||
reply[kReplyEndTime] = now()
|
||||
reply.raw.removeListener('finish', onResFinished)
|
||||
reply.raw.removeListener('error', onResFinished)
|
||||
|
||||
const ctx = reply[kRouteContext]
|
||||
|
||||
if (ctx && ctx.onResponse !== null) {
|
||||
onResponseHookRunner(
|
||||
ctx.onResponse,
|
||||
reply.request,
|
||||
reply,
|
||||
onResponseCallback
|
||||
)
|
||||
} else {
|
||||
onResponseCallback(err, reply.request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
reply.raw.on('finish', onResFinished)
|
||||
reply.raw.on('error', onResFinished)
|
||||
}
|
||||
|
||||
function onResponseCallback (err, request, reply) {
|
||||
if (reply.log[kDisableRequestLogging]) {
|
||||
return
|
||||
}
|
||||
|
||||
const responseTime = reply.elapsedTime
|
||||
|
||||
if (err != null) {
|
||||
reply.log.error({
|
||||
res: reply,
|
||||
err,
|
||||
responseTime
|
||||
}, 'request errored')
|
||||
return
|
||||
}
|
||||
|
||||
reply.log.info({
|
||||
res: reply,
|
||||
responseTime
|
||||
}, 'request completed')
|
||||
}
|
||||
|
||||
function buildReply (R) {
|
||||
const props = R.props.slice()
|
||||
|
||||
function _Reply (res, request, log) {
|
||||
this.raw = res
|
||||
this[kReplyIsError] = false
|
||||
this[kReplyErrorHandlerCalled] = false
|
||||
this[kReplyHijacked] = false
|
||||
this[kReplySerializer] = null
|
||||
this.request = request
|
||||
this[kReplyHeaders] = {}
|
||||
this[kReplyTrailers] = null
|
||||
this[kReplyStartTime] = undefined
|
||||
this[kReplyEndTime] = undefined
|
||||
this.log = log
|
||||
|
||||
let prop
|
||||
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
prop = props[i]
|
||||
this[prop.key] = prop.value
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(_Reply.prototype, R.prototype)
|
||||
Object.setPrototypeOf(_Reply, R)
|
||||
_Reply.parent = R
|
||||
_Reply.props = props
|
||||
return _Reply
|
||||
}
|
||||
|
||||
function notFound (reply) {
|
||||
if (reply[kRouteContext][kFourOhFourContext] === null) {
|
||||
reply.log.warn('Trying to send a NotFound error inside a 404 handler. Sending basic 404 response.')
|
||||
reply.code(404).send('404 Not Found')
|
||||
return
|
||||
}
|
||||
|
||||
reply.request[kRouteContext] = reply[kRouteContext][kFourOhFourContext]
|
||||
|
||||
// preHandler hook
|
||||
if (reply[kRouteContext].preHandler !== null) {
|
||||
preHandlerHookRunner(
|
||||
reply[kRouteContext].preHandler,
|
||||
reply.request,
|
||||
reply,
|
||||
internals.preHandlerCallback
|
||||
)
|
||||
} else {
|
||||
internals.preHandlerCallback(null, reply.request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function runs when a payload that is not a string|buffer|stream or null
|
||||
* should be serialized to be streamed to the response.
|
||||
* This is the default serializer that can be customized by the user using the replySerializer
|
||||
*
|
||||
* @param {object} context the request context
|
||||
* @param {object} data the JSON payload to serialize
|
||||
* @param {number} statusCode the http status code
|
||||
* @param {string} [contentType] the reply content type
|
||||
* @returns {string} the serialized payload
|
||||
*/
|
||||
function serialize (context, data, statusCode, contentType) {
|
||||
const fnSerialize = getSchemaSerializer(context, statusCode, contentType)
|
||||
if (fnSerialize) {
|
||||
return fnSerialize(data)
|
||||
}
|
||||
return JSON.stringify(data)
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
module.exports = Reply
|
||||
module.exports.buildReply = buildReply
|
||||
module.exports.setupResponseListeners = setupResponseListeners
|
||||
52
node_modules/fastify/lib/reqIdGenFactory.js
generated
vendored
Normal file
52
node_modules/fastify/lib/reqIdGenFactory.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @callback GenerateRequestId
|
||||
* @param {Object} req
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} [requestIdHeader]
|
||||
* @param {GenerateRequestId} [optGenReqId]
|
||||
* @returns {GenerateRequestId}
|
||||
*/
|
||||
function reqIdGenFactory (requestIdHeader, optGenReqId) {
|
||||
const genReqId = optGenReqId || buildDefaultGenReqId()
|
||||
|
||||
if (requestIdHeader) {
|
||||
return buildOptionalHeaderReqId(requestIdHeader, genReqId)
|
||||
}
|
||||
|
||||
return genReqId
|
||||
}
|
||||
|
||||
function getGenReqId (contextServer, req) {
|
||||
return contextServer.genReqId(req)
|
||||
}
|
||||
|
||||
function buildDefaultGenReqId () {
|
||||
// 2,147,483,647 (2^31 − 1) stands for max SMI value (an internal optimization of V8).
|
||||
// With this upper bound, if you'll be generating 1k ids/sec, you're going to hit it in ~25 days.
|
||||
// This is very likely to happen in real-world applications, hence the limit is enforced.
|
||||
// Growing beyond this value will make the id generation slower and cause a deopt.
|
||||
// In the worst cases, it will become a float, losing accuracy.
|
||||
const maxInt = 2147483647
|
||||
|
||||
let nextReqId = 0
|
||||
return function defaultGenReqId () {
|
||||
nextReqId = (nextReqId + 1) & maxInt
|
||||
return `req-${nextReqId.toString(36)}`
|
||||
}
|
||||
}
|
||||
|
||||
function buildOptionalHeaderReqId (requestIdHeader, genReqId) {
|
||||
return function (req) {
|
||||
return req.headers[requestIdHeader] || genReqId(req)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getGenReqId,
|
||||
reqIdGenFactory
|
||||
}
|
||||
369
node_modules/fastify/lib/request.js
generated
vendored
Normal file
369
node_modules/fastify/lib/request.js
generated
vendored
Normal file
@@ -0,0 +1,369 @@
|
||||
'use strict'
|
||||
|
||||
const proxyAddr = require('@fastify/proxy-addr')
|
||||
const {
|
||||
kHasBeenDecorated,
|
||||
kSchemaBody,
|
||||
kSchemaHeaders,
|
||||
kSchemaParams,
|
||||
kSchemaQuerystring,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kRequestCacheValidateFns,
|
||||
kRouteContext,
|
||||
kRequestOriginalUrl
|
||||
} = require('./symbols')
|
||||
const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION, FST_ERR_DEC_UNDECLARED } = require('./errors')
|
||||
const decorators = require('./decorate')
|
||||
|
||||
const HTTP_PART_SYMBOL_MAP = {
|
||||
body: kSchemaBody,
|
||||
headers: kSchemaHeaders,
|
||||
params: kSchemaParams,
|
||||
querystring: kSchemaQuerystring,
|
||||
query: kSchemaQuerystring
|
||||
}
|
||||
|
||||
function Request (id, params, req, query, log, context) {
|
||||
this.id = id
|
||||
this[kRouteContext] = context
|
||||
this.params = params
|
||||
this.raw = req
|
||||
this.query = query
|
||||
this.log = log
|
||||
this.body = undefined
|
||||
}
|
||||
Request.props = []
|
||||
|
||||
function getTrustProxyFn (tp) {
|
||||
if (typeof tp === 'function') {
|
||||
return tp
|
||||
}
|
||||
if (tp === true) {
|
||||
// Support trusting everything
|
||||
return null
|
||||
}
|
||||
if (typeof tp === 'number') {
|
||||
// Support trusting hop count
|
||||
return function (a, i) { return i < tp }
|
||||
}
|
||||
if (typeof tp === 'string') {
|
||||
// Support comma-separated tps
|
||||
const values = tp.split(',').map(it => it.trim())
|
||||
return proxyAddr.compile(values)
|
||||
}
|
||||
return proxyAddr.compile(tp)
|
||||
}
|
||||
|
||||
function buildRequest (R, trustProxy) {
|
||||
if (trustProxy) {
|
||||
return buildRequestWithTrustProxy(R, trustProxy)
|
||||
}
|
||||
|
||||
return buildRegularRequest(R)
|
||||
}
|
||||
|
||||
function buildRegularRequest (R) {
|
||||
const props = R.props.slice()
|
||||
function _Request (id, params, req, query, log, context) {
|
||||
this.id = id
|
||||
this[kRouteContext] = context
|
||||
this.params = params
|
||||
this.raw = req
|
||||
this.query = query
|
||||
this.log = log
|
||||
this.body = undefined
|
||||
|
||||
let prop
|
||||
for (let i = 0; i < props.length; i++) {
|
||||
prop = props[i]
|
||||
this[prop.key] = prop.value
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(_Request.prototype, R.prototype)
|
||||
Object.setPrototypeOf(_Request, R)
|
||||
_Request.props = props
|
||||
_Request.parent = R
|
||||
|
||||
return _Request
|
||||
}
|
||||
|
||||
function getLastEntryInMultiHeaderValue (headerValue) {
|
||||
// we use the last one if the header is set more than once
|
||||
const lastIndex = headerValue.lastIndexOf(',')
|
||||
return lastIndex === -1 ? headerValue.trim() : headerValue.slice(lastIndex + 1).trim()
|
||||
}
|
||||
|
||||
function buildRequestWithTrustProxy (R, trustProxy) {
|
||||
const _Request = buildRegularRequest(R)
|
||||
const proxyFn = getTrustProxyFn(trustProxy)
|
||||
|
||||
// This is a more optimized version of decoration
|
||||
_Request[kHasBeenDecorated] = true
|
||||
|
||||
Object.defineProperties(_Request.prototype, {
|
||||
ip: {
|
||||
get () {
|
||||
const addrs = proxyAddr.all(this.raw, proxyFn)
|
||||
return addrs[addrs.length - 1]
|
||||
}
|
||||
},
|
||||
ips: {
|
||||
get () {
|
||||
return proxyAddr.all(this.raw, proxyFn)
|
||||
}
|
||||
},
|
||||
host: {
|
||||
get () {
|
||||
if (this.ip !== undefined && this.headers['x-forwarded-host']) {
|
||||
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
|
||||
}
|
||||
/**
|
||||
* The last fallback supports the following cases:
|
||||
* 1. http.requireHostHeader === false
|
||||
* 2. HTTP/1.0 without a Host Header
|
||||
* 3. Headers schema that may remove the Host Header
|
||||
*/
|
||||
return this.headers.host ?? this.headers[':authority'] ?? ''
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
get () {
|
||||
if (this.headers['x-forwarded-proto']) {
|
||||
return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto'])
|
||||
}
|
||||
if (this.socket) {
|
||||
return this.socket.encrypted ? 'https' : 'http'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return _Request
|
||||
}
|
||||
|
||||
function assertsRequestDecoration (request, name) {
|
||||
if (!decorators.hasKey(request, name) && !decorators.exist(request, name)) {
|
||||
throw new FST_ERR_DEC_UNDECLARED(name, 'request')
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperties(Request.prototype, {
|
||||
server: {
|
||||
get () {
|
||||
return this[kRouteContext].server
|
||||
}
|
||||
},
|
||||
url: {
|
||||
get () {
|
||||
return this.raw.url
|
||||
}
|
||||
},
|
||||
originalUrl: {
|
||||
get () {
|
||||
/* istanbul ignore else */
|
||||
if (!this[kRequestOriginalUrl]) {
|
||||
this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url
|
||||
}
|
||||
return this[kRequestOriginalUrl]
|
||||
}
|
||||
},
|
||||
method: {
|
||||
get () {
|
||||
return this.raw.method
|
||||
}
|
||||
},
|
||||
routeOptions: {
|
||||
get () {
|
||||
const context = this[kRouteContext]
|
||||
const routeLimit = context._parserOptions.limit
|
||||
const serverLimit = context.server.initialConfig.bodyLimit
|
||||
const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
|
||||
const options = {
|
||||
method: context.config?.method,
|
||||
url: context.config?.url,
|
||||
bodyLimit: (routeLimit || serverLimit),
|
||||
attachValidation: context.attachValidation,
|
||||
logLevel: context.logLevel,
|
||||
exposeHeadRoute: context.exposeHeadRoute,
|
||||
prefixTrailingSlash: context.prefixTrailingSlash,
|
||||
handler: context.handler,
|
||||
config: context.config,
|
||||
schema: context.schema,
|
||||
version
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
},
|
||||
is404: {
|
||||
get () {
|
||||
return this[kRouteContext].config?.url === undefined
|
||||
}
|
||||
},
|
||||
socket: {
|
||||
get () {
|
||||
return this.raw.socket
|
||||
}
|
||||
},
|
||||
ip: {
|
||||
get () {
|
||||
if (this.socket) {
|
||||
return this.socket.remoteAddress
|
||||
}
|
||||
}
|
||||
},
|
||||
host: {
|
||||
get () {
|
||||
/**
|
||||
* The last fallback supports the following cases:
|
||||
* 1. http.requireHostHeader === false
|
||||
* 2. HTTP/1.0 without a Host Header
|
||||
* 3. Headers schema that may remove the Host Header
|
||||
*/
|
||||
return this.raw.headers.host ?? this.raw.headers[':authority'] ?? ''
|
||||
}
|
||||
},
|
||||
hostname: {
|
||||
get () {
|
||||
return this.host.split(':', 1)[0]
|
||||
}
|
||||
},
|
||||
port: {
|
||||
get () {
|
||||
// first try taking port from host
|
||||
const portFromHost = parseInt(this.host.split(':').slice(-1)[0])
|
||||
if (!isNaN(portFromHost)) {
|
||||
return portFromHost
|
||||
}
|
||||
// now fall back to port from host/:authority header
|
||||
const host = (this.headers.host ?? this.headers[':authority'] ?? '')
|
||||
const portFromHeader = parseInt(host.split(':').slice(-1)[0])
|
||||
if (!isNaN(portFromHeader)) {
|
||||
return portFromHeader
|
||||
}
|
||||
// fall back to null
|
||||
return null
|
||||
}
|
||||
},
|
||||
protocol: {
|
||||
get () {
|
||||
if (this.socket) {
|
||||
return this.socket.encrypted ? 'https' : 'http'
|
||||
}
|
||||
}
|
||||
},
|
||||
headers: {
|
||||
get () {
|
||||
if (this.additionalHeaders) {
|
||||
return Object.assign({}, this.raw.headers, this.additionalHeaders)
|
||||
}
|
||||
return this.raw.headers
|
||||
},
|
||||
set (headers) {
|
||||
this.additionalHeaders = headers
|
||||
}
|
||||
},
|
||||
getValidationFunction: {
|
||||
value: function (httpPartOrSchema) {
|
||||
if (typeof httpPartOrSchema === 'string') {
|
||||
const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema]
|
||||
return this[kRouteContext][symbol]
|
||||
} else if (typeof httpPartOrSchema === 'object') {
|
||||
return this[kRouteContext][kRequestCacheValidateFns]?.get(httpPartOrSchema)
|
||||
}
|
||||
}
|
||||
},
|
||||
compileValidationSchema: {
|
||||
value: function (schema, httpPart = null) {
|
||||
const { method, url } = this
|
||||
|
||||
if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
||||
return this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
||||
}
|
||||
|
||||
const validatorCompiler = this[kRouteContext].validatorCompiler ||
|
||||
this.server[kSchemaController].validatorCompiler ||
|
||||
(
|
||||
// We compile the schemas if no custom validatorCompiler is provided
|
||||
// nor set
|
||||
this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
|
||||
this.server[kSchemaController].validatorCompiler
|
||||
)
|
||||
|
||||
const validateFn = validatorCompiler({
|
||||
schema,
|
||||
method,
|
||||
url,
|
||||
httpPart
|
||||
})
|
||||
|
||||
// We create a WeakMap to compile the schema only once
|
||||
// Its done lazily to avoid add overhead by creating the WeakMap
|
||||
// if it is not used
|
||||
// TODO: Explore a central cache for all the schemas shared across
|
||||
// encapsulated contexts
|
||||
if (this[kRouteContext][kRequestCacheValidateFns] == null) {
|
||||
this[kRouteContext][kRequestCacheValidateFns] = new WeakMap()
|
||||
}
|
||||
|
||||
this[kRouteContext][kRequestCacheValidateFns].set(schema, validateFn)
|
||||
|
||||
return validateFn
|
||||
}
|
||||
},
|
||||
validateInput: {
|
||||
value: function (input, schema, httpPart) {
|
||||
httpPart = typeof schema === 'string' ? schema : httpPart
|
||||
|
||||
const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart]
|
||||
let validate
|
||||
|
||||
if (symbol) {
|
||||
// Validate using the HTTP Request Part schema
|
||||
validate = this[kRouteContext][symbol]
|
||||
}
|
||||
|
||||
// We cannot compile if the schema is missed
|
||||
if (validate == null && (schema == null ||
|
||||
typeof schema !== 'object' ||
|
||||
Array.isArray(schema))
|
||||
) {
|
||||
throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
|
||||
}
|
||||
|
||||
if (validate == null) {
|
||||
if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
||||
validate = this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
||||
} else {
|
||||
// We proceed to compile if there's no validate function yet
|
||||
validate = this.compileValidationSchema(schema, httpPart)
|
||||
}
|
||||
}
|
||||
|
||||
return validate(input)
|
||||
}
|
||||
},
|
||||
getDecorator: {
|
||||
value: function (name) {
|
||||
assertsRequestDecoration(this, name)
|
||||
|
||||
const decorator = this[name]
|
||||
if (typeof decorator === 'function') {
|
||||
return decorator.bind(this)
|
||||
}
|
||||
|
||||
return decorator
|
||||
}
|
||||
},
|
||||
setDecorator: {
|
||||
value: function (name, value) {
|
||||
assertsRequestDecoration(this, name)
|
||||
|
||||
this[name] = value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = Request
|
||||
module.exports.buildRequest = buildRequest
|
||||
620
node_modules/fastify/lib/route.js
generated
vendored
Normal file
620
node_modules/fastify/lib/route.js
generated
vendored
Normal file
@@ -0,0 +1,620 @@
|
||||
'use strict'
|
||||
|
||||
const FindMyWay = require('find-my-way')
|
||||
const Context = require('./context')
|
||||
const handleRequest = require('./handleRequest')
|
||||
const { onRequestAbortHookRunner, lifecycleHooks, preParsingHookRunner, onTimeoutHookRunner, onRequestHookRunner } = require('./hooks')
|
||||
const { normalizeSchema } = require('./schemas')
|
||||
const { parseHeadOnSendHandlers } = require('./headRoute')
|
||||
|
||||
const {
|
||||
compileSchemasForValidation,
|
||||
compileSchemasForSerialization
|
||||
} = require('./validation')
|
||||
|
||||
const {
|
||||
FST_ERR_SCH_VALIDATION_BUILD,
|
||||
FST_ERR_SCH_SERIALIZATION_BUILD,
|
||||
FST_ERR_DUPLICATED_ROUTE,
|
||||
FST_ERR_INVALID_URL,
|
||||
FST_ERR_HOOK_INVALID_HANDLER,
|
||||
FST_ERR_ROUTE_OPTIONS_NOT_OBJ,
|
||||
FST_ERR_ROUTE_DUPLICATED_HANDLER,
|
||||
FST_ERR_ROUTE_HANDLER_NOT_FN,
|
||||
FST_ERR_ROUTE_MISSING_HANDLER,
|
||||
FST_ERR_ROUTE_METHOD_NOT_SUPPORTED,
|
||||
FST_ERR_ROUTE_METHOD_INVALID,
|
||||
FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED,
|
||||
FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT,
|
||||
FST_ERR_HOOK_INVALID_ASYNC_HANDLER
|
||||
} = require('./errors')
|
||||
|
||||
const { FSTDEP022 } = require('./warnings')
|
||||
|
||||
const {
|
||||
kRoutePrefix,
|
||||
kSupportedHTTPMethods,
|
||||
kLogLevel,
|
||||
kLogSerializers,
|
||||
kHooks,
|
||||
kSchemaController,
|
||||
kOptions,
|
||||
kReplySerializerDefault,
|
||||
kReplyIsError,
|
||||
kRequestPayloadStream,
|
||||
kDisableRequestLogging,
|
||||
kSchemaErrorFormatter,
|
||||
kErrorHandler,
|
||||
kHasBeenDecorated,
|
||||
kRequestAcceptVersion,
|
||||
kRouteByFastify,
|
||||
kRouteContext
|
||||
} = require('./symbols.js')
|
||||
const { buildErrorHandler } = require('./error-handler')
|
||||
const { createChildLogger } = require('./logger-factory.js')
|
||||
const { getGenReqId } = require('./reqIdGenFactory.js')
|
||||
|
||||
const routerKeys = [
|
||||
'allowUnsafeRegex',
|
||||
'buildPrettyMeta',
|
||||
'caseSensitive',
|
||||
'constraints',
|
||||
'defaultRoute',
|
||||
'ignoreDuplicateSlashes',
|
||||
'ignoreTrailingSlash',
|
||||
'maxParamLength',
|
||||
'onBadUrl',
|
||||
'querystringParser',
|
||||
'useSemicolonDelimiter'
|
||||
]
|
||||
|
||||
function buildRouting (options) {
|
||||
const router = FindMyWay(options.config)
|
||||
|
||||
let avvio
|
||||
let fourOhFour
|
||||
let logger
|
||||
let hasLogger
|
||||
let setupResponseListeners
|
||||
let throwIfAlreadyStarted
|
||||
let disableRequestLogging
|
||||
let ignoreTrailingSlash
|
||||
let ignoreDuplicateSlashes
|
||||
let return503OnClosing
|
||||
let globalExposeHeadRoutes
|
||||
let keepAliveConnections
|
||||
|
||||
let closing = false
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {import('../fastify').FastifyServerOptions} options
|
||||
* @param {*} fastifyArgs
|
||||
*/
|
||||
setup (options, fastifyArgs) {
|
||||
avvio = fastifyArgs.avvio
|
||||
fourOhFour = fastifyArgs.fourOhFour
|
||||
logger = fastifyArgs.logger
|
||||
hasLogger = fastifyArgs.hasLogger
|
||||
setupResponseListeners = fastifyArgs.setupResponseListeners
|
||||
throwIfAlreadyStarted = fastifyArgs.throwIfAlreadyStarted
|
||||
|
||||
globalExposeHeadRoutes = options.exposeHeadRoutes
|
||||
disableRequestLogging = options.disableRequestLogging
|
||||
ignoreTrailingSlash = options.routerOptions.ignoreTrailingSlash
|
||||
ignoreDuplicateSlashes = options.routerOptions.ignoreDuplicateSlashes
|
||||
return503OnClosing = Object.hasOwn(options, 'return503OnClosing') ? options.return503OnClosing : true
|
||||
keepAliveConnections = fastifyArgs.keepAliveConnections
|
||||
},
|
||||
routing: router.lookup.bind(router), // router func to find the right handler to call
|
||||
route, // configure a route in the fastify instance
|
||||
hasRoute,
|
||||
prepareRoute,
|
||||
routeHandler,
|
||||
closeRoutes: () => { closing = true },
|
||||
printRoutes: router.prettyPrint.bind(router),
|
||||
addConstraintStrategy,
|
||||
hasConstraintStrategy,
|
||||
isAsyncConstraint,
|
||||
findRoute
|
||||
}
|
||||
|
||||
function addConstraintStrategy (strategy) {
|
||||
throwIfAlreadyStarted('Cannot add constraint strategy!')
|
||||
return router.addConstraintStrategy(strategy)
|
||||
}
|
||||
|
||||
function hasConstraintStrategy (strategyName) {
|
||||
return router.hasConstraintStrategy(strategyName)
|
||||
}
|
||||
|
||||
function isAsyncConstraint () {
|
||||
return router.constrainer.asyncStrategiesInUse.size > 0
|
||||
}
|
||||
|
||||
// Convert shorthand to extended route declaration
|
||||
function prepareRoute ({ method, url, options, handler, isFastify }) {
|
||||
if (typeof url !== 'string') {
|
||||
throw new FST_ERR_INVALID_URL(typeof url)
|
||||
}
|
||||
|
||||
if (!handler && typeof options === 'function') {
|
||||
handler = options // for support over direct function calls such as fastify.get() options are reused as the handler
|
||||
options = {}
|
||||
} else if (handler && typeof handler === 'function') {
|
||||
if (Object.prototype.toString.call(options) !== '[object Object]') {
|
||||
throw new FST_ERR_ROUTE_OPTIONS_NOT_OBJ(method, url)
|
||||
} else if (options.handler) {
|
||||
if (typeof options.handler === 'function') {
|
||||
throw new FST_ERR_ROUTE_DUPLICATED_HANDLER(method, url)
|
||||
} else {
|
||||
throw new FST_ERR_ROUTE_HANDLER_NOT_FN(method, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
options = Object.assign({}, options, {
|
||||
method,
|
||||
url,
|
||||
path: url,
|
||||
handler: handler || (options && options.handler)
|
||||
})
|
||||
|
||||
return route.call(this, { options, isFastify })
|
||||
}
|
||||
|
||||
function hasRoute ({ options }) {
|
||||
const normalizedMethod = options.method?.toUpperCase() ?? ''
|
||||
return router.hasRoute(
|
||||
normalizedMethod,
|
||||
options.url || '',
|
||||
options.constraints
|
||||
)
|
||||
}
|
||||
|
||||
function findRoute (options) {
|
||||
const route = router.find(
|
||||
options.method,
|
||||
options.url || '',
|
||||
options.constraints
|
||||
)
|
||||
if (route) {
|
||||
// we must reduce the expose surface, otherwise
|
||||
// we provide the ability for the user to modify
|
||||
// all the route and server information in runtime
|
||||
return {
|
||||
handler: route.handler,
|
||||
params: route.params,
|
||||
searchParams: route.searchParams
|
||||
}
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Route management
|
||||
* @param {{ options: import('../fastify').RouteOptions, isFastify: boolean }}
|
||||
*/
|
||||
function route ({ options, isFastify }) {
|
||||
throwIfAlreadyStarted('Cannot add route!')
|
||||
|
||||
// Since we are mutating/assigning only top level props, it is fine to have a shallow copy using the spread operator
|
||||
const opts = { ...options }
|
||||
|
||||
const path = opts.url || opts.path || ''
|
||||
|
||||
if (!opts.handler) {
|
||||
throw new FST_ERR_ROUTE_MISSING_HANDLER(opts.method, path)
|
||||
}
|
||||
|
||||
if (opts.errorHandler !== undefined && typeof opts.errorHandler !== 'function') {
|
||||
throw new FST_ERR_ROUTE_HANDLER_NOT_FN(opts.method, path)
|
||||
}
|
||||
|
||||
validateBodyLimitOption(opts.bodyLimit)
|
||||
|
||||
const shouldExposeHead = opts.exposeHeadRoute ?? globalExposeHeadRoutes
|
||||
|
||||
let isGetRoute = false
|
||||
let isHeadRoute = false
|
||||
|
||||
if (Array.isArray(opts.method)) {
|
||||
for (let i = 0; i < opts.method.length; ++i) {
|
||||
opts.method[i] = normalizeAndValidateMethod.call(this, opts.method[i])
|
||||
validateSchemaBodyOption.call(this, opts.method[i], path, opts.schema)
|
||||
|
||||
isGetRoute = opts.method.includes('GET')
|
||||
isHeadRoute = opts.method.includes('HEAD')
|
||||
}
|
||||
} else {
|
||||
opts.method = normalizeAndValidateMethod.call(this, opts.method)
|
||||
validateSchemaBodyOption.call(this, opts.method, path, opts.schema)
|
||||
|
||||
isGetRoute = opts.method === 'GET'
|
||||
isHeadRoute = opts.method === 'HEAD'
|
||||
}
|
||||
|
||||
// we need to clone a set of initial options for HEAD route
|
||||
const headOpts = shouldExposeHead && isGetRoute ? { ...options } : null
|
||||
|
||||
const prefix = this[kRoutePrefix]
|
||||
|
||||
if (path === '/' && prefix.length > 0 && opts.method !== 'HEAD') {
|
||||
switch (opts.prefixTrailingSlash) {
|
||||
case 'slash':
|
||||
addNewRoute.call(this, { path, isFastify })
|
||||
break
|
||||
case 'no-slash':
|
||||
addNewRoute.call(this, { path: '', isFastify })
|
||||
break
|
||||
case 'both':
|
||||
default:
|
||||
addNewRoute.call(this, { path: '', isFastify })
|
||||
// If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
|
||||
if (ignoreTrailingSlash !== true && (ignoreDuplicateSlashes !== true || !prefix.endsWith('/'))) {
|
||||
addNewRoute.call(this, { path, prefixing: true, isFastify })
|
||||
}
|
||||
}
|
||||
} else if (path[0] === '/' && prefix.endsWith('/')) {
|
||||
// Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
|
||||
addNewRoute.call(this, { path: path.slice(1), isFastify })
|
||||
} else {
|
||||
addNewRoute.call(this, { path, isFastify })
|
||||
}
|
||||
|
||||
// chainable api
|
||||
return this
|
||||
|
||||
function addNewRoute ({ path, prefixing = false, isFastify = false }) {
|
||||
const url = prefix + path
|
||||
|
||||
opts.url = url
|
||||
opts.path = url
|
||||
opts.routePath = path
|
||||
opts.prefix = prefix
|
||||
opts.logLevel = opts.logLevel || this[kLogLevel]
|
||||
|
||||
if (this[kLogSerializers] || opts.logSerializers) {
|
||||
opts.logSerializers = Object.assign(Object.create(this[kLogSerializers]), opts.logSerializers)
|
||||
}
|
||||
|
||||
if (opts.attachValidation == null) {
|
||||
opts.attachValidation = false
|
||||
}
|
||||
|
||||
if (prefixing === false) {
|
||||
// run 'onRoute' hooks
|
||||
for (const hook of this[kHooks].onRoute) {
|
||||
hook.call(this, opts)
|
||||
}
|
||||
}
|
||||
|
||||
for (const hook of lifecycleHooks) {
|
||||
if (opts && hook in opts) {
|
||||
if (Array.isArray(opts[hook])) {
|
||||
for (const func of opts[hook]) {
|
||||
if (typeof func !== 'function') {
|
||||
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(func))
|
||||
}
|
||||
|
||||
if (hook === 'onSend' || hook === 'preSerialization' || hook === 'onError' || hook === 'preParsing') {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length === 4) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else if (hook === 'onRequestAbort') {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length !== 1) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
} else {
|
||||
if (func.constructor.name === 'AsyncFunction' && func.length === 3) {
|
||||
throw new FST_ERR_HOOK_INVALID_ASYNC_HANDLER()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (opts[hook] !== undefined && typeof opts[hook] !== 'function') {
|
||||
throw new FST_ERR_HOOK_INVALID_HANDLER(hook, Object.prototype.toString.call(opts[hook]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const constraints = opts.constraints || {}
|
||||
const config = {
|
||||
...opts.config,
|
||||
url,
|
||||
method: opts.method
|
||||
}
|
||||
|
||||
const context = new Context({
|
||||
schema: opts.schema,
|
||||
handler: opts.handler.bind(this),
|
||||
config,
|
||||
errorHandler: opts.errorHandler,
|
||||
childLoggerFactory: opts.childLoggerFactory,
|
||||
bodyLimit: opts.bodyLimit,
|
||||
logLevel: opts.logLevel,
|
||||
logSerializers: opts.logSerializers,
|
||||
attachValidation: opts.attachValidation,
|
||||
schemaErrorFormatter: opts.schemaErrorFormatter,
|
||||
replySerializer: this[kReplySerializerDefault],
|
||||
validatorCompiler: opts.validatorCompiler,
|
||||
serializerCompiler: opts.serializerCompiler,
|
||||
exposeHeadRoute: shouldExposeHead,
|
||||
prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'),
|
||||
server: this,
|
||||
isFastify
|
||||
})
|
||||
|
||||
const headHandler = router.findRoute('HEAD', opts.url, constraints)
|
||||
const hasHEADHandler = headHandler !== null
|
||||
|
||||
try {
|
||||
router.on(opts.method, opts.url, { constraints }, routeHandler, context)
|
||||
} catch (error) {
|
||||
// any route insertion error created by fastify can be safely ignore
|
||||
// because it only duplicate route for head
|
||||
if (!context[kRouteByFastify]) {
|
||||
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route`)
|
||||
if (isDuplicatedRoute) {
|
||||
throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url)
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
this.after((notHandledErr, done) => {
|
||||
// Send context async
|
||||
context.errorHandler = opts.errorHandler ? buildErrorHandler(this[kErrorHandler], opts.errorHandler) : this[kErrorHandler]
|
||||
context._parserOptions.limit = opts.bodyLimit || null
|
||||
context.logLevel = opts.logLevel
|
||||
context.logSerializers = opts.logSerializers
|
||||
context.attachValidation = opts.attachValidation
|
||||
context[kReplySerializerDefault] = this[kReplySerializerDefault]
|
||||
context.schemaErrorFormatter = opts.schemaErrorFormatter || this[kSchemaErrorFormatter] || context.schemaErrorFormatter
|
||||
|
||||
// Run hooks and more
|
||||
avvio.once('preReady', () => {
|
||||
for (const hook of lifecycleHooks) {
|
||||
const toSet = this[kHooks][hook]
|
||||
.concat(opts[hook] || [])
|
||||
.map(h => h.bind(this))
|
||||
context[hook] = toSet.length ? toSet : null
|
||||
}
|
||||
|
||||
// Optimization: avoid encapsulation if no decoration has been done.
|
||||
while (!context.Request[kHasBeenDecorated] && context.Request.parent) {
|
||||
context.Request = context.Request.parent
|
||||
}
|
||||
while (!context.Reply[kHasBeenDecorated] && context.Reply.parent) {
|
||||
context.Reply = context.Reply.parent
|
||||
}
|
||||
|
||||
// Must store the 404 Context in 'preReady' because it is only guaranteed to
|
||||
// be available after all of the plugins and routes have been loaded.
|
||||
fourOhFour.setContext(this, context)
|
||||
|
||||
if (opts.schema) {
|
||||
context.schema = normalizeSchema(context.schema, this.initialConfig)
|
||||
|
||||
const schemaController = this[kSchemaController]
|
||||
if (!opts.validatorCompiler && (opts.schema.body || opts.schema.headers || opts.schema.querystring || opts.schema.params)) {
|
||||
schemaController.setupValidator(this[kOptions])
|
||||
}
|
||||
try {
|
||||
const isCustom = typeof opts?.validatorCompiler === 'function' || schemaController.isCustomValidatorCompiler
|
||||
compileSchemasForValidation(context, opts.validatorCompiler || schemaController.validatorCompiler, isCustom)
|
||||
} catch (error) {
|
||||
throw new FST_ERR_SCH_VALIDATION_BUILD(opts.method, url, error.message)
|
||||
}
|
||||
|
||||
if (opts.schema.response && !opts.serializerCompiler) {
|
||||
schemaController.setupSerializer(this[kOptions])
|
||||
}
|
||||
try {
|
||||
compileSchemasForSerialization(context, opts.serializerCompiler || schemaController.serializerCompiler)
|
||||
} catch (error) {
|
||||
throw new FST_ERR_SCH_SERIALIZATION_BUILD(opts.method, url, error.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
done(notHandledErr)
|
||||
})
|
||||
|
||||
// register head route in sync
|
||||
// we must place it after the `this.after`
|
||||
|
||||
if (shouldExposeHead && isGetRoute && !isHeadRoute && !hasHEADHandler) {
|
||||
const onSendHandlers = parseHeadOnSendHandlers(headOpts.onSend)
|
||||
prepareRoute.call(this, { method: 'HEAD', url: path, options: { ...headOpts, onSend: onSendHandlers }, isFastify: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP request entry point, the routing has already been executed
|
||||
function routeHandler (req, res, params, context, query) {
|
||||
const id = getGenReqId(context.server, req)
|
||||
|
||||
const loggerOpts = {
|
||||
level: context.logLevel
|
||||
}
|
||||
|
||||
if (context.logSerializers) {
|
||||
loggerOpts.serializers = context.logSerializers
|
||||
}
|
||||
const childLogger = createChildLogger(context, logger, req, id, loggerOpts)
|
||||
childLogger[kDisableRequestLogging] = disableRequestLogging
|
||||
|
||||
if (closing === true) {
|
||||
/* istanbul ignore next mac, windows */
|
||||
if (req.httpVersionMajor !== 2) {
|
||||
res.setHeader('Connection', 'close')
|
||||
}
|
||||
|
||||
// TODO remove return503OnClosing after Node v18 goes EOL
|
||||
/* istanbul ignore else */
|
||||
if (return503OnClosing) {
|
||||
// On Node v19 we cannot test this behavior as it won't be necessary
|
||||
// anymore. It will close all the idle connections before they reach this
|
||||
// stage.
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': '80'
|
||||
}
|
||||
res.writeHead(503, headers)
|
||||
res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
|
||||
childLogger.info({ res: { statusCode: 503 } }, 'request aborted - refusing to accept new requests as server is closing')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// When server.forceCloseConnections is true, we will collect any requests
|
||||
// that have indicated they want persistence so that they can be reaped
|
||||
// on server close. Otherwise, the container is a noop container.
|
||||
const connHeader = String.prototype.toLowerCase.call(req.headers.connection || '')
|
||||
if (connHeader === 'keep-alive') {
|
||||
if (keepAliveConnections.has(req.socket) === false) {
|
||||
keepAliveConnections.add(req.socket)
|
||||
req.socket.on('close', removeTrackedSocket.bind({ keepAliveConnections, socket: req.socket }))
|
||||
}
|
||||
}
|
||||
|
||||
// we revert the changes in defaultRoute
|
||||
if (req.headers[kRequestAcceptVersion] !== undefined) {
|
||||
req.headers['accept-version'] = req.headers[kRequestAcceptVersion]
|
||||
req.headers[kRequestAcceptVersion] = undefined
|
||||
}
|
||||
|
||||
const request = new context.Request(id, params, req, query, childLogger, context)
|
||||
const reply = new context.Reply(res, request, childLogger)
|
||||
if (disableRequestLogging === false) {
|
||||
childLogger.info({ req: request }, 'incoming request')
|
||||
}
|
||||
|
||||
if (hasLogger === true || context.onResponse !== null) {
|
||||
setupResponseListeners(reply)
|
||||
}
|
||||
|
||||
if (context.onRequest !== null) {
|
||||
onRequestHookRunner(
|
||||
context.onRequest,
|
||||
request,
|
||||
reply,
|
||||
runPreParsing
|
||||
)
|
||||
} else {
|
||||
runPreParsing(null, request, reply)
|
||||
}
|
||||
|
||||
if (context.onRequestAbort !== null) {
|
||||
req.on('close', () => {
|
||||
/* istanbul ignore else */
|
||||
if (req.aborted) {
|
||||
onRequestAbortHookRunner(
|
||||
context.onRequestAbort,
|
||||
request,
|
||||
handleOnRequestAbortHooksErrors.bind(null, reply)
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (context.onTimeout !== null) {
|
||||
if (!request.raw.socket._meta) {
|
||||
request.raw.socket.on('timeout', handleTimeout)
|
||||
}
|
||||
request.raw.socket._meta = { context, request, reply }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleOnRequestAbortHooksErrors (reply, err) {
|
||||
if (err) {
|
||||
reply.log.error({ err }, 'onRequestAborted hook failed')
|
||||
}
|
||||
}
|
||||
|
||||
function handleTimeout () {
|
||||
const { context, request, reply } = this._meta
|
||||
onTimeoutHookRunner(
|
||||
context.onTimeout,
|
||||
request,
|
||||
reply,
|
||||
noop
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeAndValidateMethod (method) {
|
||||
if (typeof method !== 'string') {
|
||||
throw new FST_ERR_ROUTE_METHOD_INVALID()
|
||||
}
|
||||
method = method.toUpperCase()
|
||||
if (!this[kSupportedHTTPMethods].bodyless.has(method) &&
|
||||
!this[kSupportedHTTPMethods].bodywith.has(method)) {
|
||||
throw new FST_ERR_ROUTE_METHOD_NOT_SUPPORTED(method)
|
||||
}
|
||||
|
||||
return method
|
||||
}
|
||||
|
||||
function validateSchemaBodyOption (method, path, schema) {
|
||||
if (this[kSupportedHTTPMethods].bodyless.has(method) && schema?.body) {
|
||||
throw new FST_ERR_ROUTE_BODY_VALIDATION_SCHEMA_NOT_SUPPORTED(method, path)
|
||||
}
|
||||
}
|
||||
|
||||
function validateBodyLimitOption (bodyLimit) {
|
||||
if (bodyLimit === undefined) return
|
||||
if (!Number.isInteger(bodyLimit) || bodyLimit <= 0) {
|
||||
throw new FST_ERR_ROUTE_BODY_LIMIT_OPTION_NOT_INT(bodyLimit)
|
||||
}
|
||||
}
|
||||
|
||||
function runPreParsing (err, request, reply) {
|
||||
if (reply.sent === true) return
|
||||
if (err != null) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
return
|
||||
}
|
||||
|
||||
request[kRequestPayloadStream] = request.raw
|
||||
|
||||
if (request[kRouteContext].preParsing !== null) {
|
||||
preParsingHookRunner(request[kRouteContext].preParsing, request, reply, handleRequest.bind(request.server))
|
||||
} else {
|
||||
handleRequest.call(request.server, null, request, reply)
|
||||
}
|
||||
}
|
||||
|
||||
function buildRouterOptions (options, defaultOptions) {
|
||||
const routerOptions = options.routerOptions || Object.create(null)
|
||||
|
||||
const usedDeprecatedOptions = routerKeys.filter(key => Object.hasOwn(options, key))
|
||||
|
||||
if (usedDeprecatedOptions.length > 0) {
|
||||
FSTDEP022(usedDeprecatedOptions.join(', '))
|
||||
}
|
||||
|
||||
for (const key of routerKeys) {
|
||||
if (!Object.hasOwn(routerOptions, key)) {
|
||||
routerOptions[key] = options[key] ?? defaultOptions[key]
|
||||
}
|
||||
}
|
||||
|
||||
return routerOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Used within the route handler as a `net.Socket.close` event handler.
|
||||
* The purpose is to remove a socket from the tracked sockets collection when
|
||||
* the socket has naturally timed out.
|
||||
*/
|
||||
function removeTrackedSocket () {
|
||||
this.keepAliveConnections.delete(this.socket)
|
||||
}
|
||||
|
||||
function noop () { }
|
||||
|
||||
module.exports = { buildRouting, validateBodyLimitOption, buildRouterOptions }
|
||||
164
node_modules/fastify/lib/schema-controller.js
generated
vendored
Normal file
164
node_modules/fastify/lib/schema-controller.js
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
'use strict'
|
||||
|
||||
const { buildSchemas } = require('./schemas')
|
||||
const SerializerSelector = require('@fastify/fast-json-stringify-compiler')
|
||||
const ValidatorSelector = require('@fastify/ajv-compiler')
|
||||
|
||||
/**
|
||||
* Called at every fastify context that is being created.
|
||||
* @param {object} parentSchemaCtrl: the SchemaController instance of the Fastify parent context
|
||||
* @param {object} opts: the `schemaController` server option. It can be undefined when a parentSchemaCtrl is set
|
||||
* @return {object}:a new SchemaController
|
||||
*/
|
||||
function buildSchemaController (parentSchemaCtrl, opts) {
|
||||
if (parentSchemaCtrl) {
|
||||
return new SchemaController(parentSchemaCtrl, opts)
|
||||
}
|
||||
|
||||
const compilersFactory = Object.assign({
|
||||
buildValidator: null,
|
||||
buildSerializer: null
|
||||
}, opts?.compilersFactory)
|
||||
|
||||
if (!compilersFactory.buildValidator) {
|
||||
compilersFactory.buildValidator = ValidatorSelector()
|
||||
}
|
||||
if (!compilersFactory.buildSerializer) {
|
||||
compilersFactory.buildSerializer = SerializerSelector()
|
||||
}
|
||||
|
||||
const option = {
|
||||
bucket: (opts && opts.bucket) || buildSchemas,
|
||||
compilersFactory,
|
||||
isCustomValidatorCompiler: typeof opts?.compilersFactory?.buildValidator === 'function',
|
||||
isCustomSerializerCompiler: typeof opts?.compilersFactory?.buildValidator === 'function'
|
||||
}
|
||||
|
||||
return new SchemaController(undefined, option)
|
||||
}
|
||||
|
||||
class SchemaController {
|
||||
constructor (parent, options) {
|
||||
this.opts = options || parent?.opts
|
||||
this.addedSchemas = false
|
||||
|
||||
this.compilersFactory = this.opts.compilersFactory
|
||||
|
||||
if (parent) {
|
||||
this.schemaBucket = this.opts.bucket(parent.getSchemas())
|
||||
this.validatorCompiler = parent.getValidatorCompiler()
|
||||
this.serializerCompiler = parent.getSerializerCompiler()
|
||||
this.isCustomValidatorCompiler = parent.isCustomValidatorCompiler
|
||||
this.isCustomSerializerCompiler = parent.isCustomSerializerCompiler
|
||||
this.parent = parent
|
||||
} else {
|
||||
this.schemaBucket = this.opts.bucket()
|
||||
this.isCustomValidatorCompiler = this.opts.isCustomValidatorCompiler || false
|
||||
this.isCustomSerializerCompiler = this.opts.isCustomSerializerCompiler || false
|
||||
}
|
||||
}
|
||||
|
||||
// Bucket interface
|
||||
add (schema) {
|
||||
this.addedSchemas = true
|
||||
return this.schemaBucket.add(schema)
|
||||
}
|
||||
|
||||
getSchema (schemaId) {
|
||||
return this.schemaBucket.getSchema(schemaId)
|
||||
}
|
||||
|
||||
getSchemas () {
|
||||
return this.schemaBucket.getSchemas()
|
||||
}
|
||||
|
||||
setValidatorCompiler (validatorCompiler) {
|
||||
// Set up as if the fixed validator compiler had been provided
|
||||
// by a custom 'options.compilersFactory.buildValidator' that
|
||||
// always returns the same compiler object. This is required because:
|
||||
//
|
||||
// - setValidatorCompiler must immediately install a compiler to preserve
|
||||
// legacy behavior
|
||||
// - setupValidator will recreate compilers from builders in some
|
||||
// circumstances, so we have to install this adapter to make it
|
||||
// behave the same if the legacy API is used
|
||||
//
|
||||
// The cloning of the compilersFactory object is necessary because
|
||||
// we are aliasing the parent compilersFactory if none was provided
|
||||
// to us (see constructor.)
|
||||
this.compilersFactory = Object.assign(
|
||||
{},
|
||||
this.compilersFactory,
|
||||
{ buildValidator: () => validatorCompiler })
|
||||
this.validatorCompiler = validatorCompiler
|
||||
this.isCustomValidatorCompiler = true
|
||||
}
|
||||
|
||||
setSerializerCompiler (serializerCompiler) {
|
||||
// Set up as if the fixed serializer compiler had been provided
|
||||
// by a custom 'options.compilersFactory.buildSerializer' that
|
||||
// always returns the same compiler object. This is required because:
|
||||
//
|
||||
// - setSerializerCompiler must immediately install a compiler to preserve
|
||||
// legacy behavior
|
||||
// - setupSerializer will recreate compilers from builders in some
|
||||
// circumstances, so we have to install this adapter to make it
|
||||
// behave the same if the legacy API is used
|
||||
//
|
||||
// The cloning of the compilersFactory object is necessary because
|
||||
// we are aliasing the parent compilersFactory if none was provided
|
||||
// to us (see constructor.)
|
||||
this.compilersFactory = Object.assign(
|
||||
{},
|
||||
this.compilersFactory,
|
||||
{ buildSerializer: () => serializerCompiler })
|
||||
this.serializerCompiler = serializerCompiler
|
||||
this.isCustomSerializerCompiler = true
|
||||
}
|
||||
|
||||
getValidatorCompiler () {
|
||||
return this.validatorCompiler || (this.parent && this.parent.getValidatorCompiler())
|
||||
}
|
||||
|
||||
getSerializerCompiler () {
|
||||
return this.serializerCompiler || (this.parent && this.parent.getSerializerCompiler())
|
||||
}
|
||||
|
||||
getSerializerBuilder () {
|
||||
return this.compilersFactory.buildSerializer || (this.parent && this.parent.getSerializerBuilder())
|
||||
}
|
||||
|
||||
getValidatorBuilder () {
|
||||
return this.compilersFactory.buildValidator || (this.parent && this.parent.getValidatorBuilder())
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called when a validator must be setup.
|
||||
* Do not setup the compiler more than once
|
||||
* @param {object} serverOptions the fastify server options
|
||||
*/
|
||||
setupValidator (serverOptions) {
|
||||
const isReady = this.validatorCompiler !== undefined && !this.addedSchemas
|
||||
if (isReady) {
|
||||
return
|
||||
}
|
||||
this.validatorCompiler = this.getValidatorBuilder()(this.schemaBucket.getSchemas(), serverOptions.ajv)
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will be called when a serializer must be setup.
|
||||
* Do not setup the compiler more than once
|
||||
* @param {object} serverOptions the fastify server options
|
||||
*/
|
||||
setupSerializer (serverOptions) {
|
||||
const isReady = this.serializerCompiler !== undefined && !this.addedSchemas
|
||||
if (isReady) {
|
||||
return
|
||||
}
|
||||
|
||||
this.serializerCompiler = this.getSerializerBuilder()(this.schemaBucket.getSchemas(), serverOptions.serializerOpts)
|
||||
}
|
||||
}
|
||||
|
||||
SchemaController.buildSchemaController = buildSchemaController
|
||||
module.exports = SchemaController
|
||||
207
node_modules/fastify/lib/schemas.js
generated
vendored
Normal file
207
node_modules/fastify/lib/schemas.js
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
'use strict'
|
||||
|
||||
const fastClone = require('rfdc')({ circles: false, proto: true })
|
||||
const { kSchemaVisited, kSchemaResponse } = require('./symbols')
|
||||
const kFluentSchema = Symbol.for('fluent-schema-object')
|
||||
|
||||
const {
|
||||
FST_ERR_SCH_MISSING_ID,
|
||||
FST_ERR_SCH_ALREADY_PRESENT,
|
||||
FST_ERR_SCH_DUPLICATE,
|
||||
FST_ERR_SCH_CONTENT_MISSING_SCHEMA
|
||||
} = require('./errors')
|
||||
|
||||
const SCHEMAS_SOURCE = ['params', 'body', 'querystring', 'query', 'headers']
|
||||
|
||||
function Schemas (initStore) {
|
||||
this.store = initStore || {}
|
||||
}
|
||||
|
||||
Schemas.prototype.add = function (inputSchema) {
|
||||
const schema = fastClone((inputSchema.isFluentSchema || inputSchema.isFluentJSONSchema || inputSchema[kFluentSchema])
|
||||
? inputSchema.valueOf()
|
||||
: inputSchema
|
||||
)
|
||||
|
||||
// developers can add schemas without $id, but with $def instead
|
||||
const id = schema.$id
|
||||
if (!id) {
|
||||
throw new FST_ERR_SCH_MISSING_ID()
|
||||
}
|
||||
|
||||
if (this.store[id]) {
|
||||
throw new FST_ERR_SCH_ALREADY_PRESENT(id)
|
||||
}
|
||||
|
||||
this.store[id] = schema
|
||||
}
|
||||
|
||||
Schemas.prototype.getSchemas = function () {
|
||||
return Object.assign({}, this.store)
|
||||
}
|
||||
|
||||
Schemas.prototype.getSchema = function (schemaId) {
|
||||
return this.store[schemaId]
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a schema is a non-plain object.
|
||||
*
|
||||
* @param {*} schema the schema to check
|
||||
* @returns {boolean} true if schema has a custom prototype
|
||||
*/
|
||||
function isCustomSchemaPrototype (schema) {
|
||||
return typeof schema === 'object' && Object.getPrototypeOf(schema) !== Object.prototype
|
||||
}
|
||||
|
||||
function normalizeSchema (routeSchemas, serverOptions) {
|
||||
if (routeSchemas[kSchemaVisited]) {
|
||||
return routeSchemas
|
||||
}
|
||||
|
||||
// alias query to querystring schema
|
||||
if (routeSchemas.query) {
|
||||
// check if our schema has both querystring and query
|
||||
if (routeSchemas.querystring) {
|
||||
throw new FST_ERR_SCH_DUPLICATE('querystring')
|
||||
}
|
||||
routeSchemas.querystring = routeSchemas.query
|
||||
}
|
||||
|
||||
generateFluentSchema(routeSchemas)
|
||||
|
||||
for (const key of SCHEMAS_SOURCE) {
|
||||
const schema = routeSchemas[key]
|
||||
if (schema && !isCustomSchemaPrototype(schema)) {
|
||||
if (key === 'body' && schema.content) {
|
||||
const contentProperty = schema.content
|
||||
const keys = Object.keys(contentProperty)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const contentType = keys[i]
|
||||
const contentSchema = contentProperty[contentType].schema
|
||||
if (!contentSchema) {
|
||||
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (routeSchemas.response) {
|
||||
const httpCodes = Object.keys(routeSchemas.response)
|
||||
for (const code of httpCodes) {
|
||||
if (isCustomSchemaPrototype(routeSchemas.response[code])) {
|
||||
continue
|
||||
}
|
||||
|
||||
const contentProperty = routeSchemas.response[code].content
|
||||
|
||||
if (contentProperty) {
|
||||
const keys = Object.keys(contentProperty)
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const mediaName = keys[i]
|
||||
if (!contentProperty[mediaName].schema) {
|
||||
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(mediaName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
routeSchemas[kSchemaVisited] = true
|
||||
return routeSchemas
|
||||
}
|
||||
|
||||
function generateFluentSchema (schema) {
|
||||
for (const key of SCHEMAS_SOURCE) {
|
||||
if (schema[key] && (schema[key].isFluentSchema || schema[key][kFluentSchema])) {
|
||||
schema[key] = schema[key].valueOf()
|
||||
}
|
||||
}
|
||||
|
||||
if (schema.response) {
|
||||
const httpCodes = Object.keys(schema.response)
|
||||
for (const code of httpCodes) {
|
||||
if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) {
|
||||
schema.response[code] = schema.response[code].valueOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the right JSON schema compiled function in the request context
|
||||
* setup by the route configuration `schema.response`.
|
||||
* It will look for the exact match (eg 200) or generic (eg 2xx)
|
||||
*
|
||||
* @param {object} context the request context
|
||||
* @param {number} statusCode the http status code
|
||||
* @param {string} [contentType] the reply content type
|
||||
* @returns {function|false} the right JSON Schema function to serialize
|
||||
* the reply or false if it is not set
|
||||
*/
|
||||
function getSchemaSerializer (context, statusCode, contentType) {
|
||||
const responseSchemaDef = context[kSchemaResponse]
|
||||
if (!responseSchemaDef) {
|
||||
return false
|
||||
}
|
||||
if (responseSchemaDef[statusCode]) {
|
||||
if (responseSchemaDef[statusCode].constructor === Object && contentType) {
|
||||
const mediaName = contentType.split(';', 1)[0]
|
||||
if (responseSchemaDef[statusCode][mediaName]) {
|
||||
return responseSchemaDef[statusCode][mediaName]
|
||||
}
|
||||
|
||||
// fallback to match all media-type
|
||||
if (responseSchemaDef[statusCode]['*/*']) {
|
||||
return responseSchemaDef[statusCode]['*/*']
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return responseSchemaDef[statusCode]
|
||||
}
|
||||
const fallbackStatusCode = (statusCode + '')[0] + 'xx'
|
||||
if (responseSchemaDef[fallbackStatusCode]) {
|
||||
if (responseSchemaDef[fallbackStatusCode].constructor === Object && contentType) {
|
||||
const mediaName = contentType.split(';', 1)[0]
|
||||
if (responseSchemaDef[fallbackStatusCode][mediaName]) {
|
||||
return responseSchemaDef[fallbackStatusCode][mediaName]
|
||||
}
|
||||
|
||||
// fallback to match all media-type
|
||||
if (responseSchemaDef[fallbackStatusCode]['*/*']) {
|
||||
return responseSchemaDef[fallbackStatusCode]['*/*']
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return responseSchemaDef[fallbackStatusCode]
|
||||
}
|
||||
if (responseSchemaDef.default) {
|
||||
if (responseSchemaDef.default.constructor === Object && contentType) {
|
||||
const mediaName = contentType.split(';', 1)[0]
|
||||
if (responseSchemaDef.default[mediaName]) {
|
||||
return responseSchemaDef.default[mediaName]
|
||||
}
|
||||
|
||||
// fallback to match all media-type
|
||||
if (responseSchemaDef.default['*/*']) {
|
||||
return responseSchemaDef.default['*/*']
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return responseSchemaDef.default
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildSchemas (initStore) { return new Schemas(initStore) },
|
||||
getSchemaSerializer,
|
||||
normalizeSchema
|
||||
}
|
||||
355
node_modules/fastify/lib/server.js
generated
vendored
Normal file
355
node_modules/fastify/lib/server.js
generated
vendored
Normal file
@@ -0,0 +1,355 @@
|
||||
'use strict'
|
||||
|
||||
const http = require('node:http')
|
||||
const https = require('node:https')
|
||||
const http2 = require('node:http2')
|
||||
const dns = require('node:dns')
|
||||
const os = require('node:os')
|
||||
|
||||
const { kState, kOptions, kServerBindings } = require('./symbols')
|
||||
const { FSTWRN003 } = require('./warnings')
|
||||
const { onListenHookRunner } = require('./hooks')
|
||||
const {
|
||||
FST_ERR_REOPENED_CLOSE_SERVER,
|
||||
FST_ERR_REOPENED_SERVER,
|
||||
FST_ERR_LISTEN_OPTIONS_INVALID
|
||||
} = require('./errors')
|
||||
const PonyPromise = require('./promise')
|
||||
|
||||
module.exports.createServer = createServer
|
||||
|
||||
function defaultResolveServerListeningText (address) {
|
||||
return `Server listening at ${address}`
|
||||
}
|
||||
|
||||
function createServer (options, httpHandler) {
|
||||
const server = getServerInstance(options, httpHandler)
|
||||
|
||||
// `this` is the Fastify object
|
||||
function listen (
|
||||
listenOptions = { port: 0, host: 'localhost' },
|
||||
cb = undefined
|
||||
) {
|
||||
if (typeof cb === 'function') {
|
||||
if (cb.constructor.name === 'AsyncFunction') {
|
||||
FSTWRN003('listen method')
|
||||
}
|
||||
|
||||
listenOptions.cb = cb
|
||||
}
|
||||
if (listenOptions.signal) {
|
||||
if (typeof listenOptions.signal.on !== 'function' && typeof listenOptions.signal.addEventListener !== 'function') {
|
||||
throw new FST_ERR_LISTEN_OPTIONS_INVALID('Invalid options.signal')
|
||||
}
|
||||
|
||||
// copy the current signal state
|
||||
this[kState].aborted = listenOptions.signal.aborted
|
||||
|
||||
if (this[kState].aborted) {
|
||||
return this.close()
|
||||
} else {
|
||||
const onAborted = () => {
|
||||
this[kState].aborted = true
|
||||
this.close()
|
||||
}
|
||||
listenOptions.signal.addEventListener('abort', onAborted, { once: true })
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a path specified, don't default host to 'localhost' so we don't end up listening
|
||||
// on both path and host
|
||||
// See https://github.com/fastify/fastify/issues/4007
|
||||
let host
|
||||
if (listenOptions.path == null) {
|
||||
host = listenOptions.host ?? 'localhost'
|
||||
} else {
|
||||
host = listenOptions.host
|
||||
}
|
||||
if (!Object.hasOwn(listenOptions, 'host') ||
|
||||
listenOptions.host == null) {
|
||||
listenOptions.host = host
|
||||
}
|
||||
if (host === 'localhost') {
|
||||
listenOptions.cb = (err, address) => {
|
||||
if (err) {
|
||||
// the server did not start
|
||||
cb(err, address)
|
||||
return
|
||||
}
|
||||
|
||||
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
|
||||
this[kState].listening = true
|
||||
cb(null, address)
|
||||
onListenHookRunner(this)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
listenOptions.cb = (err, address) => {
|
||||
// the server did not start
|
||||
if (err) {
|
||||
cb(err, address)
|
||||
return
|
||||
}
|
||||
this[kState].listening = true
|
||||
cb(null, address)
|
||||
onListenHookRunner(this)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/node/issues/9390
|
||||
// If listening to 'localhost', listen to both 127.0.0.1 or ::1 if they are available.
|
||||
// If listening to 127.0.0.1, only listen to 127.0.0.1.
|
||||
// If listening to ::1, only listen to ::1.
|
||||
|
||||
if (cb === undefined) {
|
||||
const listening = listenPromise.call(this, server, listenOptions)
|
||||
return listening.then(address => {
|
||||
const { promise, resolve } = PonyPromise.withResolvers()
|
||||
if (host === 'localhost') {
|
||||
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
|
||||
this[kState].listening = true
|
||||
resolve(address)
|
||||
onListenHookRunner(this)
|
||||
})
|
||||
} else {
|
||||
resolve(address)
|
||||
onListenHookRunner(this)
|
||||
}
|
||||
return promise
|
||||
})
|
||||
}
|
||||
|
||||
this.ready(listenCallback.call(this, server, listenOptions))
|
||||
}
|
||||
|
||||
return { server, listen }
|
||||
}
|
||||
|
||||
function multipleBindings (mainServer, httpHandler, serverOpts, listenOptions, onListen) {
|
||||
// the main server is started, we need to start the secondary servers
|
||||
this[kState].listening = false
|
||||
|
||||
// let's check if we need to bind additional addresses
|
||||
dns.lookup(listenOptions.host, { all: true }, (dnsErr, addresses) => {
|
||||
if (dnsErr || this[kState].aborted) {
|
||||
// not blocking the main server listening
|
||||
// this.log.warn('dns.lookup error:', dnsErr)
|
||||
onListen()
|
||||
return
|
||||
}
|
||||
|
||||
const isMainServerListening = mainServer.listening && serverOpts.serverFactory
|
||||
|
||||
let binding = 0
|
||||
let bound = 0
|
||||
if (!isMainServerListening) {
|
||||
const primaryAddress = mainServer.address()
|
||||
for (const adr of addresses) {
|
||||
if (adr.address !== primaryAddress.address) {
|
||||
binding++
|
||||
const secondaryOpts = Object.assign({}, listenOptions, {
|
||||
host: adr.address,
|
||||
port: primaryAddress.port,
|
||||
cb: (_ignoreErr) => {
|
||||
bound++
|
||||
|
||||
if (!_ignoreErr) {
|
||||
this[kServerBindings].push(secondaryServer)
|
||||
}
|
||||
|
||||
if (bound === binding) {
|
||||
// regardless of the error, we are done
|
||||
onListen()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const secondaryServer = getServerInstance(serverOpts, httpHandler)
|
||||
const closeSecondary = () => {
|
||||
// To avoid falling into situations where the close of the
|
||||
// secondary server is triggered before the preClose hook
|
||||
// is done running, we better wait until the main server is closed.
|
||||
// No new TCP connections are accepted
|
||||
// We swallow any error from the secondary server
|
||||
secondaryServer.close(() => {})
|
||||
if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections === true) {
|
||||
secondaryServer.closeAllConnections()
|
||||
}
|
||||
}
|
||||
|
||||
secondaryServer.on('upgrade', mainServer.emit.bind(mainServer, 'upgrade'))
|
||||
mainServer.on('unref', closeSecondary)
|
||||
mainServer.on('close', closeSecondary)
|
||||
mainServer.on('error', closeSecondary)
|
||||
this[kState].listening = false
|
||||
listenCallback.call(this, secondaryServer, secondaryOpts)()
|
||||
}
|
||||
}
|
||||
}
|
||||
// no extra bindings are necessary
|
||||
if (binding === 0) {
|
||||
onListen()
|
||||
return
|
||||
}
|
||||
|
||||
// in test files we are using unref so we need to propagate the unref event
|
||||
// to the secondary servers. It is valid only when the user is
|
||||
// listening on localhost
|
||||
const originUnref = mainServer.unref
|
||||
mainServer.unref = function () {
|
||||
originUnref.call(mainServer)
|
||||
mainServer.emit('unref')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function listenCallback (server, listenOptions) {
|
||||
const wrap = (err) => {
|
||||
server.removeListener('error', wrap)
|
||||
server.removeListener('listening', wrap)
|
||||
if (!err) {
|
||||
const address = logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText)
|
||||
listenOptions.cb(null, address)
|
||||
} else {
|
||||
this[kState].listening = false
|
||||
listenOptions.cb(err, null)
|
||||
}
|
||||
}
|
||||
|
||||
return (err) => {
|
||||
if (err != null) return listenOptions.cb(err)
|
||||
|
||||
if (this[kState].listening && this[kState].closing) {
|
||||
return listenOptions.cb(new FST_ERR_REOPENED_CLOSE_SERVER(), null)
|
||||
}
|
||||
if (this[kState].listening) {
|
||||
return listenOptions.cb(new FST_ERR_REOPENED_SERVER(), null)
|
||||
}
|
||||
|
||||
server.once('error', wrap)
|
||||
if (!this[kState].closing) {
|
||||
server.once('listening', wrap)
|
||||
server.listen(listenOptions)
|
||||
this[kState].listening = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function listenPromise (server, listenOptions) {
|
||||
if (this[kState].listening && this[kState].closing) {
|
||||
return Promise.reject(new FST_ERR_REOPENED_CLOSE_SERVER())
|
||||
}
|
||||
if (this[kState].listening) {
|
||||
return Promise.reject(new FST_ERR_REOPENED_SERVER())
|
||||
}
|
||||
|
||||
return this.ready().then(() => {
|
||||
// skip listen when aborted during ready
|
||||
if (this[kState].aborted) return
|
||||
|
||||
const { promise, resolve, reject } = PonyPromise.withResolvers()
|
||||
|
||||
const errEventHandler = (err) => {
|
||||
cleanup()
|
||||
this[kState].listening = false
|
||||
reject(err)
|
||||
}
|
||||
const listeningEventHandler = () => {
|
||||
cleanup()
|
||||
this[kState].listening = true
|
||||
resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText))
|
||||
}
|
||||
function cleanup () {
|
||||
server.removeListener('error', errEventHandler)
|
||||
server.removeListener('listening', listeningEventHandler)
|
||||
}
|
||||
server.once('error', errEventHandler)
|
||||
server.once('listening', listeningEventHandler)
|
||||
|
||||
server.listen(listenOptions)
|
||||
|
||||
return promise
|
||||
})
|
||||
}
|
||||
|
||||
function getServerInstance (options, httpHandler) {
|
||||
if (options.serverFactory) {
|
||||
// User provided server instance
|
||||
return options.serverFactory(httpHandler, options)
|
||||
}
|
||||
|
||||
// We have accepted true as a valid way to init https but node requires an options obj
|
||||
const httpsOptions = options.https === true ? {} : options.https
|
||||
|
||||
if (options.http2) {
|
||||
const server = typeof httpsOptions === 'object' ? http2.createSecureServer(httpsOptions, httpHandler) : http2.createServer(options.http, httpHandler)
|
||||
server.on('session', (session) => session.setTimeout(options.http2SessionTimeout, function closeSession () {
|
||||
this.close()
|
||||
}))
|
||||
|
||||
server.setTimeout(options.connectionTimeout)
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
// HTTP1 server instance
|
||||
const server = httpsOptions ? https.createServer(httpsOptions, httpHandler) : http.createServer(options.http, httpHandler)
|
||||
server.keepAliveTimeout = options.keepAliveTimeout
|
||||
server.requestTimeout = options.requestTimeout
|
||||
server.setTimeout(options.connectionTimeout)
|
||||
// We treat zero as null(node default) so we do not pass zero to the server instance
|
||||
if (options.maxRequestsPerSocket > 0) {
|
||||
server.maxRequestsPerSocket = options.maxRequestsPerSocket
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects the provided `server.address` object and returns a
|
||||
* normalized list of IP address strings. Normalization in this
|
||||
* case refers to mapping wildcard `0.0.0.0` to the list of IP
|
||||
* addresses the wildcard refers to.
|
||||
*
|
||||
* @see https://nodejs.org/docs/latest/api/net.html#serveraddress
|
||||
*
|
||||
* @param {object} A server address object as described in the
|
||||
* linked docs.
|
||||
*
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getAddresses (address) {
|
||||
if (address.address === '0.0.0.0') {
|
||||
return Object.values(os.networkInterfaces()).flatMap((iface) => {
|
||||
return iface.filter((iface) => iface.family === 'IPv4')
|
||||
}).sort((iface) => {
|
||||
/* c8 ignore next 2 */
|
||||
// Order the interfaces so that internal ones come first
|
||||
return iface.internal ? -1 : 1
|
||||
}).map((iface) => { return iface.address })
|
||||
}
|
||||
return [address.address]
|
||||
}
|
||||
|
||||
function logServerAddress (server, listenTextResolver) {
|
||||
let addresses
|
||||
const isUnixSocket = typeof server.address() === 'string'
|
||||
if (!isUnixSocket) {
|
||||
if (server.address().address.indexOf(':') === -1) {
|
||||
// IPv4
|
||||
addresses = getAddresses(server.address()).map((address) => address + ':' + server.address().port)
|
||||
} else {
|
||||
// IPv6
|
||||
addresses = ['[' + server.address().address + ']:' + server.address().port]
|
||||
}
|
||||
|
||||
addresses = addresses.map((address) => ('http' + (this[kOptions].https ? 's' : '') + '://') + address)
|
||||
} else {
|
||||
addresses = [server.address()]
|
||||
}
|
||||
|
||||
for (const address of addresses) {
|
||||
this.log.info(listenTextResolver(address))
|
||||
}
|
||||
return addresses[0]
|
||||
}
|
||||
66
node_modules/fastify/lib/symbols.js
generated
vendored
Normal file
66
node_modules/fastify/lib/symbols.js
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
'use strict'
|
||||
|
||||
const keys = {
|
||||
kAvvioBoot: Symbol('fastify.avvioBoot'),
|
||||
kChildren: Symbol('fastify.children'),
|
||||
kServerBindings: Symbol('fastify.serverBindings'),
|
||||
kBodyLimit: Symbol('fastify.bodyLimit'),
|
||||
kSupportedHTTPMethods: Symbol('fastify.acceptedHTTPMethods'),
|
||||
kRoutePrefix: Symbol('fastify.routePrefix'),
|
||||
kLogLevel: Symbol('fastify.logLevel'),
|
||||
kLogSerializers: Symbol('fastify.logSerializers'),
|
||||
kHooks: Symbol('fastify.hooks'),
|
||||
kContentTypeParser: Symbol('fastify.contentTypeParser'),
|
||||
kState: Symbol('fastify.state'),
|
||||
kOptions: Symbol('fastify.options'),
|
||||
kDisableRequestLogging: Symbol('fastify.disableRequestLogging'),
|
||||
kPluginNameChain: Symbol('fastify.pluginNameChain'),
|
||||
kRouteContext: Symbol('fastify.context'),
|
||||
kGenReqId: Symbol('fastify.genReqId'),
|
||||
// Schema
|
||||
kSchemaController: Symbol('fastify.schemaController'),
|
||||
kSchemaHeaders: Symbol('headers-schema'),
|
||||
kSchemaParams: Symbol('params-schema'),
|
||||
kSchemaQuerystring: Symbol('querystring-schema'),
|
||||
kSchemaBody: Symbol('body-schema'),
|
||||
kSchemaResponse: Symbol('response-schema'),
|
||||
kSchemaErrorFormatter: Symbol('fastify.schemaErrorFormatter'),
|
||||
kSchemaVisited: Symbol('fastify.schemas.visited'),
|
||||
// Request
|
||||
kRequest: Symbol('fastify.Request'),
|
||||
kRequestPayloadStream: Symbol('fastify.RequestPayloadStream'),
|
||||
kRequestAcceptVersion: Symbol('fastify.RequestAcceptVersion'),
|
||||
kRequestCacheValidateFns: Symbol('fastify.request.cache.validateFns'),
|
||||
kRequestOriginalUrl: Symbol('fastify.request.originalUrl'),
|
||||
// 404
|
||||
kFourOhFour: Symbol('fastify.404'),
|
||||
kCanSetNotFoundHandler: Symbol('fastify.canSetNotFoundHandler'),
|
||||
kFourOhFourLevelInstance: Symbol('fastify.404LogLevelInstance'),
|
||||
kFourOhFourContext: Symbol('fastify.404ContextKey'),
|
||||
kDefaultJsonParse: Symbol('fastify.defaultJSONParse'),
|
||||
// Reply
|
||||
kReply: Symbol('fastify.Reply'),
|
||||
kReplySerializer: Symbol('fastify.reply.serializer'),
|
||||
kReplyIsError: Symbol('fastify.reply.isError'),
|
||||
kReplyHeaders: Symbol('fastify.reply.headers'),
|
||||
kReplyTrailers: Symbol('fastify.reply.trailers'),
|
||||
kReplyHasStatusCode: Symbol('fastify.reply.hasStatusCode'),
|
||||
kReplyHijacked: Symbol('fastify.reply.hijacked'),
|
||||
kReplyStartTime: Symbol('fastify.reply.startTime'),
|
||||
kReplyNextErrorHandler: Symbol('fastify.reply.nextErrorHandler'),
|
||||
kReplyEndTime: Symbol('fastify.reply.endTime'),
|
||||
kReplyErrorHandlerCalled: Symbol('fastify.reply.errorHandlerCalled'),
|
||||
kReplyIsRunningOnErrorHook: Symbol('fastify.reply.isRunningOnErrorHook'),
|
||||
kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
|
||||
kReplyCacheSerializeFns: Symbol('fastify.reply.cache.serializeFns'),
|
||||
// This symbol is only meant to be used for fastify tests and should not be used for any other purpose
|
||||
kTestInternals: Symbol('fastify.testInternals'),
|
||||
kErrorHandler: Symbol('fastify.errorHandler'),
|
||||
kErrorHandlerAlreadySet: Symbol('fastify.errorHandlerAlreadySet'),
|
||||
kChildLoggerFactory: Symbol('fastify.childLoggerFactory'),
|
||||
kHasBeenDecorated: Symbol('fastify.hasBeenDecorated'),
|
||||
kKeepAliveConnections: Symbol('fastify.keepAliveConnections'),
|
||||
kRouteByFastify: Symbol('fastify.routeByFastify')
|
||||
}
|
||||
|
||||
module.exports = keys
|
||||
272
node_modules/fastify/lib/validation.js
generated
vendored
Normal file
272
node_modules/fastify/lib/validation.js
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kSchemaHeaders: headersSchema,
|
||||
kSchemaParams: paramsSchema,
|
||||
kSchemaQuerystring: querystringSchema,
|
||||
kSchemaBody: bodySchema,
|
||||
kSchemaResponse: responseSchema
|
||||
} = require('./symbols')
|
||||
const scChecker = /^[1-5](?:\d{2}|xx)$|^default$/
|
||||
|
||||
const {
|
||||
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX
|
||||
} = require('./errors')
|
||||
|
||||
const { FSTWRN001 } = require('./warnings')
|
||||
|
||||
function compileSchemasForSerialization (context, compile) {
|
||||
if (!context.schema || !context.schema.response) {
|
||||
return
|
||||
}
|
||||
const { method, url } = context.config || {}
|
||||
context[responseSchema] = Object.keys(context.schema.response)
|
||||
.reduce(function (acc, statusCode) {
|
||||
const schema = context.schema.response[statusCode]
|
||||
statusCode = statusCode.toLowerCase()
|
||||
if (!scChecker.test(statusCode)) {
|
||||
throw new FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX()
|
||||
}
|
||||
|
||||
if (schema.content) {
|
||||
const contentTypesSchemas = {}
|
||||
for (const mediaName of Object.keys(schema.content)) {
|
||||
const contentSchema = schema.content[mediaName].schema
|
||||
contentTypesSchemas[mediaName] = compile({
|
||||
schema: contentSchema,
|
||||
url,
|
||||
method,
|
||||
httpStatus: statusCode,
|
||||
contentType: mediaName
|
||||
})
|
||||
}
|
||||
acc[statusCode] = contentTypesSchemas
|
||||
} else {
|
||||
acc[statusCode] = compile({
|
||||
schema,
|
||||
url,
|
||||
method,
|
||||
httpStatus: statusCode
|
||||
})
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
function compileSchemasForValidation (context, compile, isCustom) {
|
||||
const { schema } = context
|
||||
if (!schema) {
|
||||
return
|
||||
}
|
||||
|
||||
const { method, url } = context.config || {}
|
||||
|
||||
const headers = schema.headers
|
||||
// the or part is used for backward compatibility
|
||||
if (headers && (isCustom || Object.getPrototypeOf(headers) !== Object.prototype)) {
|
||||
// do not mess with schema when custom validator applied, e.g. Joi, Typebox
|
||||
context[headersSchema] = compile({ schema: headers, method, url, httpPart: 'headers' })
|
||||
} else if (headers) {
|
||||
// The header keys are case insensitive
|
||||
// https://datatracker.ietf.org/doc/html/rfc2616#section-4.2
|
||||
const headersSchemaLowerCase = {}
|
||||
Object.keys(headers).forEach(k => { headersSchemaLowerCase[k] = headers[k] })
|
||||
if (headersSchemaLowerCase.required instanceof Array) {
|
||||
headersSchemaLowerCase.required = headersSchemaLowerCase.required.map(h => h.toLowerCase())
|
||||
}
|
||||
if (headers.properties) {
|
||||
headersSchemaLowerCase.properties = {}
|
||||
Object.keys(headers.properties).forEach(k => {
|
||||
headersSchemaLowerCase.properties[k.toLowerCase()] = headers.properties[k]
|
||||
})
|
||||
}
|
||||
context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' })
|
||||
} else if (Object.hasOwn(schema, 'headers')) {
|
||||
FSTWRN001('headers', method, url)
|
||||
}
|
||||
|
||||
if (schema.body) {
|
||||
const contentProperty = schema.body.content
|
||||
if (contentProperty) {
|
||||
const contentTypeSchemas = {}
|
||||
for (const contentType of Object.keys(contentProperty)) {
|
||||
const contentSchema = contentProperty[contentType].schema
|
||||
contentTypeSchemas[contentType] = compile({ schema: contentSchema, method, url, httpPart: 'body', contentType })
|
||||
}
|
||||
context[bodySchema] = contentTypeSchemas
|
||||
} else {
|
||||
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
|
||||
}
|
||||
} else if (Object.hasOwn(schema, 'body')) {
|
||||
FSTWRN001('body', method, url)
|
||||
}
|
||||
|
||||
if (schema.querystring) {
|
||||
context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' })
|
||||
} else if (Object.hasOwn(schema, 'querystring')) {
|
||||
FSTWRN001('querystring', method, url)
|
||||
}
|
||||
|
||||
if (schema.params) {
|
||||
context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' })
|
||||
} else if (Object.hasOwn(schema, 'params')) {
|
||||
FSTWRN001('params', method, url)
|
||||
}
|
||||
}
|
||||
|
||||
function validateParam (validatorFunction, request, paramName) {
|
||||
const isUndefined = request[paramName] === undefined
|
||||
const ret = validatorFunction && validatorFunction(isUndefined ? null : request[paramName])
|
||||
|
||||
if (ret && typeof ret.then === 'function') {
|
||||
return ret
|
||||
.then((res) => { return answer(res) })
|
||||
.catch(err => { return err }) // return as simple error (not throw)
|
||||
}
|
||||
|
||||
return answer(ret)
|
||||
|
||||
function answer (ret) {
|
||||
if (ret === false) return validatorFunction.errors
|
||||
if (ret && ret.error) return ret.error
|
||||
if (ret && ret.value) request[paramName] = ret.value
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function validate (context, request, execution) {
|
||||
const runExecution = execution === undefined
|
||||
|
||||
if (runExecution || !execution.skipParams) {
|
||||
const params = validateParam(context[paramsSchema], request, 'params')
|
||||
if (params) {
|
||||
if (typeof params.then !== 'function') {
|
||||
return wrapValidationError(params, 'params', context.schemaErrorFormatter)
|
||||
} else {
|
||||
return validateAsyncParams(params, context, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runExecution || !execution.skipBody) {
|
||||
let validatorFunction = null
|
||||
if (typeof context[bodySchema] === 'function') {
|
||||
validatorFunction = context[bodySchema]
|
||||
} else if (context[bodySchema]) {
|
||||
// TODO: add request.contentType and reuse it here
|
||||
const contentType = getEssenceMediaType(request.headers['content-type'])
|
||||
const contentSchema = context[bodySchema][contentType]
|
||||
if (contentSchema) {
|
||||
validatorFunction = contentSchema
|
||||
}
|
||||
}
|
||||
const body = validateParam(validatorFunction, request, 'body')
|
||||
if (body) {
|
||||
if (typeof body.then !== 'function') {
|
||||
return wrapValidationError(body, 'body', context.schemaErrorFormatter)
|
||||
} else {
|
||||
return validateAsyncBody(body, context, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runExecution || !execution.skipQuery) {
|
||||
const query = validateParam(context[querystringSchema], request, 'query')
|
||||
if (query) {
|
||||
if (typeof query.then !== 'function') {
|
||||
return wrapValidationError(query, 'querystring', context.schemaErrorFormatter)
|
||||
} else {
|
||||
return validateAsyncQuery(query, context, request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const headers = validateParam(context[headersSchema], request, 'headers')
|
||||
if (headers) {
|
||||
if (typeof headers.then !== 'function') {
|
||||
return wrapValidationError(headers, 'headers', context.schemaErrorFormatter)
|
||||
} else {
|
||||
return validateAsyncHeaders(headers, context, request)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function validateAsyncParams (validatePromise, context, request) {
|
||||
return validatePromise
|
||||
.then((paramsResult) => {
|
||||
if (paramsResult) {
|
||||
return wrapValidationError(paramsResult, 'params', context.schemaErrorFormatter)
|
||||
}
|
||||
|
||||
return validate(context, request, { skipParams: true })
|
||||
})
|
||||
}
|
||||
|
||||
function validateAsyncBody (validatePromise, context, request) {
|
||||
return validatePromise
|
||||
.then((bodyResult) => {
|
||||
if (bodyResult) {
|
||||
return wrapValidationError(bodyResult, 'body', context.schemaErrorFormatter)
|
||||
}
|
||||
|
||||
return validate(context, request, { skipParams: true, skipBody: true })
|
||||
})
|
||||
}
|
||||
|
||||
function validateAsyncQuery (validatePromise, context, request) {
|
||||
return validatePromise
|
||||
.then((queryResult) => {
|
||||
if (queryResult) {
|
||||
return wrapValidationError(queryResult, 'querystring', context.schemaErrorFormatter)
|
||||
}
|
||||
|
||||
return validate(context, request, { skipParams: true, skipBody: true, skipQuery: true })
|
||||
})
|
||||
}
|
||||
|
||||
function validateAsyncHeaders (validatePromise, context, request) {
|
||||
return validatePromise
|
||||
.then((headersResult) => {
|
||||
if (headersResult) {
|
||||
return wrapValidationError(headersResult, 'headers', context.schemaErrorFormatter)
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
function wrapValidationError (result, dataVar, schemaErrorFormatter) {
|
||||
if (result instanceof Error) {
|
||||
result.statusCode = result.statusCode || 400
|
||||
result.code = result.code || 'FST_ERR_VALIDATION'
|
||||
result.validationContext = result.validationContext || dataVar
|
||||
return result
|
||||
}
|
||||
|
||||
const error = schemaErrorFormatter(result, dataVar)
|
||||
error.statusCode = error.statusCode || 400
|
||||
error.code = error.code || 'FST_ERR_VALIDATION'
|
||||
error.validation = result
|
||||
error.validationContext = dataVar
|
||||
return error
|
||||
}
|
||||
|
||||
/**
|
||||
* simple function to retrieve the essence media type
|
||||
* @param {string} header
|
||||
* @returns {string} Mimetype string.
|
||||
*/
|
||||
function getEssenceMediaType (header) {
|
||||
if (!header) return ''
|
||||
return header.split(/[ ;]/, 1)[0].trim().toLowerCase()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
symbols: { bodySchema, querystringSchema, responseSchema, paramsSchema, headersSchema },
|
||||
compileSchemasForValidation,
|
||||
compileSchemasForSerialization,
|
||||
validate
|
||||
}
|
||||
57
node_modules/fastify/lib/warnings.js
generated
vendored
Normal file
57
node_modules/fastify/lib/warnings.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
'use strict'
|
||||
|
||||
const { createWarning } = require('process-warning')
|
||||
|
||||
/**
|
||||
* Deprecation codes:
|
||||
* - FSTWRN001
|
||||
* - FSTSEC001
|
||||
* - FSTDEP022
|
||||
*
|
||||
* Deprecation Codes FSTDEP001 - FSTDEP021 were used by v4 and MUST NOT not be reused.
|
||||
* - FSTDEP022 is used by v5 and MUST NOT be reused.
|
||||
* Warning Codes FSTWRN001 - FSTWRN002 were used by v4 and MUST NOT not be reused.
|
||||
*/
|
||||
|
||||
const FSTWRN001 = createWarning({
|
||||
name: 'FastifyWarning',
|
||||
code: 'FSTWRN001',
|
||||
message: 'The %s schema for %s: %s is missing. This may indicate the schema is not well specified.',
|
||||
unlimited: true
|
||||
})
|
||||
|
||||
const FSTWRN003 = createWarning({
|
||||
name: 'FastifyWarning',
|
||||
code: 'FSTWRN003',
|
||||
message: 'The %s mixes async and callback styles that may lead to unhandled rejections. Please use only one of them.',
|
||||
unlimited: true
|
||||
})
|
||||
|
||||
const FSTWRN004 = createWarning({
|
||||
name: 'FastifyWarning',
|
||||
code: 'FSTWRN004',
|
||||
message: 'It seems that you are overriding an errorHandler in the same scope, which can lead to subtle bugs.',
|
||||
unlimited: true
|
||||
})
|
||||
|
||||
const FSTSEC001 = createWarning({
|
||||
name: 'FastifySecurity',
|
||||
code: 'FSTSEC001',
|
||||
message: 'You are using /%s/ Content-Type which may be vulnerable to CORS attack. Please make sure your RegExp start with "^" or include ";?" to proper detection of the essence MIME type.',
|
||||
unlimited: true
|
||||
})
|
||||
|
||||
const FSTDEP022 = createWarning({
|
||||
name: 'FastifyWarning',
|
||||
code: 'FSTDPE022',
|
||||
message: 'The router options for %s property access is deprecated. Please use "options.routerOptions" instead for accessing router options. The router options will be removed in `fastify@6`.',
|
||||
unlimited: true
|
||||
})
|
||||
|
||||
module.exports = {
|
||||
FSTWRN001,
|
||||
FSTWRN003,
|
||||
FSTWRN004,
|
||||
FSTSEC001,
|
||||
FSTDEP022
|
||||
}
|
||||
81
node_modules/fastify/lib/wrapThenable.js
generated
vendored
Normal file
81
node_modules/fastify/lib/wrapThenable.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
'use strict'
|
||||
|
||||
const {
|
||||
kReplyIsError,
|
||||
kReplyHijacked
|
||||
} = require('./symbols')
|
||||
|
||||
const diagnostics = require('node:diagnostics_channel')
|
||||
const channels = diagnostics.tracingChannel('fastify.request.handler')
|
||||
|
||||
function wrapThenable (thenable, reply, store) {
|
||||
if (store) store.async = true
|
||||
thenable.then(function (payload) {
|
||||
if (reply[kReplyHijacked] === true) {
|
||||
return
|
||||
}
|
||||
|
||||
if (store) {
|
||||
channels.asyncStart.publish(store)
|
||||
}
|
||||
|
||||
try {
|
||||
// this is for async functions that are using reply.send directly
|
||||
//
|
||||
// since wrap-thenable will be called when using reply.send directly
|
||||
// without actual return. the response can be sent already or
|
||||
// the request may be terminated during the reply. in this situation,
|
||||
// it require an extra checking of request.aborted to see whether
|
||||
// the request is killed by client.
|
||||
if (payload !== undefined || //
|
||||
(reply.sent === false && //
|
||||
reply.raw.headersSent === false &&
|
||||
reply.request.raw.aborted === false &&
|
||||
reply.request.socket &&
|
||||
!reply.request.socket.destroyed
|
||||
)
|
||||
) {
|
||||
// we use a try-catch internally to avoid adding a catch to another
|
||||
// promise, increase promise perf by 10%
|
||||
try {
|
||||
reply.send(payload)
|
||||
} catch (err) {
|
||||
reply[kReplyIsError] = true
|
||||
reply.send(err)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (store) {
|
||||
channels.asyncEnd.publish(store)
|
||||
}
|
||||
}
|
||||
}, function (err) {
|
||||
if (store) {
|
||||
store.error = err
|
||||
channels.error.publish(store) // note that error happens before asyncStart
|
||||
channels.asyncStart.publish(store)
|
||||
}
|
||||
|
||||
try {
|
||||
if (reply.sent === true) {
|
||||
reply.log.error({ err }, 'Promise errored, but reply.sent = true was set')
|
||||
return
|
||||
}
|
||||
|
||||
reply[kReplyIsError] = true
|
||||
|
||||
reply.send(err)
|
||||
// The following should not happen
|
||||
/* c8 ignore next 3 */
|
||||
} catch (err) {
|
||||
// try-catch allow to re-throw error in error handler for async handler
|
||||
reply.send(err)
|
||||
} finally {
|
||||
if (store) {
|
||||
channels.asyncEnd.publish(store)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = wrapThenable
|
||||
Reference in New Issue
Block a user