Skip to content

Commit

Permalink
fix: default clientError replies on reused connection (#4101) (#4133)
Browse files Browse the repository at this point in the history
When fastify server receives request with invalid url
in a reused connection, it closes the connection
instead of 400 Bad Request reply.

The closed connection is then propagated by load balancer (ALB)
as 502 error. This turns client errors into closely monitored
server errors.

`socket.bytesWritten` is never going to be 0 on reused connection.

Co-authored-by: Branislav Katreniak <bkatreniak@slido.com>
  • Loading branch information
katreniak and Branislav Katreniak committed Jul 12, 2022
1 parent ed4f4c1 commit fe889ea
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 2 deletions.
2 changes: 1 addition & 1 deletion fastify.js
Expand Up @@ -621,7 +621,7 @@ function fastify (options) {
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666

// If the socket is not writable, there is no reason to try to send data.
if (socket.writable && socket.bytesWritten === 0) {
if (socket.writable) {
socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
}
socket.destroy(err)
Expand Down
45 changes: 44 additions & 1 deletion test/request-error.test.js
Expand Up @@ -150,7 +150,7 @@ test('default clientError handler ignores sockets in destroyed state', t => {
})

test('default clientError handler destroys sockets in writable state', t => {
t.plan(1)
t.plan(2)

const fastify = Fastify({
bodyLimit: 1,
Expand All @@ -166,6 +166,9 @@ test('default clientError handler destroys sockets in writable state', t => {
},
destroy () {
t.pass('destroy should be called')
},
write (response) {
t.match(response, /^HTTP\/1.1 400 Bad Request/)
}
})
})
Expand All @@ -186,6 +189,9 @@ test('default clientError handler destroys http sockets in non-writable state',
},
destroy () {
t.pass('destroy should be called')
},
write (response) {
t.fail('write should not be called')
}
})
})
Expand Down Expand Up @@ -270,3 +276,40 @@ test('encapsulated error handler binding', t => {
t.equal(fastify.hello, undefined)
})
})

test('default clientError replies with bad request on reused keep-alive connection', t => {
t.plan(2)

let response = ''

const fastify = Fastify({
bodyLimit: 1,
keepAliveTimeout: 100
})

fastify.get('/', (request, reply) => {
reply.send('OK\n')
})

fastify.listen({ port: 0 }, function (err) {
t.error(err)
fastify.server.unref()

const client = connect(fastify.server.address().port)

client.on('data', chunk => {
response += chunk.toString('utf-8')
})

client.on('end', () => {
t.match(response, /^HTTP\/1.1 200 OK.*HTTP\/1.1 400 Bad Request/s)
})

client.resume()
client.write('GET / HTTP/1.1\r\n')
client.write('\r\n\r\n')
client.write('GET /?a b HTTP/1.1\r\n')
client.write('Connection: close\r\n')
client.write('\r\n\r\n')
})
})

0 comments on commit fe889ea

Please sign in to comment.