node-fastify 5.8.3

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 (354) hide show
  1. package/.borp.yaml +3 -0
  2. package/.markdownlint-cli2.yaml +22 -0
  3. package/.prettierignore +1 -0
  4. package/GOVERNANCE.md +4 -0
  5. package/LICENSE +21 -0
  6. package/PROJECT_CHARTER.md +126 -0
  7. package/README.md +423 -0
  8. package/SECURITY.md +220 -0
  9. package/SPONSORS.md +24 -0
  10. package/build/build-error-serializer.js +35 -0
  11. package/build/build-validation.js +169 -0
  12. package/build/sync-version.js +11 -0
  13. package/docs/Guides/Benchmarking.md +60 -0
  14. package/docs/Guides/Database.md +321 -0
  15. package/docs/Guides/Delay-Accepting-Requests.md +608 -0
  16. package/docs/Guides/Detecting-When-Clients-Abort.md +172 -0
  17. package/docs/Guides/Ecosystem.md +726 -0
  18. package/docs/Guides/Fluent-Schema.md +127 -0
  19. package/docs/Guides/Getting-Started.md +620 -0
  20. package/docs/Guides/Index.md +43 -0
  21. package/docs/Guides/Migration-Guide-V3.md +287 -0
  22. package/docs/Guides/Migration-Guide-V4.md +267 -0
  23. package/docs/Guides/Migration-Guide-V5.md +727 -0
  24. package/docs/Guides/Plugins-Guide.md +520 -0
  25. package/docs/Guides/Prototype-Poisoning.md +383 -0
  26. package/docs/Guides/Recommendations.md +378 -0
  27. package/docs/Guides/Serverless.md +604 -0
  28. package/docs/Guides/Style-Guide.md +246 -0
  29. package/docs/Guides/Testing.md +481 -0
  30. package/docs/Guides/Write-Plugin.md +103 -0
  31. package/docs/Guides/Write-Type-Provider.md +34 -0
  32. package/docs/Reference/ContentTypeParser.md +271 -0
  33. package/docs/Reference/Decorators.md +436 -0
  34. package/docs/Reference/Encapsulation.md +194 -0
  35. package/docs/Reference/Errors.md +377 -0
  36. package/docs/Reference/HTTP2.md +94 -0
  37. package/docs/Reference/Hooks.md +958 -0
  38. package/docs/Reference/Index.md +73 -0
  39. package/docs/Reference/LTS.md +86 -0
  40. package/docs/Reference/Lifecycle.md +99 -0
  41. package/docs/Reference/Logging.md +268 -0
  42. package/docs/Reference/Middleware.md +79 -0
  43. package/docs/Reference/Plugins.md +245 -0
  44. package/docs/Reference/Principles.md +73 -0
  45. package/docs/Reference/Reply.md +1001 -0
  46. package/docs/Reference/Request.md +295 -0
  47. package/docs/Reference/Routes.md +802 -0
  48. package/docs/Reference/Server.md +2389 -0
  49. package/docs/Reference/Type-Providers.md +256 -0
  50. package/docs/Reference/TypeScript.md +1729 -0
  51. package/docs/Reference/Validation-and-Serialization.md +1130 -0
  52. package/docs/Reference/Warnings.md +58 -0
  53. package/docs/index.md +24 -0
  54. package/docs/resources/encapsulation_context.drawio +1 -0
  55. package/docs/resources/encapsulation_context.svg +3 -0
  56. package/eslint.config.js +35 -0
  57. package/examples/asyncawait.js +38 -0
  58. package/examples/benchmark/body.json +3 -0
  59. package/examples/benchmark/hooks-benchmark-async-await.js +44 -0
  60. package/examples/benchmark/hooks-benchmark.js +52 -0
  61. package/examples/benchmark/parser.js +47 -0
  62. package/examples/benchmark/simple.js +30 -0
  63. package/examples/benchmark/webstream.js +27 -0
  64. package/examples/hooks.js +91 -0
  65. package/examples/http2.js +39 -0
  66. package/examples/https.js +38 -0
  67. package/examples/parser.js +53 -0
  68. package/examples/plugin.js +12 -0
  69. package/examples/route-prefix.js +38 -0
  70. package/examples/shared-schema.js +38 -0
  71. package/examples/simple-stream.js +20 -0
  72. package/examples/simple.js +32 -0
  73. package/examples/simple.mjs +27 -0
  74. package/examples/typescript-server.ts +79 -0
  75. package/examples/use-plugin.js +29 -0
  76. package/fastify.d.ts +253 -0
  77. package/fastify.js +985 -0
  78. package/integration/server.js +29 -0
  79. package/integration/test.sh +23 -0
  80. package/lib/config-validator.js +1266 -0
  81. package/lib/content-type-parser.js +413 -0
  82. package/lib/content-type.js +160 -0
  83. package/lib/context.js +98 -0
  84. package/lib/decorate.js +152 -0
  85. package/lib/error-handler.js +173 -0
  86. package/lib/error-serializer.js +134 -0
  87. package/lib/error-status.js +14 -0
  88. package/lib/errors.js +516 -0
  89. package/lib/four-oh-four.js +190 -0
  90. package/lib/handle-request.js +195 -0
  91. package/lib/head-route.js +45 -0
  92. package/lib/hooks.js +429 -0
  93. package/lib/initial-config-validation.js +37 -0
  94. package/lib/logger-factory.js +136 -0
  95. package/lib/logger-pino.js +68 -0
  96. package/lib/noop-set.js +10 -0
  97. package/lib/plugin-override.js +90 -0
  98. package/lib/plugin-utils.js +169 -0
  99. package/lib/promise.js +23 -0
  100. package/lib/reply.js +1030 -0
  101. package/lib/req-id-gen-factory.js +52 -0
  102. package/lib/request.js +391 -0
  103. package/lib/route.js +686 -0
  104. package/lib/schema-controller.js +164 -0
  105. package/lib/schemas.js +207 -0
  106. package/lib/server.js +441 -0
  107. package/lib/symbols.js +71 -0
  108. package/lib/validation.js +280 -0
  109. package/lib/warnings.js +57 -0
  110. package/lib/wrap-thenable.js +84 -0
  111. package/package.json +225 -0
  112. package/scripts/validate-ecosystem-links.js +179 -0
  113. package/test/404s.test.js +2035 -0
  114. package/test/500s.test.js +422 -0
  115. package/test/allow-unsafe-regex.test.js +92 -0
  116. package/test/als.test.js +65 -0
  117. package/test/async-await.test.js +705 -0
  118. package/test/async-dispose.test.js +20 -0
  119. package/test/async_hooks.test.js +52 -0
  120. package/test/body-limit.test.js +224 -0
  121. package/test/buffer.test.js +74 -0
  122. package/test/build/error-serializer.test.js +36 -0
  123. package/test/build/version.test.js +14 -0
  124. package/test/build-certificate.js +109 -0
  125. package/test/bundler/README.md +29 -0
  126. package/test/bundler/esbuild/bundler-test.js +32 -0
  127. package/test/bundler/esbuild/package.json +10 -0
  128. package/test/bundler/esbuild/src/fail-plugin-version.js +14 -0
  129. package/test/bundler/esbuild/src/index.js +9 -0
  130. package/test/bundler/webpack/bundler-test.js +32 -0
  131. package/test/bundler/webpack/package.json +11 -0
  132. package/test/bundler/webpack/src/fail-plugin-version.js +14 -0
  133. package/test/bundler/webpack/src/index.js +9 -0
  134. package/test/bundler/webpack/webpack.config.js +15 -0
  135. package/test/case-insensitive.test.js +102 -0
  136. package/test/chainable.test.js +40 -0
  137. package/test/child-logger-factory.test.js +128 -0
  138. package/test/client-timeout.test.js +38 -0
  139. package/test/close-pipelining.test.js +78 -0
  140. package/test/close.test.js +706 -0
  141. package/test/conditional-pino.test.js +47 -0
  142. package/test/connection-timeout.test.js +42 -0
  143. package/test/constrained-routes.test.js +1138 -0
  144. package/test/content-length.test.js +174 -0
  145. package/test/content-parser.test.js +739 -0
  146. package/test/content-type.test.js +181 -0
  147. package/test/context-config.test.js +164 -0
  148. package/test/custom-http-server.test.js +118 -0
  149. package/test/custom-parser-async.test.js +59 -0
  150. package/test/custom-parser.0.test.js +701 -0
  151. package/test/custom-parser.1.test.js +266 -0
  152. package/test/custom-parser.2.test.js +91 -0
  153. package/test/custom-parser.3.test.js +208 -0
  154. package/test/custom-parser.4.test.js +218 -0
  155. package/test/custom-parser.5.test.js +130 -0
  156. package/test/custom-querystring-parser.test.js +129 -0
  157. package/test/decorator.test.js +1330 -0
  158. package/test/delete.test.js +344 -0
  159. package/test/diagnostics-channel/404.test.js +49 -0
  160. package/test/diagnostics-channel/async-delay-request.test.js +65 -0
  161. package/test/diagnostics-channel/async-request.test.js +64 -0
  162. package/test/diagnostics-channel/error-before-handler.test.js +35 -0
  163. package/test/diagnostics-channel/error-request.test.js +53 -0
  164. package/test/diagnostics-channel/error-status.test.js +123 -0
  165. package/test/diagnostics-channel/init.test.js +50 -0
  166. package/test/diagnostics-channel/sync-delay-request.test.js +49 -0
  167. package/test/diagnostics-channel/sync-request-reply.test.js +51 -0
  168. package/test/diagnostics-channel/sync-request.test.js +54 -0
  169. package/test/encapsulated-child-logger-factory.test.js +69 -0
  170. package/test/encapsulated-error-handler.test.js +237 -0
  171. package/test/esm/errorCodes.test.mjs +10 -0
  172. package/test/esm/esm.test.mjs +13 -0
  173. package/test/esm/index.test.js +8 -0
  174. package/test/esm/named-exports.mjs +14 -0
  175. package/test/esm/other.mjs +8 -0
  176. package/test/esm/plugin.mjs +8 -0
  177. package/test/fastify-instance.test.js +300 -0
  178. package/test/find-route.test.js +152 -0
  179. package/test/fluent-schema.test.js +209 -0
  180. package/test/genReqId.test.js +426 -0
  181. package/test/handler-context.test.js +45 -0
  182. package/test/handler-timeout.test.js +367 -0
  183. package/test/has-route.test.js +88 -0
  184. package/test/header-overflow.test.js +55 -0
  185. package/test/helper.js +496 -0
  186. package/test/hooks-async.test.js +1099 -0
  187. package/test/hooks.on-listen.test.js +1162 -0
  188. package/test/hooks.on-ready.test.js +421 -0
  189. package/test/hooks.test.js +3578 -0
  190. package/test/http-methods/copy.test.js +35 -0
  191. package/test/http-methods/custom-http-methods.test.js +114 -0
  192. package/test/http-methods/get.test.js +412 -0
  193. package/test/http-methods/head.test.js +263 -0
  194. package/test/http-methods/lock.test.js +108 -0
  195. package/test/http-methods/mkcalendar.test.js +143 -0
  196. package/test/http-methods/mkcol.test.js +35 -0
  197. package/test/http-methods/move.test.js +42 -0
  198. package/test/http-methods/propfind.test.js +136 -0
  199. package/test/http-methods/proppatch.test.js +105 -0
  200. package/test/http-methods/report.test.js +142 -0
  201. package/test/http-methods/search.test.js +233 -0
  202. package/test/http-methods/trace.test.js +21 -0
  203. package/test/http-methods/unlock.test.js +38 -0
  204. package/test/http2/closing.test.js +270 -0
  205. package/test/http2/constraint.test.js +109 -0
  206. package/test/http2/head.test.js +34 -0
  207. package/test/http2/plain.test.js +68 -0
  208. package/test/http2/secure-with-fallback.test.js +113 -0
  209. package/test/http2/secure.test.js +67 -0
  210. package/test/http2/unknown-http-method.test.js +34 -0
  211. package/test/https/custom-https-server.test.js +58 -0
  212. package/test/https/https.test.js +136 -0
  213. package/test/imports.test.js +17 -0
  214. package/test/inject.test.js +502 -0
  215. package/test/input-validation.js +335 -0
  216. package/test/internals/all.test.js +38 -0
  217. package/test/internals/content-type-parser.test.js +111 -0
  218. package/test/internals/context.test.js +31 -0
  219. package/test/internals/decorator.test.js +156 -0
  220. package/test/internals/errors.test.js +982 -0
  221. package/test/internals/handle-request.test.js +270 -0
  222. package/test/internals/hook-runner.test.js +449 -0
  223. package/test/internals/hooks.test.js +96 -0
  224. package/test/internals/initial-config.test.js +383 -0
  225. package/test/internals/logger.test.js +163 -0
  226. package/test/internals/plugin.test.js +170 -0
  227. package/test/internals/promise.test.js +63 -0
  228. package/test/internals/reply-serialize.test.js +714 -0
  229. package/test/internals/reply.test.js +1920 -0
  230. package/test/internals/req-id-gen-factory.test.js +133 -0
  231. package/test/internals/request-validate.test.js +1402 -0
  232. package/test/internals/request.test.js +506 -0
  233. package/test/internals/schema-controller-perf.test.js +40 -0
  234. package/test/internals/server.test.js +91 -0
  235. package/test/internals/validation.test.js +352 -0
  236. package/test/issue-4959.test.js +118 -0
  237. package/test/keep-alive-timeout.test.js +42 -0
  238. package/test/listen.1.test.js +154 -0
  239. package/test/listen.2.test.js +113 -0
  240. package/test/listen.3.test.js +83 -0
  241. package/test/listen.4.test.js +168 -0
  242. package/test/listen.5.test.js +122 -0
  243. package/test/logger/instantiation.test.js +341 -0
  244. package/test/logger/logger-test-utils.js +47 -0
  245. package/test/logger/logging.test.js +460 -0
  246. package/test/logger/options.test.js +579 -0
  247. package/test/logger/request.test.js +292 -0
  248. package/test/logger/response.test.js +183 -0
  249. package/test/logger/tap-parallel-not-ok +0 -0
  250. package/test/max-requests-per-socket.test.js +113 -0
  251. package/test/middleware.test.js +37 -0
  252. package/test/noop-set.test.js +19 -0
  253. package/test/nullable-validation.test.js +187 -0
  254. package/test/options.error-handler.test.js +5 -0
  255. package/test/options.test.js +5 -0
  256. package/test/output-validation.test.js +140 -0
  257. package/test/patch.error-handler.test.js +5 -0
  258. package/test/patch.test.js +5 -0
  259. package/test/plugin.1.test.js +230 -0
  260. package/test/plugin.2.test.js +314 -0
  261. package/test/plugin.3.test.js +287 -0
  262. package/test/plugin.4.test.js +504 -0
  263. package/test/plugin.helper.js +8 -0
  264. package/test/plugin.name.display.js +10 -0
  265. package/test/post-empty-body.test.js +38 -0
  266. package/test/pretty-print.test.js +366 -0
  267. package/test/promises.test.js +125 -0
  268. package/test/proto-poisoning.test.js +145 -0
  269. package/test/put.error-handler.test.js +5 -0
  270. package/test/put.test.js +5 -0
  271. package/test/register.test.js +184 -0
  272. package/test/reply-code.test.js +148 -0
  273. package/test/reply-early-hints.test.js +100 -0
  274. package/test/reply-error.test.js +815 -0
  275. package/test/reply-trailers.test.js +445 -0
  276. package/test/reply-web-stream-locked.test.js +37 -0
  277. package/test/request-error.test.js +624 -0
  278. package/test/request-header-host.test.js +339 -0
  279. package/test/request-id.test.js +118 -0
  280. package/test/request-timeout.test.js +53 -0
  281. package/test/route-hooks.test.js +635 -0
  282. package/test/route-prefix.test.js +904 -0
  283. package/test/route-shorthand.test.js +48 -0
  284. package/test/route.1.test.js +259 -0
  285. package/test/route.2.test.js +100 -0
  286. package/test/route.3.test.js +213 -0
  287. package/test/route.4.test.js +127 -0
  288. package/test/route.5.test.js +211 -0
  289. package/test/route.6.test.js +306 -0
  290. package/test/route.7.test.js +406 -0
  291. package/test/route.8.test.js +225 -0
  292. package/test/router-options.test.js +1108 -0
  293. package/test/same-shape.test.js +124 -0
  294. package/test/schema-examples.test.js +661 -0
  295. package/test/schema-feature.test.js +2198 -0
  296. package/test/schema-serialization.test.js +1171 -0
  297. package/test/schema-special-usage.test.js +1348 -0
  298. package/test/schema-validation.test.js +1572 -0
  299. package/test/scripts/validate-ecosystem-links.test.js +339 -0
  300. package/test/serialize-response.test.js +186 -0
  301. package/test/server.test.js +347 -0
  302. package/test/set-error-handler.test.js +69 -0
  303. package/test/skip-reply-send.test.js +317 -0
  304. package/test/stream-serializers.test.js +40 -0
  305. package/test/stream.1.test.js +94 -0
  306. package/test/stream.2.test.js +129 -0
  307. package/test/stream.3.test.js +198 -0
  308. package/test/stream.4.test.js +176 -0
  309. package/test/stream.5.test.js +188 -0
  310. package/test/sync-routes.test.js +32 -0
  311. package/test/throw.test.js +359 -0
  312. package/test/toolkit.js +63 -0
  313. package/test/trust-proxy.test.js +162 -0
  314. package/test/type-provider.test.js +22 -0
  315. package/test/types/content-type-parser.test-d.ts +72 -0
  316. package/test/types/decorate-request-reply.test-d.ts +18 -0
  317. package/test/types/dummy-plugin.ts +9 -0
  318. package/test/types/errors.test-d.ts +90 -0
  319. package/test/types/fastify.test-d.ts +352 -0
  320. package/test/types/hooks.test-d.ts +550 -0
  321. package/test/types/import.ts +2 -0
  322. package/test/types/instance.test-d.ts +588 -0
  323. package/test/types/logger.test-d.ts +277 -0
  324. package/test/types/plugin.test-d.ts +97 -0
  325. package/test/types/register.test-d.ts +237 -0
  326. package/test/types/reply.test-d.ts +254 -0
  327. package/test/types/request.test-d.ts +188 -0
  328. package/test/types/route.test-d.ts +553 -0
  329. package/test/types/schema.test-d.ts +135 -0
  330. package/test/types/serverFactory.test-d.ts +37 -0
  331. package/test/types/type-provider.test-d.ts +1213 -0
  332. package/test/types/using.test-d.ts +17 -0
  333. package/test/upgrade.test.js +52 -0
  334. package/test/url-rewriting.test.js +122 -0
  335. package/test/use-semicolon-delimiter.test.js +168 -0
  336. package/test/validation-error-handling.test.js +900 -0
  337. package/test/versioned-routes.test.js +603 -0
  338. package/test/web-api.test.js +616 -0
  339. package/test/wrap-thenable.test.js +30 -0
  340. package/types/content-type-parser.d.ts +75 -0
  341. package/types/context.d.ts +22 -0
  342. package/types/errors.d.ts +92 -0
  343. package/types/hooks.d.ts +875 -0
  344. package/types/instance.d.ts +609 -0
  345. package/types/logger.d.ts +107 -0
  346. package/types/plugin.d.ts +44 -0
  347. package/types/register.d.ts +42 -0
  348. package/types/reply.d.ts +81 -0
  349. package/types/request.d.ts +95 -0
  350. package/types/route.d.ts +199 -0
  351. package/types/schema.d.ts +61 -0
  352. package/types/server-factory.d.ts +19 -0
  353. package/types/type-provider.d.ts +130 -0
  354. package/types/utils.d.ts +98 -0
