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,317 @@
1
+ 'use strict'
2
+
3
+ const { test, describe } = require('node:test')
4
+ const split = require('split2')
5
+ const net = require('node:net')
6
+ const Fastify = require('../fastify')
7
+
8
+ process.removeAllListeners('warning')
9
+
10
+ const lifecycleHooks = [
11
+ 'onRequest',
12
+ 'preParsing',
13
+ 'preValidation',
14
+ 'preHandler',
15
+ 'preSerialization',
16
+ 'onSend',
17
+ 'onTimeout',
18
+ 'onResponse',
19
+ 'onError'
20
+ ]
21
+
22
+ test('skip automatic reply.send() with reply.hijack and a body', async (t) => {
23
+ const stream = split(JSON.parse)
24
+ const app = Fastify({
25
+ logger: {
26
+ stream
27
+ }
28
+ })
29
+
30
+ stream.on('data', (line) => {
31
+ t.assert.notStrictEqual(line.level, 40) // there are no errors
32
+ t.assert.notStrictEqual(line.level, 50) // there are no errors
33
+ })
34
+
35
+ app.get('/', (req, reply) => {
36
+ reply.hijack()
37
+ reply.raw.end('hello world')
38
+
39
+ return Promise.resolve('this will be skipped')
40
+ })
41
+
42
+ await app.inject({
43
+ method: 'GET',
44
+ url: '/'
45
+ }).then((res) => {
46
+ t.assert.strictEqual(res.statusCode, 200)
47
+ t.assert.strictEqual(res.body, 'hello world')
48
+ })
49
+ })
50
+
51
+ test('skip automatic reply.send() with reply.hijack and no body', async (t) => {
52
+ const stream = split(JSON.parse)
53
+ const app = Fastify({
54
+ logger: {
55
+ stream
56
+ }
57
+ })
58
+
59
+ stream.on('data', (line) => {
60
+ t.assert.notStrictEqual(line.level, 40) // there are no error
61
+ t.assert.notStrictEqual(line.level, 50) // there are no error
62
+ })
63
+
64
+ app.get('/', (req, reply) => {
65
+ reply.hijack()
66
+ reply.raw.end('hello world')
67
+
68
+ return Promise.resolve()
69
+ })
70
+
71
+ await app.inject({
72
+ method: 'GET',
73
+ url: '/'
74
+ }).then((res) => {
75
+ t.assert.strictEqual(res.statusCode, 200)
76
+ t.assert.strictEqual(res.body, 'hello world')
77
+ })
78
+ })
79
+
80
+ test('skip automatic reply.send() with reply.hijack and an error', async (t) => {
81
+ const stream = split(JSON.parse)
82
+ const app = Fastify({
83
+ logger: {
84
+ stream
85
+ }
86
+ })
87
+
88
+ let errorSeen = false
89
+
90
+ stream.on('data', (line) => {
91
+ if (line.level === 50) {
92
+ errorSeen = true
93
+ t.assert.strictEqual(line.err.message, 'kaboom')
94
+ t.assert.strictEqual(line.msg, 'Promise errored, but reply.sent = true was set')
95
+ }
96
+ })
97
+
98
+ app.get('/', (req, reply) => {
99
+ reply.hijack()
100
+ reply.raw.end('hello world')
101
+
102
+ return Promise.reject(new Error('kaboom'))
103
+ })
104
+
105
+ await app.inject({
106
+ method: 'GET',
107
+ url: '/'
108
+ }).then((res) => {
109
+ t.assert.strictEqual(errorSeen, true)
110
+ t.assert.strictEqual(res.statusCode, 200)
111
+ t.assert.strictEqual(res.body, 'hello world')
112
+ })
113
+ })
114
+
115
+ function testHandlerOrBeforeHandlerHook (test, hookOrHandler) {
116
+ const idx = hookOrHandler === 'handler' ? lifecycleHooks.indexOf('preHandler') : lifecycleHooks.indexOf(hookOrHandler)
117
+ const previousHooks = lifecycleHooks.slice(0, idx)
118
+ const nextHooks = lifecycleHooks.slice(idx + 1)
119
+
120
+ describe(`Hijacking inside ${hookOrHandler} skips all the following hooks and handler execution`, () => {
121
+ test('Sending a response using reply.raw => onResponse hook is called', async (t) => {
122
+ const stream = split(JSON.parse)
123
+ const app = Fastify({
124
+ logger: {
125
+ stream
126
+ }
127
+ })
128
+
129
+ stream.on('data', (line) => {
130
+ t.assert.notStrictEqual(line.level, 40) // there are no errors
131
+ t.assert.notStrictEqual(line.level, 50) // there are no errors
132
+ })
133
+
134
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)))
135
+
136
+ if (hookOrHandler === 'handler') {
137
+ app.get('/', (req, reply) => {
138
+ reply.hijack()
139
+ reply.raw.end(`hello from ${hookOrHandler}`)
140
+ })
141
+ } else {
142
+ app.addHook(hookOrHandler, async (req, reply) => {
143
+ reply.hijack()
144
+ reply.raw.end(`hello from ${hookOrHandler}`)
145
+ })
146
+ app.get('/', (req, reply) => t.assert.fail('Handler should not be called'))
147
+ }
148
+
149
+ nextHooks.forEach(h => {
150
+ if (h === 'onResponse') {
151
+ app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`))
152
+ } else {
153
+ app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`))
154
+ }
155
+ })
156
+
157
+ await app.inject({
158
+ method: 'GET',
159
+ url: '/'
160
+ }).then((res) => {
161
+ t.assert.strictEqual(res.statusCode, 200)
162
+ t.assert.strictEqual(res.body, `hello from ${hookOrHandler}`)
163
+ })
164
+ })
165
+
166
+ test('Sending a response using req.socket => onResponse not called', (t, testDone) => {
167
+ const stream = split(JSON.parse)
168
+ const app = Fastify({
169
+ logger: {
170
+ stream
171
+ }
172
+ })
173
+ t.after(() => app.close())
174
+
175
+ stream.on('data', (line) => {
176
+ t.assert.notStrictEqual(line.level, 40) // there are no errors
177
+ t.assert.notStrictEqual(line.level, 50) // there are no errors
178
+ })
179
+
180
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)))
181
+
182
+ if (hookOrHandler === 'handler') {
183
+ app.get('/', (req, reply) => {
184
+ reply.hijack()
185
+ req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
186
+ req.socket.write(`hello from ${hookOrHandler}`)
187
+ req.socket.end()
188
+ })
189
+ } else {
190
+ app.addHook(hookOrHandler, async (req, reply) => {
191
+ reply.hijack()
192
+ req.socket.write('HTTP/1.1 200 OK\r\n\r\n')
193
+ req.socket.write(`hello from ${hookOrHandler}`)
194
+ req.socket.end()
195
+ })
196
+ app.get('/', (req, reply) => t.assert.fail('Handler should not be called'))
197
+ }
198
+
199
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`)))
200
+
201
+ app.listen({ port: 0 }, err => {
202
+ t.assert.ifError(err)
203
+ const client = net.createConnection({ port: (app.server.address()).port }, () => {
204
+ client.write('GET / HTTP/1.1\r\nHost: fastify.test\r\n\r\n')
205
+
206
+ let chunks = ''
207
+ client.setEncoding('utf8')
208
+ client.on('data', data => {
209
+ chunks += data
210
+ })
211
+
212
+ client.on('end', function () {
213
+ t.assert.match(chunks, new RegExp(`hello from ${hookOrHandler}`, 'i'))
214
+ testDone()
215
+ })
216
+ })
217
+ })
218
+ })
219
+
220
+ test('Throwing an error does not trigger any hooks', async (t) => {
221
+ const stream = split(JSON.parse)
222
+ const app = Fastify({
223
+ logger: {
224
+ stream
225
+ }
226
+ })
227
+ t.after(() => app.close())
228
+
229
+ let errorSeen = false
230
+ stream.on('data', (line) => {
231
+ if (hookOrHandler === 'handler') {
232
+ if (line.level === 40) {
233
+ errorSeen = true
234
+ t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
235
+ }
236
+ } else {
237
+ t.assert.notStrictEqual(line.level, 40) // there are no errors
238
+ t.assert.notStrictEqual(line.level, 50) // there are no errors
239
+ }
240
+ })
241
+
242
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)))
243
+
244
+ if (hookOrHandler === 'handler') {
245
+ app.get('/', (req, reply) => {
246
+ reply.hijack()
247
+ throw new Error('This will be skipped')
248
+ })
249
+ } else {
250
+ app.addHook(hookOrHandler, async (req, reply) => {
251
+ reply.hijack()
252
+ throw new Error('This will be skipped')
253
+ })
254
+ app.get('/', (req, reply) => t.assert.fail('Handler should not be called'))
255
+ }
256
+
257
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`)))
258
+
259
+ await Promise.race([
260
+ app.inject({ method: 'GET', url: '/' }),
261
+ new Promise((resolve, reject) => setTimeout(resolve, 1000))
262
+ ])
263
+
264
+ if (hookOrHandler === 'handler') {
265
+ t.assert.strictEqual(errorSeen, true)
266
+ }
267
+ })
268
+
269
+ test('Calling reply.send() after hijacking logs a warning', async (t) => {
270
+ const stream = split(JSON.parse)
271
+ const app = Fastify({
272
+ logger: {
273
+ stream
274
+ }
275
+ })
276
+
277
+ let errorSeen = false
278
+
279
+ stream.on('data', (line) => {
280
+ if (line.level === 40) {
281
+ errorSeen = true
282
+ t.assert.strictEqual(line.err.code, 'FST_ERR_REP_ALREADY_SENT')
283
+ }
284
+ })
285
+
286
+ previousHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.ok(`${h} should be called`)))
287
+
288
+ if (hookOrHandler === 'handler') {
289
+ app.get('/', (req, reply) => {
290
+ reply.hijack()
291
+ reply.send('hello from reply.send()')
292
+ })
293
+ } else {
294
+ app.addHook(hookOrHandler, async (req, reply) => {
295
+ reply.hijack()
296
+ return reply.send('hello from reply.send()')
297
+ })
298
+ app.get('/', (req, reply) => t.assert.fail('Handler should not be called'))
299
+ }
300
+
301
+ nextHooks.forEach(h => app.addHook(h, async (req, reply) => t.assert.fail(`${h} should not be called`)))
302
+
303
+ await Promise.race([
304
+ app.inject({ method: 'GET', url: '/' }),
305
+ new Promise((resolve, reject) => setTimeout(resolve, 1000))
306
+ ])
307
+
308
+ t.assert.strictEqual(errorSeen, true)
309
+ })
310
+ })
311
+ }
312
+
313
+ testHandlerOrBeforeHandlerHook(test, 'onRequest')
314
+ testHandlerOrBeforeHandlerHook(test, 'preParsing')
315
+ testHandlerOrBeforeHandlerHook(test, 'preValidation')
316
+ testHandlerOrBeforeHandlerHook(test, 'preHandler')
317
+ testHandlerOrBeforeHandlerHook(test, 'handler')
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const Fastify = require('..')
5
+ const Reply = require('../lib/reply')
6
+
7
+ test('should serialize reply when response stream is ended', (t, done) => {
8
+ t.plan(5)
9
+
10
+ const stream = require('node:stream')
11
+ const fastify = Fastify({
12
+ logger: {
13
+ serializers: {
14
+ res (reply) {
15
+ t.assert.strictEqual(reply instanceof Reply, true)
16
+ t.assert.ok('passed')
17
+ return reply
18
+ }
19
+ }
20
+ }
21
+ })
22
+
23
+ fastify.get('/error', function (req, reply) {
24
+ const reallyLongStream = new stream.Readable({
25
+ read: () => { }
26
+ })
27
+ reply.code(200).send(reallyLongStream)
28
+ reply.raw.end(Buffer.from('hello\n'))
29
+ })
30
+
31
+ t.after(() => fastify.close())
32
+
33
+ fastify.inject({
34
+ url: '/error',
35
+ method: 'GET'
36
+ }, (err) => {
37
+ t.assert.ifError(err)
38
+ done()
39
+ })
40
+ })
@@ -0,0 +1,94 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const fs = require('node:fs')
5
+ const Fastify = require('../fastify')
6
+
7
+ test('should respond with a stream', async t => {
8
+ t.plan(4)
9
+ const fastify = Fastify()
10
+
11
+ fastify.get('/', function (req, reply) {
12
+ const stream = fs.createReadStream(__filename, 'utf8')
13
+ reply.code(200).send(stream)
14
+ })
15
+
16
+ const fastifyServer = await fastify.listen({ port: 0 })
17
+ t.after(() => { fastify.close() })
18
+
19
+ const response = await fetch(fastifyServer)
20
+ t.assert.ok(response.ok)
21
+ t.assert.strictEqual(response.headers.get('content-type'), null)
22
+ t.assert.strictEqual(response.status, 200)
23
+
24
+ const data = await response.text()
25
+ const expected = await fs.promises.readFile(__filename, 'utf8')
26
+ t.assert.strictEqual(expected.toString(), data.toString())
27
+ })
28
+
29
+ test('should respond with a stream (error)', async t => {
30
+ t.plan(2)
31
+ const fastify = Fastify()
32
+
33
+ fastify.get('/error', function (req, reply) {
34
+ const stream = fs.createReadStream('not-existing-file', 'utf8')
35
+ reply.code(200).send(stream)
36
+ })
37
+
38
+ const fastifyServer = await fastify.listen({ port: 0 })
39
+ t.after(() => fastify.close())
40
+
41
+ const response = await fetch(`${fastifyServer}/error`)
42
+ t.assert.ok(!response.ok)
43
+ t.assert.strictEqual(response.status, 500)
44
+ })
45
+
46
+ test('should trigger the onSend hook', async (t) => {
47
+ t.plan(3)
48
+ const fastify = Fastify()
49
+
50
+ fastify.get('/', (req, reply) => {
51
+ reply.send(fs.createReadStream(__filename, 'utf8'))
52
+ })
53
+
54
+ fastify.addHook('onSend', (req, reply, payload, done) => {
55
+ t.assert.ok(payload._readableState)
56
+ reply.header('Content-Type', 'application/javascript')
57
+ done()
58
+ })
59
+
60
+ const res = await fastify.inject({
61
+ url: '/'
62
+ })
63
+ t.assert.strictEqual(res.headers['content-type'], 'application/javascript')
64
+ t.assert.strictEqual(res.payload, fs.readFileSync(__filename, 'utf8'))
65
+ return fastify.close()
66
+ })
67
+
68
+ test('should trigger the onSend hook only twice if pumping the stream fails, first with the stream, second with the serialized error', async t => {
69
+ t.plan(4)
70
+ const fastify = Fastify()
71
+
72
+ fastify.get('/', (req, reply) => {
73
+ reply.send(fs.createReadStream('not-existing-file', 'utf8'))
74
+ })
75
+
76
+ let counter = 0
77
+ fastify.addHook('onSend', (req, reply, payload, done) => {
78
+ if (counter === 0) {
79
+ t.assert.ok(payload._readableState)
80
+ } else if (counter === 1) {
81
+ const error = JSON.parse(payload)
82
+ t.assert.strictEqual(error.statusCode, 500)
83
+ }
84
+ counter++
85
+ done()
86
+ })
87
+
88
+ const fastifyServer = await fastify.listen({ port: 0 })
89
+ t.after(() => { fastify.close() })
90
+
91
+ const response = await fetch(fastifyServer)
92
+ t.assert.ok(!response.ok)
93
+ t.assert.strictEqual(response.status, 500)
94
+ })
@@ -0,0 +1,129 @@
1
+ 'use strict'
2
+
3
+ const { test } = require('node:test')
4
+ const proxyquire = require('proxyquire')
5
+ const fs = require('node:fs')
6
+ const resolve = require('node:path').resolve
7
+ const zlib = require('node:zlib')
8
+ const pipeline = require('node:stream').pipeline
9
+ const Fastify = require('..')
10
+ const { waitForCb } = require('./toolkit')
11
+
12
+ test('onSend hook stream', t => {
13
+ t.plan(4)
14
+ const fastify = Fastify()
15
+
16
+ fastify.get('/', function (req, reply) {
17
+ reply.send({ hello: 'world' })
18
+ })
19
+
20
+ const { stepIn, patience } = waitForCb({ steps: 2 })
21
+
22
+ fastify.addHook('onSend', (req, reply, payload, done) => {
23
+ const gzStream = zlib.createGzip()
24
+
25
+ reply.header('Content-Encoding', 'gzip')
26
+ pipeline(
27
+ fs.createReadStream(resolve(__filename), 'utf8'),
28
+ gzStream,
29
+ (err) => {
30
+ t.assert.ifError(err)
31
+ stepIn()
32
+ }
33
+ )
34
+ done(null, gzStream)
35
+ })
36
+
37
+ fastify.inject({
38
+ url: '/',
39
+ method: 'GET'
40
+ }, (err, res) => {
41
+ t.assert.ifError(err)
42
+ t.assert.strictEqual(res.headers['content-encoding'], 'gzip')
43
+ const file = fs.readFileSync(resolve(__filename), 'utf8')
44
+ const payload = zlib.gunzipSync(res.rawPayload)
45
+ t.assert.strictEqual(payload.toString('utf-8'), file)
46
+ fastify.close()
47
+ stepIn()
48
+ })
49
+
50
+ return patience
51
+ })
52
+
53
+ test('onSend hook stream should work even if payload is not a proper stream', (t, testDone) => {
54
+ t.plan(1)
55
+
56
+ const reply = proxyquire('../lib/reply', {
57
+ 'node:stream': {
58
+ finished: (...args) => {
59
+ if (args.length === 2) { args[1](new Error('test-error')) }
60
+ }
61
+ }
62
+ })
63
+ const Fastify = proxyquire('..', {
64
+ './lib/reply.js': reply
65
+ })
66
+ const spyLogger = {
67
+ fatal: () => { },
68
+ error: () => { },
69
+ warn: (message) => {
70
+ t.assert.strictEqual(message, 'stream payload does not end properly')
71
+ fastify.close()
72
+ testDone()
73
+ },
74
+ info: () => { },
75
+ debug: () => { },
76
+ trace: () => { },
77
+ child: () => { return spyLogger }
78
+ }
79
+
80
+ const fastify = Fastify({ loggerInstance: spyLogger })
81
+ fastify.get('/', function (req, reply) {
82
+ reply.send({ hello: 'world' })
83
+ })
84
+ fastify.addHook('onSend', (req, reply, payload, done) => {
85
+ const fakeStream = { pipe: () => { } }
86
+ done(null, fakeStream)
87
+ })
88
+
89
+ fastify.inject({
90
+ url: '/',
91
+ method: 'GET'
92
+ })
93
+ })
94
+
95
+ test('onSend hook stream should work on payload with "close" ending function', (t, testDone) => {
96
+ t.plan(1)
97
+
98
+ const reply = proxyquire('../lib/reply', {
99
+ 'node:stream': {
100
+ finished: (...args) => {
101
+ if (args.length === 2) { args[1](new Error('test-error')) }
102
+ }
103
+ }
104
+ })
105
+ const Fastify = proxyquire('..', {
106
+ './lib/reply.js': reply
107
+ })
108
+
109
+ const fastify = Fastify({ logger: false })
110
+ fastify.get('/', function (req, reply) {
111
+ reply.send({ hello: 'world' })
112
+ })
113
+ fastify.addHook('onSend', (req, reply, payload, done) => {
114
+ const fakeStream = {
115
+ pipe: () => { },
116
+ close: (cb) => {
117
+ cb()
118
+ t.assert.ok('close callback called')
119
+ testDone()
120
+ }
121
+ }
122
+ done(null, fakeStream)
123
+ })
124
+
125
+ fastify.inject({
126
+ url: '/',
127
+ method: 'GET'
128
+ })
129
+ })