fastify 3.27.3 → 4.0.0-alpha.2

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 (164) hide show
  1. package/.taprc +3 -0
  2. package/README.md +7 -7
  3. package/build/build-error-serializer.js +27 -0
  4. package/build/build-validation.js +47 -35
  5. package/docs/Guides/Database.md +320 -0
  6. package/docs/Guides/Getting-Started.md +7 -7
  7. package/docs/Guides/Plugins-Guide.md +1 -1
  8. package/docs/Guides/Serverless.md +3 -3
  9. package/docs/Guides/Testing.md +2 -2
  10. package/docs/Migration-Guide-V4.md +12 -0
  11. package/docs/Reference/ContentTypeParser.md +4 -0
  12. package/docs/Reference/Decorators.md +2 -2
  13. package/docs/Reference/Encapsulation.md +2 -2
  14. package/docs/Reference/Errors.md +51 -6
  15. package/docs/Reference/HTTP2.md +3 -3
  16. package/docs/Reference/Hooks.md +4 -7
  17. package/docs/Reference/LTS.md +5 -4
  18. package/docs/Reference/Plugins.md +3 -3
  19. package/docs/Reference/Reply.md +23 -22
  20. package/docs/Reference/Request.md +1 -3
  21. package/docs/Reference/Routes.md +22 -15
  22. package/docs/Reference/Server.md +69 -119
  23. package/docs/Reference/TypeScript.md +20 -22
  24. package/docs/Reference/Validation-and-Serialization.md +30 -55
  25. package/docs/Type-Providers.md +257 -0
  26. package/examples/asyncawait.js +1 -1
  27. package/examples/benchmark/hooks-benchmark-async-await.js +1 -1
  28. package/examples/benchmark/hooks-benchmark.js +1 -1
  29. package/examples/benchmark/simple.js +1 -1
  30. package/examples/hooks.js +2 -2
  31. package/examples/http2.js +1 -1
  32. package/examples/https.js +1 -1
  33. package/examples/parser.js +1 -1
  34. package/examples/route-prefix.js +1 -1
  35. package/examples/shared-schema.js +1 -1
  36. package/examples/simple-stream.js +18 -0
  37. package/examples/simple.js +1 -1
  38. package/examples/simple.mjs +1 -1
  39. package/examples/typescript-server.ts +1 -1
  40. package/examples/use-plugin.js +1 -1
  41. package/fastify.d.ts +34 -22
  42. package/fastify.js +40 -36
  43. package/lib/configValidator.js +902 -1023
  44. package/lib/contentTypeParser.js +6 -16
  45. package/lib/context.js +36 -10
  46. package/lib/decorate.js +3 -1
  47. package/lib/error-handler.js +158 -0
  48. package/lib/error-serializer.js +257 -0
  49. package/lib/errors.js +43 -9
  50. package/lib/fourOhFour.js +31 -20
  51. package/lib/handleRequest.js +10 -13
  52. package/lib/hooks.js +14 -9
  53. package/lib/pluginOverride.js +0 -3
  54. package/lib/pluginUtils.js +3 -2
  55. package/lib/reply.js +29 -158
  56. package/lib/request.js +13 -10
  57. package/lib/route.js +131 -138
  58. package/lib/schema-controller.js +2 -2
  59. package/lib/schemas.js +27 -1
  60. package/lib/server.js +241 -116
  61. package/lib/symbols.js +4 -3
  62. package/lib/validation.js +2 -1
  63. package/lib/warnings.js +4 -12
  64. package/lib/wrapThenable.js +4 -11
  65. package/package.json +37 -39
  66. package/test/404s.test.js +258 -125
  67. package/test/500s.test.js +3 -3
  68. package/test/als.test.js +1 -1
  69. package/test/async-await.test.js +20 -76
  70. package/test/bodyLimit.test.js +1 -1
  71. package/test/build-certificate.js +6 -7
  72. package/test/case-insensitive.test.js +4 -4
  73. package/test/close-pipelining.test.js +2 -2
  74. package/test/close.test.js +11 -11
  75. package/test/content-parser.test.js +32 -0
  76. package/test/context-config.test.js +52 -0
  77. package/test/custom-http-server.test.js +14 -7
  78. package/test/custom-parser-async.test.js +1 -66
  79. package/test/custom-parser.test.js +92 -159
  80. package/test/custom-querystring-parser.test.js +3 -3
  81. package/test/decorator.test.js +11 -13
  82. package/test/delete.test.js +6 -6
  83. package/test/encapsulated-error-handler.test.js +50 -0
  84. package/test/esm/index.test.js +0 -14
  85. package/test/fastify-instance.test.js +4 -4
  86. package/test/fluent-schema.test.js +4 -4
  87. package/test/genReqId.test.js +1 -1
  88. package/test/get.test.js +4 -4
  89. package/test/handler-context.test.js +2 -2
  90. package/test/head.test.js +1 -1
  91. package/test/helper.js +19 -4
  92. package/test/hooks-async.test.js +15 -48
  93. package/test/hooks.on-ready.test.js +10 -5
  94. package/test/hooks.test.js +78 -119
  95. package/test/http2/closing.test.js +10 -16
  96. package/test/http2/constraint.test.js +1 -1
  97. package/test/http2/head.test.js +1 -1
  98. package/test/http2/plain.test.js +1 -1
  99. package/test/http2/secure-with-fallback.test.js +1 -1
  100. package/test/http2/secure.test.js +1 -1
  101. package/test/http2/unknown-http-method.test.js +4 -10
  102. package/test/https/custom-https-server.test.js +12 -6
  103. package/test/https/https.test.js +1 -1
  104. package/test/input-validation.js +3 -3
  105. package/test/internals/handleRequest.test.js +6 -43
  106. package/test/internals/initialConfig.test.js +41 -12
  107. package/test/internals/logger.test.js +2 -2
  108. package/test/internals/reply.test.js +281 -40
  109. package/test/internals/request.test.js +13 -7
  110. package/test/internals/server.test.js +88 -0
  111. package/test/listen.deprecated.test.js +202 -0
  112. package/test/listen.test.js +118 -150
  113. package/test/logger.test.js +82 -42
  114. package/test/maxRequestsPerSocket.test.js +8 -6
  115. package/test/middleware.test.js +2 -25
  116. package/test/nullable-validation.test.js +53 -16
  117. package/test/output-validation.test.js +1 -1
  118. package/test/plugin.test.js +47 -21
  119. package/test/pretty-print.test.js +22 -10
  120. package/test/promises.test.js +1 -1
  121. package/test/proto-poisoning.test.js +6 -6
  122. package/test/register.test.js +3 -3
  123. package/test/reply-error.test.js +126 -15
  124. package/test/request-error.test.js +3 -6
  125. package/test/route-hooks.test.js +18 -18
  126. package/test/route-prefix.test.js +2 -1
  127. package/test/route.test.js +206 -22
  128. package/test/router-options.test.js +2 -2
  129. package/test/schema-examples.test.js +11 -5
  130. package/test/schema-feature.test.js +25 -20
  131. package/test/schema-serialization.test.js +9 -9
  132. package/test/schema-special-usage.test.js +5 -153
  133. package/test/schema-validation.test.js +9 -9
  134. package/test/skip-reply-send.test.js +2 -2
  135. package/test/stream.test.js +82 -23
  136. package/test/throw.test.js +8 -5
  137. package/test/trust-proxy.test.js +6 -6
  138. package/test/type-provider.test.js +20 -0
  139. package/test/types/fastify.test-d.ts +10 -18
  140. package/test/types/import.js +2 -0
  141. package/test/types/import.ts +1 -0
  142. package/test/types/instance.test-d.ts +68 -17
  143. package/test/types/logger.test-d.ts +44 -15
  144. package/test/types/reply.test-d.ts +2 -1
  145. package/test/types/route.test-d.ts +8 -2
  146. package/test/types/schema.test-d.ts +2 -39
  147. package/test/types/type-provider.test-d.ts +417 -0
  148. package/test/url-rewriting.test.js +3 -3
  149. package/test/validation-error-handling.test.js +8 -8
  150. package/test/versioned-routes.test.js +30 -18
  151. package/test/wrapThenable.test.js +7 -6
  152. package/types/content-type-parser.d.ts +17 -8
  153. package/types/hooks.d.ts +102 -59
  154. package/types/instance.d.ts +244 -118
  155. package/types/logger.d.ts +18 -104
  156. package/types/plugin.d.ts +10 -4
  157. package/types/reply.d.ts +18 -12
  158. package/types/request.d.ts +10 -5
  159. package/types/route.d.ts +42 -31
  160. package/types/schema.d.ts +1 -1
  161. package/types/type-provider.d.ts +99 -0
  162. package/types/utils.d.ts +1 -1
  163. package/lib/schema-compilers.js +0 -12
  164. package/test/emit-warning.test.js +0 -166