@@ -0,0 +1,198 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const split = require('split2')
5
+ const Fastify = require('..')
6
+
7
+ test('Destroying streams prematurely', (t, testDone) => {
8
+ t.plan(6)
9
+
10
+ let fastify = null
11
+ const logStream = split(JSON.parse)
12
+ try {
13
+ fastify = Fastify({
14
+ logger: {
15
+ stream: logStream,
16
+ level: 'info'
17
+ }
18
+ })
19
+ } catch (e) {
20
+ t.assert.fail()
21
+ }
22
+ const stream = require('node:stream')
23
+ const http = require('node:http')
24
+
25
+ // Test that "premature close" errors are logged with level warn
26
+ logStream.on('data', line => {
27
+ if (line.res) {
28
+ t.assert.strictEqual(line.msg, 'stream closed prematurely')
29
+ t.assert.strictEqual(line.level, 30)
30
+ testDone()
31
+ }
32
+ })
33
+
34
+ fastify.get('/', function (request, reply) {
35
+ t.assert.ok('Received request')
36
+
37
+ let sent = false
38
+ const reallyLongStream = new stream.Readable({
39
+ read: function () {
40
+ if (!sent) {
41
+ this.push(Buffer.from('hello\n'))
42
+ }
43
+ sent = true
44
+ }
45
+ })
46
+
47
+ reply.send(reallyLongStream)
48
+ })
49
+
50
+ fastify.listen({ port: 0 }, err => {
51
+ t.assert.ifError(err)
52
+ t.after(() => fastify.close())
53
+
54
+ const port = fastify.server.address().port
55
+
56
+ http.get(`http://localhost:${port}`, function (response) {
57
+ t.assert.strictEqual(response.statusCode, 200)
58
+ response.on('readable', function () {
59
+ response.destroy()
60
+ })
61
+
62
+ // Node bug? Node never emits 'close' here.
63
+ response.on('aborted', function () {
64
+ t.assert.ok('Response closed')
65
+ })
66
+ })
67
+ })
68
+ })
69
+
70
+ test('Destroying streams prematurely should call close method', (t, testDone) => {
71
+ t.plan(7)
72
+
73
+ let fastify = null
74
+ const logStream = split(JSON.parse)
75
+ try {
76
+ fastify = Fastify({
77
+ logger: {
78
+ stream: logStream,
79
+ level: 'info'
80
+ }
81
+ })
82
+ } catch (e) {
83
+ t.assert.fail()
84
+ }
85
+ const stream = require('node:stream')
86
+ const http = require('node:http')
87
+
88
+ // Test that "premature close" errors are logged with level warn
89
+ logStream.on('data', line => {
90
+ if (line.res) {
91
+ t.assert.strictEqual(line.msg, 'stream closed prematurely')
92
+ t.assert.strictEqual(line.level, 30)
93
+ }
94
+ })
95
+
96
+ fastify.get('/', function (request, reply) {
97
+ t.assert.ok('Received request')
98
+
99
+ let sent = false
100
+ const reallyLongStream = new stream.Readable({
101
+ read: function () {
102
+ if (!sent) {
103
+ this.push(Buffer.from('hello\n'))
104
+ }
105
+ sent = true
106
+ }
107
+ })
108
+ reallyLongStream.destroy = undefined
109
+ reallyLongStream.close = () => {
110
+ t.assert.ok('called')
111
+ testDone()
112
+ }
113
+ reply.send(reallyLongStream)
114
+ })
115
+
116
+ fastify.listen({ port: 0 }, err => {
117
+ t.assert.ifError(err)
118
+ t.after(() => { fastify.close() })
119
+
120
+ const port = fastify.server.address().port
121
+
122
+ http.get(`http://localhost:${port}`, function (response) {
123
+ t.assert.strictEqual(response.statusCode, 200)
124
+ response.on('readable', function () {
125
+ response.destroy()
126
+ })
127
+ // Node bug? Node never emits 'close' here.
128
+ response.on('aborted', function () {
129
+ t.assert.ok('Response closed')
130
+ })
131
+ })
132
+ })
133
+ })
134
+
135
+ test('Destroying streams prematurely should call close method when destroy is not a function', (t, testDone) => {
136
+ t.plan(7)
137
+
138
+ let fastify = null
139
+ const logStream = split(JSON.parse)
140
+ try {
141
+ fastify = Fastify({
142
+ logger: {
143
+ stream: logStream,
144
+ level: 'info'
145
+ }
146
+ })
147
+ } catch (e) {
148
+ t.assert.fail()
149
+ }
150
+ const stream = require('node:stream')
151
+ const http = require('node:http')
152
+
153
+ // Test that "premature close" errors are logged with level warn
154
+ logStream.on('data', line => {
155
+ if (line.res) {
156
+ t.assert.strictEqual(line.msg, 'stream closed prematurely')
157
+ t.assert.strictEqual(line.level, 30)
158
+ }
159
+ })
160
+
161
+ fastify.get('/', function (request, reply) {
162
+ t.assert.ok('Received request')
163
+
164
+ let sent = false
165
+ const reallyLongStream = new stream.Readable({
166
+ read: function () {
167
+ if (!sent) {
168
+ this.push(Buffer.from('hello\n'))
169
+ }
170
+ sent = true
171
+ }
172
+ })
173
+ reallyLongStream.destroy = true
174
+ reallyLongStream.close = () => {
175
+ t.assert.ok('called')
176
+ testDone()
177
+ }
178
+ reply.send(reallyLongStream)
179
+ })
180
+
181
+ fastify.listen({ port: 0 }, err => {
182
+ t.assert.ifError(err)
183
+ t.after(() => { fastify.close() })
184
+
185
+ const port = fastify.server.address().port
186
+
187
+ http.get(`http://localhost:${port}`, function (response) {
188
+ t.assert.strictEqual(response.statusCode, 200)
189
+ response.on('readable', function () {
190
+ response.destroy()
191
+ })
192
+ // Node bug? Node never emits 'close' here.
193
+ response.on('aborted', function () {
194
+ t.assert.ok('Response closed')
195
+ })
196
+ })
197
+ })
198
+ })
@@ -0,0 +1,176 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const errors = require('http-errors')
5
+ const JSONStream = require('JSONStream')
6
+ const Readable = require('node:stream').Readable
7
+ const split = require('split2')
8
+ const Fastify = require('..')
9
+ const { kDisableRequestLogging } = require('../lib/symbols.js')
10
+
11
+ test('Destroying streams prematurely should call abort method', (t, testDone) => {
12
+ t.plan(7)
13
+
14
+ let fastify = null
15
+ const logStream = split(JSON.parse)
16
+ try {
17
+ fastify = Fastify({
18
+ logger: {
19
+ stream: logStream,
20
+ level: 'info'
21
+ }
22
+ })
23
+ } catch (e) {
24
+ t.assert.fail()
25
+ }
26
+ const stream = require('node:stream')
27
+ const http = require('node:http')
28
+
29
+ // Test that "premature close" errors are logged with level warn
30
+ logStream.on('data', line => {
31
+ if (line.res) {
32
+ t.assert.strictEqual(line.msg, 'stream closed prematurely')
33
+ t.assert.strictEqual(line.level, 30)
34
+ testDone()
35
+ }
36
+ })
37
+
38
+ fastify.get('/', function (request, reply) {
39
+ t.assert.ok('Received request')
40
+
41
+ let sent = false
42
+ const reallyLongStream = new stream.Readable({
43
+ read: function () {
44
+ if (!sent) {
45
+ this.push(Buffer.from('hello\n'))
46
+ }
47
+ sent = true
48
+ }
49
+ })
50
+ reallyLongStream.destroy = undefined
51
+ reallyLongStream.close = undefined
52
+ reallyLongStream.abort = () => t.assert.ok('called')
53
+ reply.send(reallyLongStream)
54
+ })
55
+
56
+ fastify.listen({ port: 0 }, err => {
57
+ t.assert.ifError(err)
58
+ t.after(() => { fastify.close() })
59
+
60
+ const port = fastify.server.address().port
61
+
62
+ http.get(`http://localhost:${port}`, function (response) {
63
+ t.assert.strictEqual(response.statusCode, 200)
64
+ response.on('readable', function () {
65
+ response.destroy()
66
+ })
67
+ // Node bug? Node never emits 'close' here.
68
+ response.on('aborted', function () {
69
+ t.assert.ok('Response closed')
70
+ })
71
+ })
72
+ })
73
+ })
74
+
75
+ test('Destroying streams prematurely, log is disabled', (t, testDone) => {
76
+ t.plan(4)
77
+
78
+ let fastify = null
79
+ try {
80
+ fastify = Fastify({
81
+ logger: false
82
+ })
83
+ } catch (e) {
84
+ t.assert.fail()
85
+ }
86
+ const stream = require('node:stream')
87
+ const http = require('node:http')
88
+
89
+ fastify.get('/', function (request, reply) {
90
+ reply.log[kDisableRequestLogging] = true
91
+
92
+ let sent = false
93
+ const reallyLongStream = new stream.Readable({
94
+ read: function () {
95
+ if (!sent) {
96
+ this.push(Buffer.from('hello\n'))
97
+ }
98
+ sent = true
99
+ }
100
+ })
101
+ reallyLongStream.destroy = true
102
+ reallyLongStream.close = () => {
103
+ t.assert.ok('called')
104
+ testDone()
105
+ }
106
+ reply.send(reallyLongStream)
107
+ })
108
+
109
+ fastify.listen({ port: 0 }, err => {
110
+ t.assert.ifError(err)
111
+ t.after(() => { fastify.close() })
112
+
113
+ const port = fastify.server.address().port
114
+
115
+ http.get(`http://localhost:${port}`, function (response) {
116
+ t.assert.strictEqual(response.statusCode, 200)
117
+ response.on('readable', function () {
118
+ response.destroy()
119
+ })
120
+ // Node bug? Node never emits 'close' here.
121
+ response.on('aborted', function () {
122
+ t.assert.ok('Response closed')
123
+ })
124
+ })
125
+ })
126
+ })
127
+
128
+ test('should respond with a stream1', async (t) => {
129
+ t.plan(4)
130
+ const fastify = Fastify()
131
+
132
+ fastify.get('/', function (req, reply) {
133
+ const stream = JSONStream.stringify()
134
+ reply.code(200).type('application/json').send(stream)
135
+ stream.write({ hello: 'world' })
136
+ stream.end({ a: 42 })
137
+ })
138
+
139
+ const fastifyServer = await fastify.listen({ port: 0 })
140
+ t.after(() => fastify.close())
141
+
142
+ const response = await fetch(fastifyServer)
143
+ t.assert.ok(response.ok)
144
+ t.assert.strictEqual(response.headers.get('content-type'), 'application/json')
145
+ t.assert.strictEqual(response.status, 200)
146
+ const body = await response.text()
147
+ t.assert.deepStrictEqual(JSON.parse(body), [{ hello: 'world' }, { a: 42 }])
148
+ })
149
+
150
+ test('return a 404 if the stream emits a 404 error', async (t) => {
151
+ t.plan(4)
152
+
153
+ const fastify = Fastify()
154
+
155
+ fastify.get('/', function (request, reply) {
156
+ t.assert.ok('Received request')
157
+
158
+ const reallyLongStream = new Readable({
159
+ read: function () {
160
+ setImmediate(() => {
161
+ this.emit('error', new errors.NotFound())
162
+ })
163
+ }
164
+ })
165
+
166
+ reply.send(reallyLongStream)
167
+ })
168
+
169
+ const fastifyServer = await fastify.listen({ port: 0 })
170
+ t.after(() => fastify.close())
171
+
172
+ const response = await fetch(fastifyServer)
173
+ t.assert.ok(!response.ok)
174
+ t.assert.strictEqual(response.headers.get('content-type'), 'application/json; charset=utf-8')
175
+ t.assert.strictEqual(response.status, 404)
176
+ })
@@ -0,0 +1,188 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const proxyquire = require('proxyquire')
5
+ const fs = require('node:fs')
6
+ const Readable = require('node:stream').Readable
7
+ const Fastify = require('..')
8
+
9
+ test('should destroy stream when response is ended', async (t) => {
10
+ t.plan(3)
11
+ const stream = require('node:stream')
12
+ const fastify = Fastify()
13
+
14
+ fastify.get('/error', function (req, reply) {
15
+ const reallyLongStream = new stream.Readable({
16
+ read: function () { },
17
+ destroy: function (err, callback) {
18
+ t.assert.ok('called')
19
+ callback(err)
20
+ }
21
+ })
22
+ reply.code(200).send(reallyLongStream)
23
+ reply.raw.end(Buffer.from('hello\n'))
24
+ })
25
+
26
+ const fastifyServer = await fastify.listen({ port: 0 })
27
+ t.after(() => fastify.close())
28
+
29
+ const response = await fetch(`${fastifyServer}/error`)
30
+ t.assert.ok(response.ok)
31
+ t.assert.strictEqual(response.status, 200)
32
+ })
33
+
34
+ test('should mark reply as sent before pumping the payload stream into response for async route handler', async (t) => {
35
+ t.plan(2)
36
+ t.after(() => fastify.close())
37
+
38
+ const handleRequest = proxyquire('../lib/handle-request', {
39
+ './wrap-thenable': (thenable, reply) => {
40
+ thenable.then(function (payload) {
41
+ t.assert.strictEqual(reply.sent, true)
42
+ })
43
+ }
44
+ })
45
+
46
+ const route = proxyquire('../lib/route', {
47
+ './handle-request': handleRequest
48
+ })
49
+
50
+ const Fastify = proxyquire('..', {
51
+ './lib/route': route
52
+ })
53
+
54
+ const fastify = Fastify()
55
+
56
+ fastify.get('/', async function (req, reply) {
57
+ const stream = fs.createReadStream(__filename, 'utf8')
58
+ return reply.code(200).send(stream)
59
+ })
60
+
61
+ const res = await fastify.inject({
62
+ url: '/',
63
+ method: 'GET'
64
+ })
65
+ t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8'))
66
+ })
67
+
68
+ test('reply.send handles aborted requests', (t, done) => {
69
+ t.plan(2)
70
+
71
+ const spyLogger = {
72
+ level: 'error',
73
+ fatal: () => { },
74
+ error: () => {
75
+ t.assert.fail('should not log an error')
76
+ },
77
+ warn: () => { },
78
+ info: () => { },
79
+ debug: () => { },
80
+ trace: () => { },
81
+ child: () => { return spyLogger }
82
+ }
83
+ const fastify = Fastify({
84
+ loggerInstance: spyLogger
85
+ })
86
+
87
+ fastify.get('/', (req, reply) => {
88
+ setTimeout(() => {
89
+ const stream = new Readable({
90
+ read: function () {
91
+ this.push(null)
92
+ }
93
+ })
94
+ reply.send(stream)
95
+ }, 6)
96
+ })
97
+
98
+ fastify.listen({ port: 0 }, err => {
99
+ t.assert.ifError(err)
100
+ t.after(() => fastify.close())
101
+
102
+ const port = fastify.server.address().port
103
+ const http = require('node:http')
104
+ const req = http.get(`http://localhost:${port}`)
105
+ .on('error', (err) => {
106
+ t.assert.strictEqual(err.code, 'ECONNRESET')
107
+ done()
108
+ })
109
+
110
+ setTimeout(() => {
111
+ req.destroy()
112
+ }, 1)
113
+ })
114
+ })
115
+
116
+ test('request terminated should not crash fastify', (t, done) => {
117
+ t.plan(10)
118
+
119
+ const spyLogger = {
120
+ level: 'error',
121
+ fatal: () => { },
122
+ error: () => {
123
+ t.assert.fail('should not log an error')
124
+ },
125
+ warn: () => { },
126
+ info: () => { },
127
+ debug: () => { },
128
+ trace: () => { },
129
+ child: () => { return spyLogger }
130
+ }
131
+ const fastify = Fastify({
132
+ loggerInstance: spyLogger
133
+ })
134
+
135
+ fastify.get('/', async (req, reply) => {
136
+ const stream = new Readable()
137
+ stream._read = () => { }
138
+ reply.header('content-type', 'text/html; charset=utf-8')
139
+ reply.header('transfer-encoding', 'chunked')
140
+ stream.push('<h1>HTML</h1>')
141
+
142
+ reply.send(stream)
143
+
144
+ await new Promise((resolve) => { setTimeout(resolve, 100).unref() })
145
+
146
+ stream.push('<h1>should display on second stream</h1>')
147
+ stream.push(null)
148
+ return reply
149
+ })
150
+
151
+ fastify.listen({ port: 0 }, err => {
152
+ t.assert.ifError(err)
153
+ t.after(() => fastify.close())
154
+
155
+ const port = fastify.server.address().port
156
+ const http = require('node:http')
157
+ const req = http.get(`http://localhost:${port}`, function (res) {
158
+ const { statusCode, headers } = res
159
+ t.assert.strictEqual(statusCode, 200)
160
+ t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8')
161
+ t.assert.strictEqual(headers['transfer-encoding'], 'chunked')
162
+ res.on('data', function (chunk) {
163
+ t.assert.strictEqual(chunk.toString(), '<h1>HTML</h1>')
164
+ })
165
+
166
+ setTimeout(() => {
167
+ req.destroy()
168
+
169
+ // the server is not crash, we can connect it
170
+ http.get(`http://localhost:${port}`, function (res) {
171
+ const { statusCode, headers } = res
172
+ t.assert.strictEqual(statusCode, 200)
173
+ t.assert.strictEqual(headers['content-type'], 'text/html; charset=utf-8')
174
+ t.assert.strictEqual(headers['transfer-encoding'], 'chunked')
175
+ let payload = ''
176
+ res.on('data', function (chunk) {
177
+ payload += chunk.toString()
178
+ })
179
+ res.on('end', function () {
180
+ t.assert.strictEqual(payload, '<h1>HTML</h1><h1>should display on second stream</h1>')
181
+ t.assert.ok('should end properly')
182
+ done()
183
+ })
184
+ })
185
+ }, 1)
186
+ })
187
+ })
188
+ })
@@ -0,0 +1,32 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+
6
+ test('sync route', async t => {
7
+ const fastify = Fastify()
8
+ t.after(() => fastify.close())
9
+ fastify.get('/', () => 'hello world')
10
+ const res = await fastify.inject('/')
11
+ t.assert.strictEqual(res.statusCode, 200)
12
+ t.assert.strictEqual(res.body, 'hello world')
13
+ })
14
+
15
+ test('sync route return null', async t => {
16
+ const fastify = Fastify()
17
+ t.after(() => fastify.close())
18
+ fastify.get('/', () => null)
19
+ const res = await fastify.inject('/')
20
+ t.assert.strictEqual(res.statusCode, 200)
21
+ t.assert.strictEqual(res.body, 'null')
22
+ })
23
+
24
+ test('sync route, error', async t => {
25
+ const fastify = Fastify()
26
+ t.after(() => fastify.close())
27
+ fastify.get('/', () => {
28
+ throw new Error('kaboom')
29
+ })
30
+ const res = await fastify.inject('/')
31
+ t.assert.strictEqual(res.statusCode, 500)
32
+ })