Skip to content

Commit d4a9851

Browse files
authored
fix: Return 408 on client timeout. (fastify#4552)
* fix: Return 408 on client timeout. * fix: Renamed file. * fix: Fixed types. * fix: Updated response code handling. * fix: Added host header.
1 parent d14a10f commit d4a9851

File tree

6 files changed

+79
-12
lines changed

6 files changed

+79
-12
lines changed

docs/Reference/Server.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ options object which is used to customize the resulting instance. This document
99
describes the properties available in that options object.
1010

1111
- [Factory](#factory)
12+
- [`http`](#http)
1213
- [`http2`](#http2)
1314
- [`https`](#https)
1415
- [`connectionTimeout`](#connectiontimeout)
@@ -91,6 +92,18 @@ describes the properties available in that options object.
9192
- [errorHandler](#errorhandler)
9293
- [initialConfig](#initialconfig)
9394

95+
### `http`
96+
<a id="factory-http"></a>
97+
98+
An object used to configure the server's listening socket. The options
99+
are the same as the Node.js core [`createServer`
100+
method](https://nodejs.org/dist/latest-v14.x/docs/api/http.html#http_http_createserver_options_requestlistener).
101+
102+
This option is ignored if options [`http2`](#factory-http2) or
103+
[`https`](#factory-https) are set.
104+
105+
+ Default: `null`
106+
94107
### `http2`
95108
<a id="factory-http2"></a>
96109

@@ -494,7 +507,7 @@ request-id](./Logging.md#logging-request-id) section.
494507
Setting `requestIdHeader` to `false` will always use [genReqId](#genreqid)
495508

496509
+ Default: `'request-id'`
497-
510+
498511
```js
499512
const fastify = require('fastify')({
500513
requestIdHeader: 'x-custom-id', // -> use 'X-Custom-Id' header if available
@@ -1440,9 +1453,9 @@ plugins are registered. If you would like to augment the behavior of the default
14401453
arguments `fastify.setNotFoundHandler()` within the context of these registered
14411454
plugins.
14421455
1443-
> Note: Some config properties from the request object will be
1456+
> Note: Some config properties from the request object will be
14441457
> undefined inside the custom not found handler. E.g:
1445-
> `request.routerPath`, `routerMethod` and `context.config`.
1458+
> `request.routerPath`, `routerMethod` and `context.config`.
14461459
> This method design goal is to allow calling the common not found route.
14471460
> To return a per-route customized 404 response, you can do it in
14481461
> the response itself.

fastify.d.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ declare namespace fastify {
6161
https: https.ServerOptions | null
6262
}
6363

64+
export type FastifyHttpOptions<
65+
Server extends http.Server,
66+
Logger extends FastifyBaseLogger = FastifyBaseLogger
67+
> = FastifyServerOptions<Server, Logger> & {
68+
http?: http.ServerOptions | null
69+
}
70+
6471
type FindMyWayVersion<RawServer extends RawServerBase> = RawServer extends http.Server ? HTTPVersion.V1 : HTTPVersion.V2
6572

6673
export interface ConnectionError extends Error {
@@ -224,7 +231,7 @@ declare function fastify<
224231
Reply extends RawReplyDefaultExpression<Server> = RawReplyDefaultExpression<Server>,
225232
Logger extends FastifyBaseLogger = FastifyBaseLogger,
226233
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault,
227-
>(opts?: fastify.FastifyServerOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
234+
>(opts?: fastify.FastifyHttpOptions<Server, Logger>): FastifyInstance<Server, Request, Reply, Logger, TypeProvider> & PromiseLike<FastifyInstance<Server, Request, Reply, Logger, TypeProvider>>
228235

229236
// CJS export
230237
// const fastify = require('fastify')

fastify.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -630,22 +630,30 @@ function fastify (options) {
630630
return
631631
}
632632

633-
const body = JSON.stringify({
634-
error: http.STATUS_CODES['400'],
635-
message: 'Client Error',
636-
statusCode: 400
637-
})
633+
let body, errorCode, errorStatus, errorLabel
634+
635+
if (err.code === 'ERR_HTTP_REQUEST_TIMEOUT') {
636+
errorCode = '408'
637+
errorStatus = http.STATUS_CODES[errorCode]
638+
body = `{"error":"${errorStatus}","message":"Client Timeout","statusCode":408}`
639+
errorLabel = 'timeout'
640+
} else {
641+
errorCode = '400'
642+
errorStatus = http.STATUS_CODES[errorCode]
643+
body = `{"error":"${errorStatus}","message":"Client Error","statusCode":400}`
644+
errorLabel = 'error'
645+
}
638646

639647
// Most devs do not know what to do with this error.
640648
// In the vast majority of cases, it's a network error and/or some
641649
// config issue on the load balancer side.
642-
this.log.trace({ err }, 'client error')
650+
this.log.trace({ err }, `client ${errorLabel}`)
643651
// Copying standard node behaviour
644652
// https://github.com/nodejs/node/blob/6ca23d7846cb47e84fd344543e394e50938540be/lib/_http_server.js#L666
645653

646654
// If the socket is not writable, there is no reason to try to send data.
647655
if (socket.writable) {
648-
socket.write(`HTTP/1.1 400 Bad Request\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
656+
socket.write(`HTTP/1.1 ${errorCode} ${errorStatus}\r\nContent-Length: ${body.length}\r\nContent-Type: application/json\r\n\r\n${body}`)
649657
}
650658
socket.destroy(err)
651659
}

lib/server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ function getServerInstance (options, httpHandler) {
288288
if (options.https) {
289289
server = https.createServer(options.https, httpHandler)
290290
} else {
291-
server = http.createServer(httpHandler)
291+
server = http.createServer(options.http, httpHandler)
292292
}
293293
server.keepAliveTimeout = options.keepAliveTimeout
294294
server.requestTimeout = options.requestTimeout

test/client-timeout.test.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
'use strict'
2+
3+
const { test } = require('tap')
4+
const fastify = require('..')({ requestTimeout: 5, http: { connectionsCheckingInterval: 1000 } })
5+
const { connect } = require('net')
6+
7+
test('requestTimeout should return 408', t => {
8+
t.plan(1)
9+
10+
t.teardown(() => {
11+
fastify.close()
12+
})
13+
14+
fastify.post('/', async function (req, reply) {
15+
await new Promise(resolve => setTimeout(resolve, 100))
16+
return reply.send({ hello: 'world' })
17+
})
18+
19+
fastify.listen({ port: 0 }, err => {
20+
if (err) {
21+
throw err
22+
}
23+
24+
let data = Buffer.alloc(0)
25+
const socket = connect(fastify.server.address().port)
26+
27+
socket.write('POST / HTTP/1.1\r\nHost: example.com\r\nConnection-Length: 1\r\n')
28+
29+
socket.on('data', c => (data = Buffer.concat([data, c])))
30+
socket.on('end', () => {
31+
t.equal(
32+
data.toString('utf-8'),
33+
'HTTP/1.1 408 Request Timeout\r\nContent-Length: 71\r\nContent-Type: application/json\r\n\r\n{"error":"Request Timeout","message":"Client Timeout","statusCode":408}'
34+
)
35+
t.end()
36+
})
37+
})
38+
})

test/types/fastify.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Socket } from 'net'
2525
// http server
2626
expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse>>>(fastify())
2727
expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({}))
28+
expectType<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<http.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ http: {} }))
2829
// https server
2930
expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ https: {} }))
3031
expectType<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse> & PromiseLike<FastifyInstance<https.Server, http.IncomingMessage, http.ServerResponse>>>(fastify({ https: null }))

0 commit comments

Comments
 (0)