@@ -5,9 +5,8 @@ const test = t.test
5
5
  const sget = require('simple-get').concat
6
6
  const http = require('http')
7
7
  const NotFound = require('http-errors').NotFound
8
- const EventEmitter = require('events').EventEmitter
9
8
  const Reply = require('../../lib/reply')
10
- const { Writable } = require('stream')
9
+ const { Readable, Writable } = require('stream')
11
10
  const {
12
11
  kReplyErrorHandlerCalled,
13
12
  kReplyHeaders,
@@ -15,6 +14,21 @@ const {
15
14
  kReplyIsError,
16
15
  kReplySerializerDefault
17
16
  } = require('../../lib/symbols')
17
+ const fs = require('fs')
18
+ const path = require('path')
19
+ const warning = require('../../lib/warnings')
20
+
21
+ const doGet = function (url) {
22
+ return new Promise((resolve, reject) => {
23
+ sget({ method: 'GET', url, followRedirects: false }, (err, response, body) => {
24
+ if (err) {
25
+ reject(err)
26
+ } else {
27
+ resolve({ response, body })
28
+ }
29
+ })
30
+ })
31
+ }
18
32
 
19
33
  test('Once called, Reply should return an object with methods', t => {
20
34
  t.plan(13)
@@ -37,33 +51,33 @@ test('Once called, Reply should return an object with methods', t => {
37
51
  t.equal(reply.request, request)
38
52
  })
39
53
 
40
- test('reply.send will logStream error and destroy the stream', { only: true }, t => {
54
+ test('reply.send will logStream error and destroy the stream', t => {
41
55
  t.plan(1)
42
56
  let destroyCalled
43
- const payload = new EventEmitter()
57
+ const payload = new Readable({
58
+ read () {},
59
+ destroy (err, cb) {
60
+ destroyCalled = true
61
+ cb(err)
62
+ }
63
+ })
44
64
 
45
- const response = {
65
+ const response = new Writable()
66
+ Object.assign(response, {
46
67
  setHeader: () => {},
47
68
  hasHeader: () => false,
48
69
  getHeader: () => undefined,
49
70
  writeHead: () => {},
50
- end: () => {},
51
- headersSent: true,
52
- destroy: () => (destroyCalled = true),
53
- on: () => {}
54
- }
71
+ headersSent: true
72
+ })
55
73
 
56
74
  const log = {
57
75
  warn: () => {}
58
76
  }
59
77
 
60
- Object.assign(payload, {
61
- pipe: () => {},
62
- destroy: () => {}
63
- })
64
78
  const reply = new Reply(response, { context: { onSend: null } }, log)
65
79
  reply.send(payload)
66
- payload.emit('error', new Error('stream error'))
80
+ payload.destroy(new Error('stream error'))
67
81
 
68
82
  t.equal(destroyCalled, true, 'Error not logged and not streamed')
69
83
  })
@@ -248,7 +262,7 @@ test('within an instance', t => {
248
262
  done()
249
263
  })
250
264
 
251
- fastify.listen(0, err => {
265
+ fastify.listen({ port: 0 }, err => {
252
266
  t.error(err)
253
267
  fastify.server.unref()
254
268
 
@@ -408,7 +422,7 @@ test('buffer without content type should send a application/octet-stream and raw
408
422
  reply.send(Buffer.alloc(1024))
409
423
  })
410
424
 
411
- fastify.listen(0, err => {
425
+ fastify.listen({ port: 0 }, err => {
412
426
  t.error(err)
413
427
  fastify.server.unref()
414
428
 
@@ -433,7 +447,7 @@ test('buffer with content type should not send application/octet-stream', t => {
433
447
  reply.send(Buffer.alloc(1024))
434
448
  })
435
449
 
436
- fastify.listen(0, err => {
450
+ fastify.listen({ port: 0 }, err => {
437
451
  t.error(err)
438
452
  fastify.server.unref()
439
453
 
@@ -452,8 +466,6 @@ test('stream with content type should not send application/octet-stream', t => {
452
466
  t.plan(4)
453
467
 
454
468
  const fastify = require('../..')()
455
- const fs = require('fs')
456
- const path = require('path')
457
469
 
458
470
  const streamPath = path.join(__dirname, '..', '..', 'package.json')
459
471
  const stream = fs.createReadStream(streamPath)
@@ -463,7 +475,7 @@ test('stream with content type should not send application/octet-stream', t => {
463
475
  reply.header('Content-Type', 'text/plain').send(stream)
464
476
  })
465
477
 
466
- fastify.listen(0, err => {
478
+ fastify.listen({ port: 0 }, err => {
467
479
  t.error(err)
468
480
  fastify.server.unref()
469
481
  sget({
@@ -477,6 +489,32 @@ test('stream with content type should not send application/octet-stream', t => {
477
489
  })
478
490
  })
479
491
 
492
+ test('stream without content type should not send application/octet-stream', t => {
493
+ t.plan(4)
494
+
495
+ const fastify = require('../..')()
496
+
497
+ const stream = fs.createReadStream(__filename)
498
+ const buf = fs.readFileSync(__filename)
499
+
500
+ fastify.get('/', function (req, reply) {
501
+ reply.send(stream)
502
+ })
503
+
504
+ fastify.listen({ port: 0 }, err => {
505
+ t.error(err)
506
+ fastify.server.unref()
507
+ sget({
508
+ method: 'GET',
509
+ url: 'http://localhost:' + fastify.server.address().port
510
+ }, (err, response, body) => {
511
+ t.error(err)
512
+ t.equal(response.headers['content-type'], undefined)
513
+ t.same(body, buf)
514
+ })
515
+ })
516
+ })
517
+
480
518
  test('stream using reply.raw.writeHead should return customize headers', t => {
481
519
  t.plan(6)
482
520
 
@@ -498,7 +536,7 @@ test('stream using reply.raw.writeHead should return customize headers', t => {
498
536
  reply.send(stream)
499
537
  })
500
538
 
501
- fastify.listen(0, err => {
539
+ fastify.listen({ port: 0 }, err => {
502
540
  t.error(err)
503
541
  fastify.server.unref()
504
542
  sget({
@@ -522,7 +560,7 @@ test('plain string without content type should send a text/plain', t => {
522
560
  reply.send('hello world!')
523
561
  })
524
562
 
525
- fastify.listen(0, err => {
563
+ fastify.listen({ port: 0 }, err => {
526
564
  t.error(err)
527
565
  fastify.server.unref()
528
566
 
@@ -546,7 +584,7 @@ test('plain string with content type should be sent unmodified', t => {
546
584
  reply.type('text/css').send('hello world!')
547
585
  })
548
586
 
549
- fastify.listen(0, err => {
587
+ fastify.listen({ port: 0 }, err => {
550
588
  t.error(err)
551
589
  fastify.server.unref()
552
590
 
@@ -573,7 +611,7 @@ test('plain string with content type and custom serializer should be serialized'
573
611
  .send('hello world!')
574
612
  })
575
613
 
576
- fastify.listen(0, err => {
614
+ fastify.listen({ port: 0 }, err => {
577
615
  t.error(err)
578
616
  fastify.server.unref()
579
617
 
@@ -597,7 +635,7 @@ test('plain string with content type application/json should NOT be serialized a
597
635
  reply.type('application/json').send('{"key": "hello world!"}')
598
636
  })
599
637
 
600
- fastify.listen(0, err => {
638
+ fastify.listen({ port: 0 }, err => {
601
639
  t.error(err)
602
640
  fastify.server.unref()
603
641
 
@@ -650,7 +688,7 @@ test('plain string with custom json content type should NOT be serialized as jso
650
688
  })
651
689
  })
652
690
 
653
- fastify.listen(0, err => {
691
+ fastify.listen({ port: 0 }, err => {
654
692
  t.error(err)
655
693
  fastify.server.unref()
656
694
 
@@ -676,7 +714,7 @@ test('non-string with content type application/json SHOULD be serialized as json
676
714
  reply.type('application/json').send({ key: 'hello world!' })
677
715
  })
678
716
 
679
- fastify.listen(0, err => {
717
+ fastify.listen({ port: 0 }, err => {
680
718
  t.error(err)
681
719
  fastify.server.unref()
682
720
 
@@ -725,7 +763,7 @@ test('non-string with custom json content type SHOULD be serialized as json', t
725
763
  })
726
764
  })
727
765
 
728
- fastify.listen(0, err => {
766
+ fastify.listen({ port: 0 }, err => {
729
767
  t.error(err)
730
768
  fastify.server.unref()
731
769
 
@@ -790,7 +828,7 @@ test('undefined payload should be sent as-is', t => {
790
828
  reply.code(204).send()
791
829
  })
792
830
 
793
- fastify.listen(0, err => {
831
+ fastify.listen({ port: 0 }, err => {
794
832
  t.error(err)
795
833
  fastify.server.unref()
796
834
 
@@ -835,7 +873,7 @@ test('for HEAD method, no body should be sent but content-length should be', t =
835
873
  reply.code(200).send(null)
836
874
  })
837
875
 
838
- fastify.listen(0, err => {
876
+ fastify.listen({ port: 0 }, err => {
839
877
  t.error(err)
840
878
  fastify.server.unref()
841
879
 
@@ -882,7 +920,7 @@ test('reply.send(new NotFound()) should not invoke the 404 handler', t => {
882
920
  done()
883
921
  }, { prefix: '/prefixed' })
884
922
 
885
- fastify.listen(0, err => {
923
+ fastify.listen({ port: 0 }, err => {
886
924
  t.error(err)
887
925
 
888
926
  fastify.server.unref()
@@ -929,7 +967,7 @@ test('reply can set multiple instances of same header', t => {
929
967
  .send({})
930
968
  })
931
969
 
932
- fastify.listen(0, err => {
970
+ fastify.listen({ port: 0 }, err => {
933
971
  t.error(err)
934
972
  fastify.server.unref()
935
973
 
@@ -956,7 +994,7 @@ test('reply.hasHeader returns correct values', t => {
956
994
  reply.send()
957
995
  })
958
996
 
959
- fastify.listen(0, err => {
997
+ fastify.listen({ port: 0 }, err => {
960
998
  t.error(err)
961
999
  fastify.server.unref()
962
1000
  sget({
@@ -985,7 +1023,7 @@ test('reply.getHeader returns correct values', t => {
985
1023
  reply.send()
986
1024
  })
987
1025
 
988
- fastify.listen(0, err => {
1026
+ fastify.listen({ port: 0 }, err => {
989
1027
  t.error(err)
990
1028
  fastify.server.unref()
991
1029
  sget({
@@ -1055,7 +1093,7 @@ test('reply.removeHeader can remove the value', t => {
1055
1093
  reply.send()
1056
1094
  })
1057
1095
 
1058
- fastify.listen(0, err => {
1096
+ fastify.listen({ port: 0 }, err => {
1059
1097
  t.error(err)
1060
1098
  fastify.server.unref()
1061
1099
  sget({
@@ -1082,7 +1120,7 @@ test('reply.header can reset the value', t => {
1082
1120
  reply.send()
1083
1121
  })
1084
1122
 
1085
- fastify.listen(0, err => {
1123
+ fastify.listen({ port: 0 }, err => {
1086
1124
  t.error(err)
1087
1125
  fastify.server.unref()
1088
1126
  sget({
@@ -1111,7 +1149,7 @@ test('reply.hasHeader computes raw and fastify headers', t => {
1111
1149
  reply.send()
1112
1150
  })
1113
1151
 
1114
- fastify.listen(0, err => {
1152
+ fastify.listen({ port: 0 }, err => {
1115
1153
  t.error(err)
1116
1154
  fastify.server.unref()
1117
1155
  sget({
@@ -1280,7 +1318,7 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
1280
1318
  .send({})
1281
1319
  })
1282
1320
 
1283
- fastify.listen(0, err => {
1321
+ fastify.listen({ port: 0 }, err => {
1284
1322
  t.error(err)
1285
1323
  fastify.server.unref()
1286
1324
 
@@ -1301,6 +1339,34 @@ test('reply.header setting multiple cookies as multiple Set-Cookie headers', t =
1301
1339
  })
1302
1340
  })
1303
1341
 
1342
+ test('should emit deprecation warning when trying to modify the reply.sent property', t => {
1343
+ t.plan(4)
1344
+ const fastify = require('../..')()
1345
+
1346
+ const deprecationCode = 'FSTDEP010'
1347
+ warning.emitted.delete(deprecationCode)
1348
+
1349
+ process.removeAllListeners('warning')
1350
+ process.on('warning', onWarning)
1351
+ function onWarning (warning) {
1352
+ t.equal(warning.name, 'FastifyDeprecation')
1353
+ t.equal(warning.code, deprecationCode)
1354
+ }
1355
+
1356
+ fastify.get('/', (req, reply) => {
1357
+ reply.sent = true
1358
+
1359
+ reply.raw.end()
1360
+ })
1361
+
1362
+ fastify.inject('/', (err, res) => {
1363
+ t.error(err)
1364
+ t.pass()
1365
+
1366
+ process.removeListener('warning', onWarning)
1367
+ })
1368
+ })
1369
+
1304
1370
  test('should throw error when passing falsy value to reply.sent', t => {
1305
1371
  t.plan(4)
1306
1372
  const fastify = require('../..')()
@@ -1329,6 +1395,7 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1329
1395
  reply.sent = true
1330
1396
  try {
1331
1397
  reply.sent = true
1398
+ t.fail('must throw')
1332
1399
  } catch (err) {
1333
1400
  t.equal(err.code, 'FST_ERR_REP_ALREADY_SENT')
1334
1401
  t.equal(err.message, 'Reply was already sent.')
@@ -1342,6 +1409,23 @@ test('should throw error when attempting to set reply.sent more than once', t =>
1342
1409
  })
1343
1410
  })
1344
1411
 
1412
+ test('should not throw error when attempting to set reply.sent if the underlining request was sent', t => {
1413
+ t.plan(3)
1414
+ const fastify = require('../..')()
1415
+
1416
+ fastify.get('/', function (req, reply) {
1417
+ reply.raw.end()
1418
+ t.doesNotThrow(() => {
1419
+ reply.sent = true
1420
+ })
1421
+ })
1422
+
1423
+ fastify.inject('/', (err, res) => {
1424
+ t.error(err)
1425
+ t.pass()
1426
+ })
1427
+ })
1428
+
1345
1429
  test('reply.getResponseTime() should return 0 before the timer is initialised on the reply by setting up response listeners', t => {
1346
1430
  t.plan(1)
1347
1431
  const response = { statusCode: 200 }
@@ -1607,7 +1691,7 @@ test('cannot set the replySerializer when the server is running', t => {
1607
1691
  const fastify = require('../..')()
1608
1692
  t.teardown(fastify.close.bind(fastify))
1609
1693
 
1610
- fastify.listen(err => {
1694
+ fastify.listen({ port: 0 }, err => {
1611
1695
  t.error(err)
1612
1696
  try {
1613
1697
  fastify.setReplySerializer(() => {})
@@ -1727,3 +1811,160 @@ test('reply.then', t => {
1727
1811
  response.destroy(_err)
1728
1812
  })
1729
1813
  })
1814
+
1815
+ test('reply.sent should read from response.writableEnded if it is defined', t => {
1816
+ t.plan(1)
1817
+
1818
+ const reply = new Reply({ writableEnded: true }, {}, {})
1819
+
1820
+ t.equal(reply.sent, true)
1821
+ })
1822
+
1823
+ test('redirect to an invalid URL should not crash the server', async t => {
1824
+ const fastify = require('../..')()
1825
+ fastify.route({
1826
+ method: 'GET',
1827
+ url: '/redirect',
1828
+ handler: (req, reply) => {
1829
+ reply.log.warn = function mockWarn (obj, message) {
1830
+ t.equal(message, 'Invalid character in header content ["location"]')
1831
+ }
1832
+
1833
+ switch (req.query.useCase) {
1834
+ case '1':
1835
+ reply.redirect('/?key=a’b')
1836
+ break
1837
+
1838
+ case '2':
1839
+ reply.redirect(encodeURI('/?key=a’b'))
1840
+ break
1841
+
1842
+ default:
1843
+ reply.redirect('/?key=ab')
1844
+ break
1845
+ }
1846
+ }
1847
+ })
1848
+
1849
+ await fastify.listen({ port: 0 })
1850
+
1851
+ {
1852
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=1`)
1853
+ t.equal(response.statusCode, 500)
1854
+ t.same(JSON.parse(body), {
1855
+ statusCode: 500,
1856
+ code: 'ERR_INVALID_CHAR',
1857
+ error: 'Internal Server Error',
1858
+ message: 'Invalid character in header content ["location"]'
1859
+ })
1860
+ }
1861
+ {
1862
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=2`)
1863
+ t.equal(response.statusCode, 302)
1864
+ t.equal(response.headers.location, '/?key=a%E2%80%99b')
1865
+ }
1866
+
1867
+ {
1868
+ const { response } = await doGet(`http://localhost:${fastify.server.address().port}/redirect?useCase=3`)
1869
+ t.equal(response.statusCode, 302)
1870
+ t.equal(response.headers.location, '/?key=ab')
1871
+ }
1872
+
1873
+ await fastify.close()
1874
+ })
1875
+
1876
+ test('invalid response headers should not crash the server', async t => {
1877
+ const fastify = require('../..')()
1878
+ fastify.route({
1879
+ method: 'GET',
1880
+ url: '/bad-headers',
1881
+ handler: (req, reply) => {
1882
+ reply.log.warn = function mockWarn (obj, message) {
1883
+ t.equal(message, 'Invalid character in header content ["smile-encoded"]', 'only the first invalid header is logged')
1884
+ }
1885
+
1886
+ reply.header('foo', '$')
1887
+ reply.header('smile-encoded', '\uD83D\uDE00')
1888
+ reply.header('smile', '😄')
1889
+ reply.header('bar', 'ƒ∂å')
1890
+
1891
+ reply.send({})
1892
+ }
1893
+ })
1894
+
1895
+ await fastify.listen({ port: 0 })
1896
+
1897
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1898
+ t.equal(response.statusCode, 500)
1899
+ t.same(JSON.parse(body), {
1900
+ statusCode: 500,
1901
+ code: 'ERR_INVALID_CHAR',
1902
+ error: 'Internal Server Error',
1903
+ message: 'Invalid character in header content ["smile-encoded"]'
1904
+ })
1905
+
1906
+ await fastify.close()
1907
+ })
1908
+
1909
+ test('invalid response headers when sending back an error', async t => {
1910
+ const fastify = require('../..')()
1911
+ fastify.route({
1912
+ method: 'GET',
1913
+ url: '/bad-headers',
1914
+ handler: (req, reply) => {
1915
+ reply.log.warn = function mockWarn (obj, message) {
1916
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1917
+ }
1918
+
1919
+ reply.header('smile', '😄')
1920
+ reply.send(new Error('user land error'))
1921
+ }
1922
+ })
1923
+
1924
+ await fastify.listen({ port: 0 })
1925
+
1926
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1927
+ t.equal(response.statusCode, 500)
1928
+ t.same(JSON.parse(body), {
1929
+ statusCode: 500,
1930
+ code: 'ERR_INVALID_CHAR',
1931
+ error: 'Internal Server Error',
1932
+ message: 'Invalid character in header content ["smile"]'
1933
+ })
1934
+
1935
+ await fastify.close()
1936
+ })
1937
+
1938
+ test('invalid response headers and custom error handler', async t => {
1939
+ const fastify = require('../..')()
1940
+ fastify.route({
1941
+ method: 'GET',
1942
+ url: '/bad-headers',
1943
+ handler: (req, reply) => {
1944
+ reply.log.warn = function mockWarn (obj, message) {
1945
+ t.equal(message, 'Invalid character in header content ["smile"]', 'only the first invalid header is logged')
1946
+ }
1947
+
1948
+ reply.header('smile', '😄')
1949
+ reply.send(new Error('user land error'))
1950
+ }
1951
+ })
1952
+
1953
+ fastify.setErrorHandler(function (error, request, reply) {
1954
+ t.equal(error.message, 'user land error', 'custom error handler receives the error')
1955
+ reply.status(500).send({ ops: true })
1956
+ })
1957
+
1958
+ await fastify.listen({ port: 0 })
1959
+
1960
+ const { response, body } = await doGet(`http://localhost:${fastify.server.address().port}/bad-headers`)
1961
+ t.equal(response.statusCode, 500)
1962
+ t.same(JSON.parse(body), {
1963
+ statusCode: 500,
1964
+ code: 'ERR_INVALID_CHAR',
1965
+ error: 'Internal Server Error',
1966
+ message: 'Invalid character in header content ["smile"]'
1967
+ })
1968
+
1969
+ await fastify.close()
1970
+ })
@@ -4,8 +4,9 @@ const { test } = require('tap')
4
4
 
5
5
  const Request = require('../../lib/request')
6
6
 
7
+ process.removeAllListeners('warning')
8
+
7
9
  test('Regular request', t => {
8
- t.plan(15)
9
10
  const headers = {
10
11
  host: 'hostname'
11
12
  }
@@ -15,22 +16,27 @@ test('Regular request', t => {
15
16
  socket: { remoteAddress: 'ip' },
16
17
  headers
17
18
  }
19
+ req.connection = req.socket
18
20
  const request = new Request('id', 'params', req, 'query', 'log')
19
21
  t.type(request, Request)
20
22
  t.equal(request.id, 'id')
21
23
  t.equal(request.params, 'params')
22
- t.same(request.raw, req)
24
+ t.equal(request.raw, req)
23
25
  t.equal(request.query, 'query')
24
26
  t.equal(request.headers, headers)
25
27
  t.equal(request.log, 'log')
26
28
  t.equal(request.ip, 'ip')
27
29
  t.equal(request.ips, undefined)
28
30
  t.equal(request.hostname, 'hostname')
29
- t.equal(request.body, null)
31
+ t.equal(request.body, undefined)
30
32
  t.equal(request.method, 'GET')
31
33
  t.equal(request.url, '/')
34
+ t.equal(request.socket, req.socket)
32
35
  t.equal(request.protocol, 'http')
33
- t.same(request.socket, req.socket)
36
+
37
+ // This will be removed, it's deprecated
38
+ t.equal(request.connection, req.connection)
39
+ t.end()
34
40
  })
35
41
 
36
42
  test('Regular request - hostname from authority', t => {
@@ -92,11 +98,11 @@ test('Request with trust proxy', t => {
92
98
  t.equal(request.ip, '2.2.2.2')
93
99
  t.same(request.ips, ['ip', '1.1.1.1', '2.2.2.2'])
94
100
  t.equal(request.hostname, 'example.com')
95
- t.equal(request.body, null)
101
+ t.equal(request.body, undefined)
96
102
  t.equal(request.method, 'GET')
97
103
  t.equal(request.url, '/')
104
+ t.equal(request.socket, req.socket)
98
105
  t.equal(request.protocol, 'http')
99
- t.same(request.socket, req.socket)
100
106
  })
101
107
 
102
108
  test('Request with trust proxy, encrypted', t => {
@@ -236,7 +242,7 @@ test('Request with undefined socket', t => {
236
242
  t.equal(request.ip, undefined)
237
243
  t.equal(request.ips, undefined)
238
244
  t.equal(request.hostname, 'hostname')
239
- t.equal(request.body, null)
245
+ t.same(request.body, null)
240
246
  t.equal(request.method, 'GET')
241
247
  t.equal(request.url, '/')
242
248
  t.equal(request.protocol, undefined)
@@ -0,0 +1,88 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('tap')
4
+ const proxyquire = require('proxyquire')
5
+
6
+ const Fastify = require('../../fastify')
7
+ const createServer = require('../../lib/server')
8
+
9
+ const handler = (req, res) => {
10
+ res.writeHead(200, { 'Content-Type': 'application/json' })
11
+ res.end(JSON.stringify({ data: 'Hello World!' }))
12
+ }
13
+
14
+ test('start listening', async t => {
15
+ const { server, listen } = createServer({}, handler)
16
+ await listen.call(Fastify(), { port: 0, host: 'localhost' })
17
+ server.close()
18
+ t.pass('server started')
19
+ })
20
+
21
+ test('DNS errors does not stop the main server on localhost - promise interface', async t => {
22
+ const createServer = proxyquire('../../lib/server', {
23
+ dns: {
24
+ lookup: (hostname, options, cb) => {
25
+ cb(new Error('DNS error'))
26
+ }
27
+ }
28
+ })
29
+ const { server, listen } = createServer({}, handler)
30
+ await listen.call(Fastify(), { port: 0, host: 'localhost' })
31
+ server.close()
32
+ t.pass('server started')
33
+ })
34
+
35
+ test('DNS errors does not stop the main server on localhost - callback interface', t => {
36
+ t.plan(2)
37
+ const createServer = proxyquire('../../lib/server', {
38
+ dns: {
39
+ lookup: (hostname, options, cb) => {
40
+ cb(new Error('DNS error'))
41
+ }
42
+ }
43
+ })
44
+ const { server, listen } = createServer({}, handler)
45
+ listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => {
46
+ t.error(err)
47
+ server.close()
48
+ t.pass('server started')
49
+ })
50
+ })
51
+
52
+ test('DNS returns empty binding', t => {
53
+ t.plan(2)
54
+ const createServer = proxyquire('../../lib/server', {
55
+ dns: {
56
+ lookup: (hostname, options, cb) => {
57
+ cb(null, [])
58
+ }
59
+ }
60
+ })
61
+ const { server, listen } = createServer({}, handler)
62
+ listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => {
63
+ t.error(err)
64
+ server.close()
65
+ t.pass('server started')
66
+ })
67
+ })
68
+
69
+ test('DNS returns more than two binding', t => {
70
+ t.plan(2)
71
+ const createServer = proxyquire('../../lib/server', {
72
+ dns: {
73
+ lookup: (hostname, options, cb) => {
74
+ cb(null, [
75
+ { address: '::1', family: 6 },
76
+ { address: '127.0.0.1', family: 4 },
77
+ { address: '0.0.0.0', family: 4 }
78
+ ])
79
+ }
80
+ }
81
+ })
82
+ const { server, listen } = createServer({}, handler)
83
+ listen.call(Fastify(), { port: 0, host: 'localhost' }, (err) => {
84
+ t.error(err)
85
+ server.close()
86
+ t.pass('server started')
87
+ })
88
+ })