fastify 2.4.1 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/SECURITY.md +33 -0
  3. package/build/build-validation.js +1 -1
  4. package/docs/Decorators.md +2 -2
  5. package/docs/Ecosystem.md +5 -1
  6. package/docs/Fluent-Schema.md +5 -7
  7. package/docs/Hooks.md +40 -40
  8. package/docs/Logging.md +15 -3
  9. package/docs/Plugins-Guide.md +21 -21
  10. package/docs/Plugins.md +11 -11
  11. package/docs/Reply.md +14 -7
  12. package/docs/Routes.md +6 -6
  13. package/docs/Server.md +40 -19
  14. package/docs/Serverless.md +127 -28
  15. package/docs/Validation-and-Serialization.md +15 -12
  16. package/fastify.d.ts +22 -14
  17. package/fastify.js +21 -0
  18. package/lib/context.js +5 -4
  19. package/lib/decorate.js +2 -0
  20. package/lib/errors.js +1 -0
  21. package/lib/handleRequest.js +2 -2
  22. package/lib/reply.js +23 -6
  23. package/lib/request.js +2 -2
  24. package/lib/route.js +24 -15
  25. package/lib/schemas.js +24 -3
  26. package/lib/symbols.js +1 -0
  27. package/lib/validation.js +19 -0
  28. package/package.json +19 -18
  29. package/test/async-await.js +1 -1
  30. package/test/close-pipelining.test.js +43 -2
  31. package/test/close.test.js +69 -12
  32. package/test/content-length.test.js +2 -2
  33. package/test/decorator.test.js +2 -0
  34. package/test/fluent-schema.js +54 -0
  35. package/test/hooks-async.js +80 -1
  36. package/test/hooks.test.js +4 -4
  37. package/test/http2/closing.js +86 -33
  38. package/test/input-validation.test.js +1 -1
  39. package/test/internals/decorator.test.js +2 -0
  40. package/test/internals/initialConfig.test.js +1 -1
  41. package/test/internals/reply.test.js +276 -1
  42. package/test/internals/validation.test.js +57 -0
  43. package/test/logger.test.js +33 -1
  44. package/test/nullable-validation.test.js +52 -0
  45. package/test/plugin.test.js +2 -0
  46. package/test/register.test.js +2 -0
  47. package/test/route-prefix.test.js +31 -0
  48. package/test/route.test.js +35 -0
  49. package/test/shared-schemas.test.js +3 -3
  50. package/test/types/index.ts +33 -2
  51. package/test/versioned-routes.test.js +3 -3
