Auto-commit 2026-04-29 16:31
This commit is contained in:
447
node_modules/fastify/lib/server.js
generated
vendored
Normal file
447
node_modules/fastify/lib/server.js
generated
vendored
Normal file
@@ -0,0 +1,447 @@
|
||||
'use strict'
|
||||
|
||||
const http = require('node:http')
|
||||
const https = require('node:https')
|
||||
const dns = require('node:dns')
|
||||
|
||||
const { FSTDEP011 } = require('./warnings')
|
||||
const { kState, kOptions, kServerBindings } = require('./symbols')
|
||||
const { onListenHookRunner } = require('./hooks')
|
||||
const {
|
||||
FST_ERR_HTTP2_INVALID_VERSION,
|
||||
FST_ERR_REOPENED_CLOSE_SERVER,
|
||||
FST_ERR_REOPENED_SERVER,
|
||||
FST_ERR_LISTEN_OPTIONS_INVALID
|
||||
} = require('./errors')
|
||||
|
||||
module.exports.createServer = createServer
|
||||
module.exports.compileValidateHTTPVersion = compileValidateHTTPVersion
|
||||
|
||||
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, ...args) {
|
||||
let cb = args.slice(-1).pop()
|
||||
// When the variadic signature deprecation is complete, the function
|
||||
// declaration should become:
|
||||
// function listen (listenOptions = { port: 0, host: 'localhost' }, cb = undefined)
|
||||
// Upon doing so, the `normalizeListenArgs` function is no longer needed,
|
||||
// and all of this preamble to feed it correctly also no longer needed.
|
||||
const firstArgType = Object.prototype.toString.call(arguments[0])
|
||||
if (arguments.length === 0) {
|
||||
listenOptions = normalizeListenArgs([])
|
||||
} else if (arguments.length > 0 && (firstArgType !== '[object Object]' && firstArgType !== '[object Function]')) {
|
||||
FSTDEP011()
|
||||
listenOptions = normalizeListenArgs(Array.from(arguments))
|
||||
cb = listenOptions.cb
|
||||
} else if (args.length > 1) {
|
||||
// `.listen(obj, a, ..., n, callback )`
|
||||
FSTDEP011()
|
||||
// Deal with `.listen(port, host, backlog, [cb])`
|
||||
const hostPath = listenOptions.path ? [listenOptions.path] : [listenOptions.port ?? 0, listenOptions.host ?? 'localhost']
|
||||
Object.assign(listenOptions, normalizeListenArgs([...hostPath, ...args]))
|
||||
} else {
|
||||
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')
|
||||
}
|
||||
|
||||
if (listenOptions.signal.aborted) {
|
||||
this.close()
|
||||
} else {
|
||||
const onAborted = () => {
|
||||
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.prototype.hasOwnProperty.call(listenOptions, 'host') === false ||
|
||||
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)
|
||||
/* istanbul ignore else */
|
||||
return listening.then(address => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (host === 'localhost') {
|
||||
multipleBindings.call(this, server, httpHandler, options, listenOptions, () => {
|
||||
this[kState].listening = true
|
||||
resolve(address)
|
||||
onListenHookRunner(this)
|
||||
})
|
||||
} else {
|
||||
resolve(address)
|
||||
onListenHookRunner(this)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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++
|
||||
|
||||
/* istanbul ignore next: the else won't be taken unless listening fails */
|
||||
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 fall 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 (serverOpts.forceCloseConnections === 'idle') {
|
||||
// Not needed in Node 19
|
||||
secondaryServer.closeIdleConnections()
|
||||
} else if (typeof secondaryServer.closeAllConnections === 'function' && serverOpts.forceCloseConnections) {
|
||||
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
|
||||
/* c8 ignore next 4 */
|
||||
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)
|
||||
} else 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())
|
||||
} else if (this[kState].listening) {
|
||||
return Promise.reject(new FST_ERR_REOPENED_SERVER())
|
||||
}
|
||||
|
||||
return this.ready().then(() => {
|
||||
let errEventHandler
|
||||
let listeningEventHandler
|
||||
function cleanup () {
|
||||
server.removeListener('error', errEventHandler)
|
||||
server.removeListener('listening', listeningEventHandler)
|
||||
}
|
||||
const errEvent = new Promise((resolve, reject) => {
|
||||
errEventHandler = (err) => {
|
||||
cleanup()
|
||||
this[kState].listening = false
|
||||
reject(err)
|
||||
}
|
||||
server.once('error', errEventHandler)
|
||||
})
|
||||
const listeningEvent = new Promise((resolve, reject) => {
|
||||
listeningEventHandler = () => {
|
||||
cleanup()
|
||||
this[kState].listening = true
|
||||
resolve(logServerAddress.call(this, server, listenOptions.listenTextResolver || defaultResolveServerListeningText))
|
||||
}
|
||||
server.once('listening', listeningEventHandler)
|
||||
})
|
||||
|
||||
server.listen(listenOptions)
|
||||
|
||||
return Promise.race([
|
||||
errEvent, // e.g invalid port range error is always emitted before the server listening
|
||||
listeningEvent
|
||||
])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a function that, based upon initial configuration, will
|
||||
* verify that every incoming request conforms to allowed
|
||||
* HTTP versions for the Fastify instance, e.g. a Fastify HTTP/1.1
|
||||
* server will not serve HTTP/2 requests upon the result of the
|
||||
* verification function.
|
||||
*
|
||||
* @param {object} options fastify option
|
||||
* @param {function} [options.serverFactory] If present, the
|
||||
* validator function will skip all checks.
|
||||
* @param {boolean} [options.http2 = false] If true, the validator
|
||||
* function will allow HTTP/2 requests.
|
||||
* @param {object} [options.https = null] https server options
|
||||
* @param {boolean} [options.https.allowHTTP1] If true and use
|
||||
* with options.http2 the validator function will allow HTTP/1
|
||||
* request to http2 server.
|
||||
*
|
||||
* @returns {function} HTTP version validator function.
|
||||
*/
|
||||
function compileValidateHTTPVersion (options) {
|
||||
let bypass = false
|
||||
// key-value map to store valid http version
|
||||
const map = new Map()
|
||||
if (options.serverFactory) {
|
||||
// When serverFactory is passed, we cannot identify how to check http version reliably
|
||||
// So, we should skip the http version check
|
||||
bypass = true
|
||||
}
|
||||
if (options.http2) {
|
||||
// HTTP2 must serve HTTP/2.0
|
||||
map.set('2.0', true)
|
||||
if (options.https && options.https.allowHTTP1 === true) {
|
||||
// HTTP2 with HTTPS.allowHTTP1 allow fallback to HTTP/1.1 and HTTP/1.0
|
||||
map.set('1.1', true)
|
||||
map.set('1.0', true)
|
||||
}
|
||||
} else {
|
||||
// HTTP must server HTTP/1.1 and HTTP/1.0
|
||||
map.set('1.1', true)
|
||||
map.set('1.0', true)
|
||||
}
|
||||
// The compiled function here placed in one of the hottest path inside fastify
|
||||
// the implementation here must be as performant as possible
|
||||
return function validateHTTPVersion (httpVersion) {
|
||||
// `bypass` skip the check when custom server factory provided
|
||||
// `httpVersion in obj` check for the valid http version we should support
|
||||
return bypass || map.has(httpVersion)
|
||||
}
|
||||
}
|
||||
|
||||
function getServerInstance (options, httpHandler) {
|
||||
let server = null
|
||||
// node@20 do not accepts options as boolean
|
||||
// we need to provide proper https option
|
||||
const httpsOptions = options.https === true ? {} : options.https
|
||||
if (options.serverFactory) {
|
||||
server = options.serverFactory(httpHandler, options)
|
||||
} else if (options.http2) {
|
||||
if (typeof httpsOptions === 'object') {
|
||||
server = http2().createSecureServer(httpsOptions, httpHandler)
|
||||
} else {
|
||||
server = http2().createServer(httpHandler)
|
||||
}
|
||||
server.on('session', sessionTimeout(options.http2SessionTimeout))
|
||||
} else {
|
||||
// this is http1
|
||||
if (httpsOptions) {
|
||||
server = https.createServer(httpsOptions, httpHandler)
|
||||
} else {
|
||||
server = http.createServer(options.http, httpHandler)
|
||||
}
|
||||
server.keepAliveTimeout = options.keepAliveTimeout
|
||||
server.requestTimeout = options.requestTimeout
|
||||
// we treat zero as null
|
||||
// and null is the default setting from nodejs
|
||||
// so we do not pass the option to server
|
||||
if (options.maxRequestsPerSocket > 0) {
|
||||
server.maxRequestsPerSocket = options.maxRequestsPerSocket
|
||||
}
|
||||
}
|
||||
|
||||
if (!options.serverFactory) {
|
||||
server.setTimeout(options.connectionTimeout)
|
||||
}
|
||||
return server
|
||||
}
|
||||
|
||||
function normalizeListenArgs (args) {
|
||||
if (args.length === 0) {
|
||||
return { port: 0, host: 'localhost' }
|
||||
}
|
||||
|
||||
const cb = typeof args[args.length - 1] === 'function' ? args.pop() : undefined
|
||||
const options = { cb }
|
||||
|
||||
const firstArg = args[0]
|
||||
const argsLength = args.length
|
||||
const lastArg = args[argsLength - 1]
|
||||
if (typeof firstArg === 'string' && isNaN(firstArg)) {
|
||||
/* Deal with listen (pipe[, backlog]) */
|
||||
options.path = firstArg
|
||||
options.backlog = argsLength > 1 ? lastArg : undefined
|
||||
} else {
|
||||
/* Deal with listen ([port[, host[, backlog]]]) */
|
||||
options.port = argsLength >= 1 && Number.isInteger(firstArg) ? firstArg : normalizePort(firstArg)
|
||||
// This will listen to what localhost is.
|
||||
// It can be 127.0.0.1 or ::1, depending on the operating system.
|
||||
// Fixes https://github.com/fastify/fastify/issues/1022.
|
||||
options.host = argsLength >= 2 && args[1] ? args[1] : 'localhost'
|
||||
options.backlog = argsLength >= 3 ? args[2] : undefined
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
function normalizePort (firstArg) {
|
||||
const port = Number(firstArg)
|
||||
return port >= 0 && !Number.isNaN(port) && Number.isInteger(port) ? port : 0
|
||||
}
|
||||
|
||||
function logServerAddress (server, listenTextResolver) {
|
||||
let address = server.address()
|
||||
const isUnixSocket = typeof address === 'string'
|
||||
/* istanbul ignore next */
|
||||
if (!isUnixSocket) {
|
||||
if (address.address.indexOf(':') === -1) {
|
||||
address = address.address + ':' + address.port
|
||||
} else {
|
||||
address = '[' + address.address + ']:' + address.port
|
||||
}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
address = (isUnixSocket ? '' : ('http' + (this[kOptions].https ? 's' : '') + '://')) + address
|
||||
|
||||
const serverListeningText = listenTextResolver(address)
|
||||
this.log.info(serverListeningText)
|
||||
return address
|
||||
}
|
||||
|
||||
function http2 () {
|
||||
try {
|
||||
return require('node:http2')
|
||||
} catch (err) {
|
||||
throw new FST_ERR_HTTP2_INVALID_VERSION()
|
||||
}
|
||||
}
|
||||
|
||||
function sessionTimeout (timeout) {
|
||||
return function (session) {
|
||||
session.setTimeout(timeout, close)
|
||||
}
|
||||
}
|
||||
|
||||
function close () {
|
||||
this.close()
|
||||
}
|
||||
Reference in New Issue
Block a user