Skip to content

Commit

Permalink
fix: handle abort signal before server is ready (#4886)
Browse files Browse the repository at this point in the history
Co-authored-by: Carlos Fuentes <me@metcoder.dev>
Co-authored-by: shaypepper <shay.pe@moonactive.com>
Co-authored-by: Uzlopak <aras.abbasi@googlemail.com>
  • Loading branch information
4 people committed Jul 13, 2023
1 parent 53164c1 commit 1c50da3
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 9 deletions.
5 changes: 5 additions & 0 deletions docs/Reference/Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,8 @@ The decorator is not present in the instance.
<a id="FST_ERR_VALIDATION"></a>

The Request failed the payload validation.

#### FST_ERR_LISTEN_OPTIONS_INVALID
<a id="FST_ERR_LISTEN_OPTIONS_INVALID"></a>

Invalid listen options.
6 changes: 6 additions & 0 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ const codes = {
'%s',
400
),
FST_ERR_LISTEN_OPTIONS_INVALID: createError(
'FST_ERR_LISTEN_OPTIONS_INVALID',
"Invalid listen options: '%s'",
500,
TypeError
),

/**
* ContentTypeParser
Expand Down
24 changes: 20 additions & 4 deletions lib/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const { kState, kOptions, kServerBindings } = require('./symbols')
const {
FST_ERR_HTTP2_INVALID_VERSION,
FST_ERR_REOPENED_CLOSE_SERVER,
FST_ERR_REOPENED_SERVER
FST_ERR_REOPENED_SERVER,
FST_ERR_LISTEN_OPTIONS_INVALID
} = require('./errors')

module.exports.createServer = createServer
Expand Down Expand Up @@ -46,6 +47,20 @@ function createServer (options, httpHandler) {
} 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
Expand Down Expand Up @@ -215,9 +230,10 @@ function listenCallback (server, listenOptions) {
}

server.once('error', wrap)
server.listen(listenOptions, wrap)

this[kState].listening = true
if (!this[kState].closing) {
server.listen(listenOptions, wrap)
this[kState].listening = true
}
}
}

Expand Down
20 changes: 15 additions & 5 deletions test/internals/errors.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const errors = require('../../lib/errors')
const { readFileSync } = require('fs')
const { resolve } = require('path')

test('should expose 77 errors', t => {
test('should expose 78 errors', t => {
t.plan(1)
const exportedKeys = Object.keys(errors)
let counter = 0
Expand All @@ -14,11 +14,11 @@ test('should expose 77 errors', t => {
counter++
}
}
t.equal(counter, 77)
t.equal(counter, 78)
})

test('ensure name and codes of Errors are identical', t => {
t.plan(77)
t.plan(78)
const exportedKeys = Object.keys(errors)
for (const key of exportedKeys) {
if (errors[key].name === 'FastifyError') {
Expand Down Expand Up @@ -807,8 +807,18 @@ test('FST_ERR_VALIDATION', t => {
t.ok(error instanceof Error)
})

test('FST_ERR_LISTEN_OPTIONS_INVALID', t => {
t.plan(5)
const error = new errors.FST_ERR_LISTEN_OPTIONS_INVALID()
t.equal(error.name, 'FastifyError')
t.equal(error.code, 'FST_ERR_LISTEN_OPTIONS_INVALID')
t.equal(error.message, "Invalid listen options: '%s'")
t.equal(error.statusCode, 500)
t.ok(error instanceof TypeError)
})

test('Ensure that all errors are in Errors.md documented', t => {
t.plan(77)
t.plan(78)
const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')

const exportedKeys = Object.keys(errors)
Expand All @@ -820,7 +830,7 @@ test('Ensure that all errors are in Errors.md documented', t => {
})

test('Ensure that non-existing errors are not in Errors.md documented', t => {
t.plan(77)
t.plan(78)
const errorsMd = readFileSync(resolve(__dirname, '../../docs/Reference/Errors.md'), 'utf8')

const matchRE = /#### ([0-9a-zA-Z_]+)\n/g
Expand Down
54 changes: 54 additions & 0 deletions test/server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const t = require('tap')
const test = t.test
const Fastify = require('..')
const semver = require('semver')

test('listen should accept null port', t => {
t.plan(1)
Expand Down Expand Up @@ -69,3 +70,56 @@ test('listen should reject string port', async (t) => {
t.equal(error.code, 'ERR_SOCKET_BAD_PORT')
}
})

test('abort signal', { skip: semver.lt(process.version, '16.0.0') }, t => {
t.test('listen should not start server', t => {
t.plan(2)
function onClose (instance, done) {
t.type(fastify, instance)
done()
}
const controller = new AbortController()

const fastify = Fastify()
fastify.addHook('onClose', onClose)
fastify.listen({ port: 1234, signal: controller.signal }, (err) => {
t.error(err)
})
controller.abort()
t.equal(fastify.server.listening, false)
})

t.test('listen should not start server if already aborted', t => {
t.plan(2)
function onClose (instance, done) {
t.type(fastify, instance)
done()
}

const controller = new AbortController()
controller.abort()
const fastify = Fastify()
fastify.addHook('onClose', onClose)
fastify.listen({ port: 1234, signal: controller.signal }, (err) => {
t.error(err)
})
t.equal(fastify.server.listening, false)
})

t.test('listen should throw if received invalid signal', t => {
t.plan(2)
const fastify = Fastify()

try {
fastify.listen({ port: 1234, signal: {} }, (err) => {
t.error(err)
})
t.fail()
} catch (e) {
t.equal(e.code, 'FST_ERR_LISTEN_OPTIONS_INVALID')
t.equal(e.message, 'Invalid listen options: \'Invalid options.signal\'')
}
})

t.end()
})

0 comments on commit 1c50da3

Please sign in to comment.