package/lib/decorate.js CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ /* eslint no-prototype-builtins: 0 */
4
+
3
5
  const {
4
6
  kReply,
5
7
  kRequest
package/lib/errors.js CHANGED
@@ -53,6 +53,7 @@ createError('FST_ERR_SEND_INSIDE_ONERR', 'You cannot use `send` inside the `onEr
53
53
  createError('FST_ERR_SCH_MISSING_ID', `Missing schema $id property`)
54
54
  createError('FST_ERR_SCH_ALREADY_PRESENT', `Schema with id '%s' already declared!`)
55
55
  createError('FST_ERR_SCH_NOT_PRESENT', `Schema with id '%s' does not exist!`)
56
+ createError('FST_ERR_SCH_DUPLICATE', `Schema with '%s' already present!`)
56
57
 
57
58
  /**
58
59
  * wrapThenable
@@ -72,7 +72,7 @@ function handler (request, reply) {
72
72
  }
73
73
 
74
74
  function preValidationCallback (err, request, reply) {
75
- if (reply.res.finished === true) return
75
+ if (reply.sent === true || reply.res.finished === true) return
76
76
  if (err != null) {
77
77
  reply.send(err)
78
78
  return
@@ -103,7 +103,7 @@ function preValidationCallback (err, request, reply) {
103
103
  }
104
104
 
105
105
  function preHandlerCallback (err, request, reply) {
106
- if (reply.res.finished === true) return
106
+ if (reply.sent || reply.res.finished === true) return
107
107
  if (err != null) {
108
108
  reply.send(err)
109
109
  return
package/lib/reply.js CHANGED
@@ -11,6 +11,7 @@ const {
11
11
  kReplySentOverwritten,
12
12
  kReplyStartTime,
13
13
  kReplySerializer,
14
+ kReplySerializerDefault,
14
15
  kReplyIsError,
15
16
  kReplyHeaders,
16
17
  kReplyHasStatusCode,
@@ -195,7 +196,11 @@ Reply.prototype.serialize = function (payload) {
195
196
  if (this[kReplySerializer] !== null) {
196
197
  return this[kReplySerializer](payload)
197
198
  } else {
198
- return serialize(this.context, payload, this.res.statusCode)
199
+ if (this.context && this.context[kReplySerializerDefault]) {
200
+ return this.context[kReplySerializerDefault](payload, this.res.statusCode)
201
+ } else {
202
+ return serialize(this.context, payload, this.res.statusCode)
203
+ }
199
204
  }
200
205
  }
201
206
 
@@ -222,6 +227,16 @@ Reply.prototype.callNotFound = function () {
222
227
  notFound(this)
223
228
  }
224
229
 
230
+ Reply.prototype.getResponseTime = function () {
231
+ var responseTime = 0
232
+
233
+ if (this[kReplyStartTime] !== undefined) {
234
+ responseTime = now() - this[kReplyStartTime]
235
+ }
236
+
237
+ return responseTime
238
+ }
239
+
225
240
  function preserializeHook (reply, payload) {
226
241
  if (reply.context.preSerialization !== null) {
227
242
  onSendHookRunner(
@@ -242,7 +257,12 @@ function preserializeHookEnd (err, request, reply, payload) {
242
257
  return
243
258
  }
244
259
 
245
- payload = serialize(reply.context, payload, reply.res.statusCode)
260
+ if (reply.context && reply.context[kReplySerializerDefault]) {
261
+ payload = reply.context[kReplySerializerDefault](payload, reply.res.statusCode)
262
+ } else {
263
+ payload = serialize(reply.context, payload, reply.res.statusCode)
264
+ }
265
+
246
266
  flatstr(payload)
247
267
 
248
268
  onSendHook(reply, payload)
@@ -460,11 +480,8 @@ function onResponseCallback (err, request, reply) {
460
480
  if (reply.log[kDisableRequestLogging]) {
461
481
  return
462
482
  }
463
- var responseTime = 0
464
483
 
465
- if (reply[kReplyStartTime] !== undefined) {
466
- responseTime = now() - reply[kReplyStartTime]
467
- }
484
+ var responseTime = reply.getResponseTime()
468
485
 
469
486
  if (err != null) {
470
487
  reply.log.error({
package/lib/request.js CHANGED
@@ -30,12 +30,12 @@ function buildRequest (R) {
30
30
  }
31
31
 
32
32
  Object.defineProperties(Request.prototype, {
33
- 'req': {
33
+ req: {
34
34
  get: function () {
35
35
  return this.raw
36
36
  }
37
37
  },
38
- 'id': {
38
+ id: {
39
39
  get: function () {
40
40
  return this.raw.id
41
41
  }
package/lib/route.js CHANGED
@@ -19,6 +19,7 @@ const {
19
19
  kSchemaCompiler,
20
20
  kContentTypeParser,
21
21
  kReply,
22
+ kReplySerializerDefault,
22
23
  kRequest,
23
24
  kMiddlewares,
24
25
  kGlobalHooks,
@@ -45,6 +46,8 @@ function buildRouting (options) {
45
46
  let modifyCoreObjects
46
47
  let genReqId
47
48
  let disableRequestLogging
49
+ let ignoreTrailingSlash
50
+ let return503OnClosing
48
51
 
49
52
  let closing = false
50
53
 
@@ -65,6 +68,8 @@ function buildRouting (options) {
65
68
  modifyCoreObjects = options.modifyCoreObjects
66
69
  genReqId = options.genReqId
67
70
  disableRequestLogging = options.disableRequestLogging
71
+ ignoreTrailingSlash = options.ignoreTrailingSlash
72
+ return503OnClosing = Object.prototype.hasOwnProperty.call(options, 'return503OnClosing') ? options.return503OnClosing : true
68
73
  },
69
74
  routing: router.lookup.bind(router), // router func to find the right handler to call
70
75
  route, // configure a route in the fastify instance
@@ -142,7 +147,10 @@ function buildRouting (options) {
142
147
  case 'both':
143
148
  default:
144
149
  afterRouteAdded.call(this, '', notHandledErr, done)
145
- afterRouteAdded.call(this, path, notHandledErr, done)
150
+ // If ignoreTrailingSlash is set to true we need to add only the '' route to prevent adding an incomplete one.
151
+ if (ignoreTrailingSlash !== true) {
152
+ afterRouteAdded.call(this, path, notHandledErr, done)
153
+ }
146
154
  }
147
155
  } else if (path[0] === '/' && prefix.endsWith('/')) {
148
156
  // Ensure that '/prefix/' + '/route' gets registered as '/prefix/route'
@@ -190,7 +198,8 @@ function buildRouting (options) {
190
198
  this._errorHandler,
191
199
  opts.bodyLimit,
192
200
  opts.logLevel,
193
- opts.attachValidation
201
+ opts.attachValidation,
202
+ this[kReplySerializerDefault]
194
203
  )
195
204
 
196
205
  // TODO this needs to be refactored so that buildSchemaCompiler is
@@ -212,7 +221,7 @@ function buildRouting (options) {
212
221
 
213
222
  const hooks = ['preParsing', 'preValidation', 'onRequest', 'preHandler', 'preSerialization']
214
223
 
215
- for (let hook of hooks) {
224
+ for (const hook of hooks) {
216
225
  if (opts[hook]) {
217
226
  if (Array.isArray(opts[hook])) {
218
227
  opts[hook] = opts[hook].map(fn => fn.bind(this))
@@ -241,7 +250,7 @@ function buildRouting (options) {
241
250
  context.onError = onError.length ? onError : null
242
251
  context.onResponse = onResponse.length ? onResponse : null
243
252
 
244
- for (let hook of hooks) {
253
+ for (const hook of hooks) {
245
254
  const toSet = this[kHooks][hook].concat(opts[hook] || [])
246
255
  context[hook] = toSet.length ? toSet : null
247
256
  }
@@ -260,20 +269,20 @@ function buildRouting (options) {
260
269
  // HTTP request entry point, the routing has already been executed
261
270
  function routeHandler (req, res, params, context) {
262
271
  if (closing === true) {
263
- const headers = {
264
- 'Content-Type': 'application/json',
265
- 'Content-Length': '80'
266
- }
267
272
  if (req.httpVersionMajor !== 2) {
268
- headers.Connection = 'close'
273
+ res.once('finish', () => req.destroy())
274
+ res.setHeader('Connection', 'close')
269
275
  }
270
- res.writeHead(503, headers)
271
- res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
272
- if (req.httpVersionMajor !== 2) {
273
- // This is not needed in HTTP/2
274
- setImmediate(() => req.destroy())
276
+
277
+ if (return503OnClosing) {
278
+ const headers = {
279
+ 'Content-Type': 'application/json',
280
+ 'Content-Length': '80'
281
+ }
282
+ res.writeHead(503, headers)
283
+ res.end('{"error":"Service Unavailable","message":"Service Unavailable","statusCode":503}')
284
+ return
275
285
  }
276
- return
277
286
  }
278
287
 
279
288
  req.id = req.headers[requestIdHeader] || genReqId(req)
package/lib/schemas.js CHANGED
@@ -1,12 +1,14 @@
1
1
  'use strict'
2
2
 
3
3
  const fastClone = require('rfdc')({ circles: false, proto: true })
4
+ const kFluentSchema = Symbol.for('fluent-schema-object')
4
5
 
5
6
  const {
6
7
  codes: {
7
8
  FST_ERR_SCH_MISSING_ID,
8
9
  FST_ERR_SCH_ALREADY_PRESENT,
9
- FST_ERR_SCH_NOT_PRESENT
10
+ FST_ERR_SCH_NOT_PRESENT,
11
+ FST_ERR_SCH_DUPLICATE
10
12
  }
11
13
  } = require('./errors')
12
14
 
@@ -17,7 +19,10 @@ function Schemas () {
17
19
  }
18
20
 
19
21
  Schemas.prototype.add = function (inputSchema) {
20
- const schema = fastClone(inputSchema)
22
+ var schema = fastClone(inputSchema[kFluentSchema]
23
+ ? inputSchema.valueOf()
24
+ : inputSchema
25
+ )
21
26
  const id = schema['$id']
22
27
  if (id === undefined) {
23
28
  throw new FST_ERR_SCH_MISSING_ID()
@@ -38,6 +43,15 @@ Schemas.prototype.resolve = function (id) {
38
43
  }
39
44
 
40
45
  Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
46
+ // alias query to querystring schema
47
+ if (routeSchemas.query) {
48
+ // check if our schema has both querystring and query
49
+ if (routeSchemas.querystring) {
50
+ throw new FST_ERR_SCH_DUPLICATE('querystring')
51
+ }
52
+ routeSchemas.querystring = routeSchemas.query
53
+ }
54
+
41
55
  // let's check if our schemas have a custom prototype
42
56
  for (const key of ['headers', 'querystring', 'params', 'body']) {
43
57
  if (typeof routeSchemas[key] === 'object' && Object.getPrototypeOf(routeSchemas[key]) !== Object.prototype) {
@@ -45,6 +59,9 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
45
59
  }
46
60
  }
47
61
 
62
+ // See issue https://github.com/fastify/fastify/issues/1767
63
+ const cachedSchema = Object.assign({}, routeSchemas)
64
+
48
65
  try {
49
66
  // this will work only for standard json schemas
50
67
  // other compilers such as Joi will fail
@@ -58,9 +75,13 @@ Schemas.prototype.resolveRefs = function (routeSchemas, dontClearId) {
58
75
  this.cleanId(routeSchemas)
59
76
  }
60
77
  } catch (err) {
61
- // if we have failed because `resolve has thrown
78
+ // if we have failed because `resolve` has thrown
62
79
  // let's rethrow the error and let avvio handle it
63
80
  if (/FST_ERR_SCH_*/.test(err.code)) throw err
81
+
82
+ // otherwise, the schema must not be a JSON schema
83
+ // so we let the user configured schemaCompiler handle it
84
+ return cachedSchema
64
85
  }
65
86
 
66
87
  if (routeSchemas.headers) {
package/lib/symbols.js CHANGED
@@ -8,6 +8,7 @@ const keys = {
8
8
  kHooks: Symbol('fastify.hooks'),
9
9
  kSchemas: Symbol('fastify.schemas'),
10
10
  kSchemaCompiler: Symbol('fastify.schemaCompiler'),
11
+ kReplySerializerDefault: Symbol('fastify.replySerializerDefault'),
11
12
  kContentTypeParser: Symbol('fastify.contentTypeParser'),
12
13
  kReply: Symbol('fastify.Reply'),
13
14
  kRequest: Symbol('fastify.Request'),
package/lib/validation.js CHANGED
@@ -8,6 +8,7 @@ const querystringSchema = Symbol('querystring-schema')
8
8
  const paramsSchema = Symbol('params-schema')
9
9
  const responseSchema = Symbol('response-schema')
10
10
  const headersSchema = Symbol('headers-schema')
11
+ const kFluentSchema = Symbol.for('fluent-schema-object')
11
12
 
12
13
  function getValidatorForStatusCodeSchema (statusCodeDefinition, externalSchema) {
13
14
  return fastJsonStringify(statusCodeDefinition, { schema: externalSchema })
@@ -26,6 +27,7 @@ function build (context, compile, schemas) {
26
27
  return
27
28
  }
28
29
 
30
+ generateFluentSchema(context.schema)
29
31
  context.schema = schemas.resolveRefs(context.schema)
30
32
 
31
33
  const headers = context.schema.headers
@@ -66,6 +68,22 @@ function build (context, compile, schemas) {
66
68
  }
67
69
  }
68
70
 
71
+ function generateFluentSchema (schema) {
72
+ ;['params', 'body', 'querystring', 'query', 'headers'].forEach(key => {
73
+ if (schema[key] && schema[key][kFluentSchema]) {
74
+ schema[key] = schema[key].valueOf()
75
+ }
76
+ })
77
+
78
+ if (schema.response) {
79
+ Object.keys(schema.response).forEach(code => {
80
+ if (schema.response[code][kFluentSchema]) {
81
+ schema.response[code] = schema.response[code].valueOf()
82
+ }
83
+ })
84
+ }
85
+ }
86
+
69
87
  function validateParam (validatorFunction, request, paramName) {
70
88
  var ret = validatorFunction && validatorFunction(request[paramName])
71
89
  if (ret === false) return validatorFunction.errors
@@ -152,6 +170,7 @@ function buildSchemaCompiler (externalSchemas, cache) {
152
170
  useDefaults: true,
153
171
  removeAdditional: true,
154
172
  allErrors: true,
173
+ nullable: true,
155
174
  cache
156
175
  })
157
176
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fastify",
3
- "version": "2.4.1",
3
+ "version": "2.7.1",
4
4
  "description": "Fast and low overhead web framework, for Node.js",
5
5
  "main": "fastify.js",
6
6
  "typings": "fastify.d.ts",
@@ -8,8 +8,8 @@
8
8
  "lint": "npm run lint:standard && npm run lint:typescript",
9
9
  "lint:standard": "standard --verbose | snazzy",
10
10
  "lint:typescript": "standard --parser @typescript-eslint/parser --plugin @typescript-eslint/eslint-plugin test/types/*.ts fastify.d.ts",
11
- "unit": "tap -J test/*.test.js test/*/*.test.js",
12
- "unit:report": "tap -J test/*.test.js test/*/*.test.js --cov --coverage-report=html --coverage-report=cobertura | tee out.tap",
11
+ "unit": "tap --no-esm -J test/*.test.js test/*/*.test.js",
12
+ "unit:report": "tap --no-esm -J test/*.test.js test/*/*.test.js --cov --coverage-report=html --coverage-report=cobertura | tee out.tap",
13
13
  "unit:junit": "tap-mocha-reporter xunit < out.tap > test/junit-testresults.xml",
14
14
  "typescript": "tsc --project ./test/types/tsconfig.json",
15
15
  "test:report": "npm run lint && npm run unit:report && npm run typescript",
@@ -88,25 +88,25 @@
88
88
  "node": ">=6"
89
89
  },
90
90
  "devDependencies": {
91
- "@types/node": "^11.9.3",
92
- "@typescript-eslint/eslint-plugin": "^1.4.2",
93
- "@typescript-eslint/parser": "^1.4.2",
91
+ "@types/node": "^11.13.18",
92
+ "@typescript-eslint/eslint-plugin": "^1.13.0",
93
+ "@typescript-eslint/parser": "^1.13.0",
94
94
  "JSONStream": "^1.3.5",
95
95
  "ajv-pack": "^0.3.1",
96
96
  "autocannon": "^3.2.0",
97
97
  "branch-comparer": "^0.4.0",
98
- "concurrently": "^4.1.0",
98
+ "concurrently": "^4.1.1",
99
99
  "cors": "^2.8.5",
100
- "coveralls": "^3.0.3",
100
+ "coveralls": "^3.0.5",
101
101
  "dns-prefetch-control": "^0.2.0",
102
102
  "eslint-import-resolver-node": "^0.3.2",
103
103
  "fast-json-body": "^1.1.0",
104
104
  "fastify-plugin": "^1.5.0",
105
- "fluent-schema": "^0.7.0",
106
- "form-data": "^2.3.3",
105
+ "fluent-schema": "^0.7.3",
106
+ "form-data": "^2.5.0",
107
107
  "frameguard": "^3.0.0",
108
108
  "h2url": "^0.1.2",
109
- "helmet": "^3.15.1",
109
+ "helmet": "^3.20.0",
110
110
  "hide-powered-by": "^1.0.0",
111
111
  "hsts": "^2.1.0",
112
112
  "http-errors": "^1.7.1",
@@ -115,31 +115,31 @@
115
115
  "license-checker": "^25.0.1",
116
116
  "lolex": "^4.0.1",
117
117
  "pre-commit": "^1.2.2",
118
- "proxyquire": "^2.1.0",
118
+ "proxyquire": "^2.1.1",
119
119
  "pump": "^3.0.0",
120
- "semver": "^6.0.0",
120
+ "semver": "^6.3.0",
121
121
  "send": "^0.17.0",
122
122
  "serve-static": "^1.13.2",
123
123
  "simple-get": "^3.0.3",
124
124
  "snazzy": "^8.0.0",
125
125
  "split2": "^3.1.0",
126
- "standard": "^12.0.1",
126
+ "standard": "^13.0.1",
127
127
  "tap": "^12.5.2",
128
128
  "tap-mocha-reporter": "^3.0.7",
129
129
  "then-sleep": "^1.0.1",
130
- "typescript": "^3.3.3333",
130
+ "typescript": "^3.5.3",
131
131
  "x-xss-protection": "^1.1.0"
132
132
  },
133
133
  "dependencies": {
134
134
  "abstract-logging": "^1.0.0",
135
- "ajv": "^6.9.2",
135
+ "ajv": "^6.10.2",
136
136
  "avvio": "^6.1.1",
137
137
  "fast-json-stringify": "^1.15.0",
138
138
  "find-my-way": "^2.0.0",
139
139
  "flatstr": "^1.0.12",
140
- "light-my-request": "^3.2.0",
140
+ "light-my-request": "^3.4.1",
141
141
  "middie": "^4.0.1",
142
- "pino": "^5.11.1",
142
+ "pino": "^5.13.1",
143
143
  "proxy-addr": "^2.0.4",
144
144
  "readable-stream": "^3.1.1",
145
145
  "rfdc": "^1.1.2",
@@ -148,6 +148,7 @@
148
148
  },
149
149
  "greenkeeper": {
150
150
  "ignore": [
151
+ "autocannon",
151
152
  "boom",
152
153
  "joi",
153
154
  "@types/node",
@@ -419,7 +419,7 @@ function asyncTest (t) {
419
419
 
420
420
  var fastify = null
421
421
  var stream = split(JSON.parse)
422
- var payload = { 'hello': 'world' }
422
+ var payload = { hello: 'world' }
423
423
  try {
424
424
  fastify = Fastify({
425
425
  logger: {
@@ -28,8 +28,49 @@ test('Should return 503 while closing - pipelining', t => {
28
28
  t.strictEqual(statusCode, codes.shift())
29
29
  })
30
30
 
31
- instance.on('done', () => t.end('Done'))
32
- instance.on('reqError', () => t.deepEqual(codes.shift(), null))
31
+ instance.on('done', () => {
32
+ t.strictEqual(codes.length, 0)
33
+ t.end('Done')
34
+ })
35
+ instance.on('reqError', () => {
36
+ t.strictEqual(codes.shift(), undefined)
37
+ })
38
+ instance.on('error', err => t.fail(err))
39
+ })
40
+ })
41
+
42
+ test('Should not return 503 while closing - pipelining - return503OnClosing', t => {
43
+ const fastify = Fastify({
44
+ return503OnClosing: false
45
+ })
46
+
47
+ fastify.get('/', (req, reply) => {
48
+ fastify.close()
49
+ reply.send({ hello: 'world' })
50
+ })
51
+
52
+ fastify.listen(0, err => {
53
+ t.error(err)
54
+
55
+ const instance = autocannon({
56
+ url: 'http://localhost:' + fastify.server.address().port,
57
+ pipelining: 1,
58
+ connections: 1,
59
+ amount: 10
60
+ })
61
+
62
+ const codes = [200, 200]
63
+ instance.on('response', (client, statusCode) => {
64
+ t.strictEqual(statusCode, codes.shift())
65
+ })
66
+
67
+ instance.on('done', () => {
68
+ t.strictEqual(codes.length, 0)
69
+ t.end('Done')
70
+ })
71
+ instance.on('reqError', () => {
72
+ t.strictEqual(codes.shift(), undefined)
73
+ })
33
74
  instance.on('error', err => t.fail(err))
34
75
  })
35
76
  })
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const net = require('net')
3
4
  const t = require('tap')
4
5
  const test = t.test
5
6
  const Fastify = require('..')
@@ -114,8 +115,8 @@ test('onClose should keep the context', t => {
114
115
  })
115
116
  })
116
117
 
117
- test('Should return 503 while closing - injection', t => {
118
- t.plan(8)
118
+ test('Should return error while closing - injection', t => {
119
+ t.plan(4)
119
120
  const fastify = Fastify()
120
121
 
121
122
  fastify.addHook('onClose', (instance, done) => {
@@ -139,17 +140,73 @@ test('Should return 503 while closing - injection', t => {
139
140
  method: 'GET',
140
141
  url: '/'
141
142
  }, (err, res) => {
142
- t.error(err)
143
- t.strictEqual(res.statusCode, 503)
144
- t.strictEqual(res.headers['content-type'], 'application/json')
145
- t.strictEqual(res.headers['content-length'], '80')
146
- t.strictEqual(res.headers['connection'], 'close')
147
- t.deepEqual(JSON.parse(res.payload), {
148
- error: 'Service Unavailable',
149
- message: 'Service Unavailable',
150
- statusCode: 503
151
- })
143
+ t.ok(err)
144
+ t.equal(err.message, 'Server is closed')
152
145
  })
153
146
  }, 100)
154
147
  })
155
148
  })
149
+
150
+ t.test('Current opened connection should continue to work after closing and return "connection: close" header - return503OnClosing: false', t => {
151
+ const fastify = Fastify({
152
+ return503OnClosing: false
153
+ })
154
+
155
+ fastify.get('/', (req, reply) => {
156
+ fastify.close()
157
+ reply.send({ hello: 'world' })
158
+ })
159
+
160
+ fastify.listen(0, err => {
161
+ t.error(err)
162
+
163
+ const port = fastify.server.address().port
164
+ const client = net.createConnection({ port: port }, () => {
165
+ client.write('GET / HTTP/1.1\r\n\r\n')
166
+
167
+ client.once('data', data => {
168
+ t.match(data.toString(), /Connection:\s*keep-alive/i)
169
+ t.match(data.toString(), /200 OK/i)
170
+
171
+ client.write('GET / HTTP/1.1\r\n\r\n')
172
+
173
+ client.once('data', data => {
174
+ t.match(data.toString(), /Connection:\s*close/i)
175
+ t.match(data.toString(), /200 OK/i)
176
+
177
+ // Test that fastify closes the TCP connection
178
+ client.once('close', () => {
179
+ t.end()
180
+ })
181
+ })
182
+ })
183
+ })
184
+ })
185
+ })
186
+
187
+ t.test('Current opened connection should not accept new incoming connections', t => {
188
+ const fastify = Fastify()
189
+
190
+ fastify.get('/', (req, reply) => {
191
+ fastify.close()
192
+ reply.send({ hello: 'world' })
193
+ })
194
+
195
+ fastify.listen(0, err => {
196
+ t.error(err)
197
+
198
+ const port = fastify.server.address().port
199
+ const client = net.createConnection({ port: port }, () => {
200
+ client.write('GET / HTTP/1.1\r\n\r\n')
201
+
202
+ const newConnection = net.createConnection({ port: port })
203
+ newConnection.on('error', err => {
204
+ t.ok(err)
205
+ t.ok(['ECONNREFUSED', 'ECONNRESET'].includes(err.code))
206
+
207
+ client.end()
208
+ t.end()
209
+ })
210
+ })
211
+ })
212
+ })
@@ -34,7 +34,7 @@ test('default 413 with bodyLimit option', t => {
34
34
  })
35
35
  })
36
36
 
37
- test('default 413 with wrong content-length', t => {
37
+ test('default 400 with wrong content-length', t => {
38
38
  t.plan(4)
39
39
 
40
40
  const fastify = Fastify()
@@ -102,7 +102,7 @@ test('custom 413 with bodyLimit option', t => {
102
102
  })
103
103
  })
104
104
 
105
- test('custom 413 with wrong content-length', t => {
105
+ test('custom 400 with wrong content-length', t => {
106
106
  t.plan(4)
107
107
 
108
108
  const fastify = Fastify()
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ /* eslint no-prototype-builtins: 0 */
4
+
3
5
  const t = require('tap')
4
6
  const test = t.test
5
7
  const Fastify = require('..')