@fastify/reply-from 12.1.0 → 12.2.0

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 (107) hide show
  1. package/.github/workflows/ci.yml +3 -0
  2. package/CLAUDE.md +66 -0
  3. package/LICENSE +4 -1
  4. package/README.md +20 -0
  5. package/index.js +10 -0
  6. package/lib/request.js +11 -2
  7. package/lib/utils.js +1 -0
  8. package/package.json +6 -8
  9. package/test/async-route-handler.test.js +29 -32
  10. package/test/balanced-pool.test.js +48 -0
  11. package/test/base-get.test.js +27 -30
  12. package/test/base-path.test.js +27 -24
  13. package/test/base-querystring.test.js +27 -30
  14. package/test/build-url.test.js +17 -15
  15. package/test/core-with-path-in-base.test.js +30 -33
  16. package/test/custom-undici-instance.test.js +28 -36
  17. package/test/disable-request-logging.test.js +38 -52
  18. package/test/fastify-multipart-incompatibility.test.js +43 -49
  19. package/test/fix-GHSA-v2v2-hph8-q5xp.test.js +3 -2
  20. package/test/full-delete-http2.test.js +12 -11
  21. package/test/full-get-test.test.js +27 -30
  22. package/test/full-https-get.test.js +30 -30
  23. package/test/full-post-extended-content-type.test.js +40 -44
  24. package/test/full-post-http2.test.js +18 -13
  25. package/test/full-post-stream-core.test.js +39 -43
  26. package/test/full-post-stream.test.js +39 -43
  27. package/test/full-post.test.js +36 -38
  28. package/test/full-querystring-rewrite-option-complex.test.js +27 -31
  29. package/test/full-querystring-rewrite-option-function-request.test.js +30 -36
  30. package/test/full-querystring-rewrite-option-function.test.js +30 -33
  31. package/test/full-querystring-rewrite-option.test.js +28 -31
  32. package/test/full-querystring-rewrite-string.test.js +27 -30
  33. package/test/full-querystring-rewrite.test.js +27 -30
  34. package/test/full-querystring.test.js +27 -30
  35. package/test/full-rewrite-body-content-type.test.js +37 -41
  36. package/test/full-rewrite-body-http.test.js +39 -43
  37. package/test/full-rewrite-body-to-empty-string.test.js +37 -41
  38. package/test/full-rewrite-body-to-null.test.js +37 -41
  39. package/test/full-rewrite-body.test.js +39 -43
  40. package/test/get-upstream-cache.test.js +12 -11
  41. package/test/get-upstream-http.test.js +35 -41
  42. package/test/get-upstream-type.test.js +24 -27
  43. package/test/get-upstream-undici.test.js +23 -26
  44. package/test/host-header.test.js +22 -18
  45. package/test/http-agents.test.js +27 -30
  46. package/test/http-global-agent.test.js +20 -33
  47. package/test/http-http2.test.js +13 -18
  48. package/test/http-invalid-target.test.js +13 -17
  49. package/test/http-retry.test.js +22 -29
  50. package/test/http-timeout.test.js +49 -42
  51. package/test/http2-canceled-streams-cleanup.test.js +117 -0
  52. package/test/http2-http2.test.js +8 -8
  53. package/test/http2-https.test.js +29 -27
  54. package/test/http2-invalid-base.test.js +2 -2
  55. package/test/http2-invalid-target.test.js +13 -17
  56. package/test/http2-target-crash.test.js +16 -20
  57. package/test/http2-target-multi-crash.test.js +22 -27
  58. package/test/http2-timeout-disabled.test.js +20 -15
  59. package/test/http2-timeout.test.js +58 -65
  60. package/test/http2-unix-socket.test.js +2 -2
  61. package/test/https-agents.test.js +31 -30
  62. package/test/https-global-agent.test.js +22 -31
  63. package/test/method.test.js +37 -42
  64. package/test/modifyCoreObjects-false.test.js +27 -30
  65. package/test/no-body-opts-with-get.test.js +25 -31
  66. package/test/no-body-opts-with-head.test.js +28 -32
  67. package/test/no-stream-body-option.test.js +34 -36
  68. package/test/on-error.test.js +21 -23
  69. package/test/on-invalid-upstream-response.test.js +30 -37
  70. package/test/onResponse.test.js +25 -32
  71. package/test/padded-body.test.js +38 -43
  72. package/test/post-formbody.test.js +35 -40
  73. package/test/post-plain-text.test.js +35 -39
  74. package/test/post-with-custom-encoded-contenttype.test.js +37 -39
  75. package/test/post-with-octet-stream.test.js +18 -88
  76. package/test/retry-on-503.test.js +27 -26
  77. package/test/retry-with-a-custom-handler.test.js +33 -40
  78. package/test/rewrite-headers-type.test.js +24 -29
  79. package/test/rewrite-headers.test.js +31 -36
  80. package/test/rewrite-request-headers-type.test.js +25 -28
  81. package/test/rewrite-request-headers.test.js +26 -30
  82. package/test/transform-body.test.js +37 -43
  83. package/test/undici-agent.test.js +27 -31
  84. package/test/undici-body.test.js +37 -42
  85. package/test/undici-chaining.test.js +35 -42
  86. package/test/undici-connect-timeout.test.js +13 -13
  87. package/test/undici-custom-dispatcher.test.js +11 -11
  88. package/test/undici-global-agent.test.js +14 -33
  89. package/test/undici-no-destroy.test.js +2 -2
  90. package/test/undici-options.test.js +21 -24
  91. package/test/undici-proxy-agent.test.js +4 -3
  92. package/test/undici-retry.test.js +24 -31
  93. package/test/undici-timeout-body-partial.test.js +14 -16
  94. package/test/undici-timeout-body.test.js +17 -19
  95. package/test/undici-timeout.test.js +41 -37
  96. package/test/undici-with-path-in-base.test.js +28 -31
  97. package/test/undici.test.js +28 -31
  98. package/test/unexpected-error.test.js +13 -18
  99. package/test/unix-http-undici-from.test.js +22 -31
  100. package/test/unix-http-undici.test.js +32 -40
  101. package/test/unix-http.test.js +31 -39
  102. package/test/unix-https-undici.test.js +30 -35
  103. package/test/unix-https.test.js +35 -40
  104. package/types/index.d.ts +9 -2
  105. package/types/index.test-d.ts +3 -4
  106. package/test/get-with-body.test.js +0 -55
  107. package/test/head-with-body.test.js +0 -57
@@ -14,6 +14,9 @@ on:
14
14
  - 'docs/**'
15
15
  - '*.md'
16
16
 
17
+ permissions:
18
+ contents: read
19
+
17
20
  jobs:
18
21
  test:
19
22
  permissions:
package/CLAUDE.md ADDED
@@ -0,0 +1,66 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Development Commands
6
+
7
+ ### Testing
8
+ - `npm test` - Run all tests (unit + TypeScript)
9
+ - `npm run test:unit` - Run unit tests with coverage using c8 and Node.js test runner
10
+ - `npm run test:typescript` - Run TypeScript definition tests using tsd
11
+
12
+ ### Linting
13
+ - `npm run lint` - Run ESLint to check code style
14
+ - `npm run lint:fix` - Run ESLint with automatic fixes
15
+ - ESLint configuration uses neostandard with TypeScript support
16
+
17
+ ### Building
18
+ - No build step required - this is a CommonJS library
19
+
20
+ ## Architecture Overview
21
+
22
+ This is a Fastify plugin for proxying HTTP requests to upstream servers. The plugin decorates the Fastify reply with a `from()` method.
23
+
24
+ ### Key Components
25
+
26
+ **Main Plugin (`index.js`)**
27
+ - Entry point that registers the plugin using fastify-plugin
28
+ - Decorates `reply.from(source, opts)` method
29
+ - Handles request proxying, retries, caching, and error handling
30
+ - Supports HTTP/1.1, HTTP/2, and Undici client strategies
31
+
32
+ **Request Builder (`lib/request.js`)**
33
+ - Factory for creating request handlers based on transport (HTTP/1.1, HTTP/2, Undici)
34
+ - Handles different agent configurations (HTTP, HTTPS, Unix sockets)
35
+ - Manages timeouts, connection pooling, and retry logic
36
+ - Returns unified interface: `{ request, close, retryOnError }`
37
+
38
+ **Utilities (`lib/utils.js`)**
39
+ - `filterPseudoHeaders()` - Removes HTTP/2 pseudo-headers (starting with :)
40
+ - `copyHeaders()` - Copies headers to Fastify reply
41
+ - `stripHttp1ConnectionHeaders()` - Removes HTTP/1 connection-specific headers for HTTP/2
42
+ - `buildURL()` - Constructs destination URLs with base path validation
43
+
44
+ **Error Definitions (`lib/errors.js`)**
45
+ - Standardized error types using @fastify/error
46
+ - Maps different error conditions to appropriate HTTP status codes
47
+ - Includes timeout, connection reset, and gateway errors
48
+
49
+ ### Transport Strategies
50
+
51
+ 1. **Undici (default)** - High-performance HTTP/1.1 client with connection pooling
52
+ 2. **HTTP/2** - Native Node.js HTTP/2 client for modern protocols
53
+ 3. **HTTP/1.1** - Node.js built-in http/https modules with custom agents
54
+
55
+ ### Key Features
56
+
57
+ - **Caching**: URL parsing cache using toad-cache LruMap (default 100 entries)
58
+ - **Retries**: Configurable retry logic for socket hangups and 503 errors
59
+ - **Headers**: Smart header rewriting for HTTP/1.1 ↔ HTTP/2 compatibility
60
+ - **Body handling**: Supports streams, JSON encoding, and custom content types
61
+ - **Unix sockets**: Special handling for unix+http: and unix+https: protocols
62
+ - **Timeouts**: Per-request and session-level timeout configuration
63
+
64
+ ### Configuration
65
+
66
+ The plugin accepts extensive configuration for different transport methods, retry behavior, caching, and request/response transformation hooks. See README.md for full option documentation.
package/LICENSE CHANGED
@@ -1,6 +1,9 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2017 Matteo Collina
3
+ Copyright (c) 2017-present Matteo Collina
4
+ Copyright (c) 2017-present The Fastify team
5
+
6
+ The Fastify team members are listed at https://github.com/fastify/fastify#team.
4
7
 
5
8
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
9
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -69,6 +69,15 @@ target.listen({ port: 3001 }, (err) => {
69
69
  Set the base URL for all the forwarded requests. Will be required if `http2` is set to `true`
70
70
  Note that _every path will be discarded_.
71
71
 
72
+ Set the base URL for all the forwarded requests.
73
+ *String or String[]*:
74
+
75
+ * **Single string** → a normal `undici.Pool` / `http.request` client is used.
76
+ * **Array with ≥ 2 elements** → **[`undici.BalancedPool`](https://undici.nodejs.org/#/docs/api/BalancedPool)** is automatically selected and requests are load-balanced round-robin across the given origins.
77
+
78
+ When you provide an array, only the *origin* (`protocol://host:port`) part of each URL is considered; any path component is ignored.
79
+
80
+
72
81
  Custom URL protocols `unix+http:` and `unix+https:` can be used to forward requests to a unix
73
82
  socket server by using `querystring.escape(socketPath)` as the hostname. This is not supported
74
83
  for http2 nor undici. To illustrate:
@@ -125,6 +134,17 @@ proxy.register(require('@fastify/reply-from'), {
125
134
  })
126
135
  ```
127
136
 
137
+ You can also use with BalancedPool:
138
+ ```js
139
+ proxy.register(require('@fastify/reply-from'), {
140
+ base: [
141
+ 'http://api-1.internal:8080',
142
+ 'http://api-2.internal:8080',
143
+ 'http://api-3.internal:8080'
144
+ ]
145
+ })
146
+ ```
147
+
128
148
  #### `http`
129
149
 
130
150
  Set the `http` option to an Object to use
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const http2 = require('node:http2')
3
4
  const fp = require('fastify-plugin')
4
5
  const { LruMap } = require('toad-cache')
5
6
  const querystring = require('fast-querystring')
@@ -24,6 +25,8 @@ const {
24
25
  BadGatewayError
25
26
  } = require('./lib/errors')
26
27
 
28
+ const { NGHTTP2_CANCEL } = http2.constants
29
+
27
30
  const fastifyReplyFrom = fp(function from (fastify, opts, next) {
28
31
  const contentTypesToEncode = new Set([
29
32
  'application/json',
@@ -206,6 +209,13 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
206
209
  onError(this, { error: new BadGatewayError() })
207
210
  this.request.log.warn(err, 'response has invalid status code')
208
211
  }
212
+ if (this.request.raw.aborted && res.stream) {
213
+ // the request could have been canceled before we got a response from the target
214
+ // forward this to the upstream server and close the stream to prevent leaks
215
+ res.stream.close(NGHTTP2_CANCEL)
216
+ // no need to send a reply for aborted requests or call the onResponse callback
217
+ return
218
+ }
209
219
  if (onResponse) {
210
220
  onResponse(this.request, this, res)
211
221
  } else {
package/lib/request.js CHANGED
@@ -37,6 +37,9 @@ function isUndiciInstance (obj) {
37
37
 
38
38
  function buildRequest (opts) {
39
39
  const isHttp2 = !!opts.http2
40
+ if (Array.isArray(opts.base) && opts.base.length === 1) {
41
+ opts.base = opts.base[0]
42
+ }
40
43
  const hasUndiciOptions = shouldUseUndici(opts)
41
44
  const requests = {
42
45
  'http:': http,
@@ -46,7 +49,8 @@ function buildRequest (opts) {
46
49
  }
47
50
  const http2Opts = getHttp2Opts(opts)
48
51
  const httpOpts = getHttpOpts(opts)
49
- const baseUrl = opts.base && new URL(opts.base).origin
52
+ const baseUrl = Array.isArray(opts.base) ? null : (opts.base && new URL(opts.base).origin)
53
+ const isBalanced = Array.isArray(opts.base) && opts.base.length > 1
50
54
  const undiciOpts = opts.undici || {}
51
55
  const globalAgent = opts.globalAgent
52
56
  const destroyAgent = opts.destroyAgent
@@ -75,7 +79,10 @@ function buildRequest (opts) {
75
79
  if (isHttp2) {
76
80
  return { request: handleHttp2Req, close, retryOnError: 'ECONNRESET' }
77
81
  } else if (hasUndiciOptions) {
78
- if (opts.base?.startsWith('unix+')) {
82
+ if (isBalanced) {
83
+ const origins = opts.base.map(u => new URL(u).origin)
84
+ undiciInstance = new undici.BalancedPool(origins, getUndiciOptions(opts.undici))
85
+ } else if (opts.base?.startsWith('unix+')) {
79
86
  const undiciOpts = getUndiciOptions(opts.undici)
80
87
  undiciOpts.socketPath = decodeURIComponent(new URL(opts.base).host)
81
88
  const protocol = opts.base.startsWith('unix+https') ? 'https' : 'http'
@@ -155,6 +162,8 @@ function buildRequest (opts) {
155
162
 
156
163
  if (undiciInstance) {
157
164
  pool = undiciInstance
165
+ } else if (pool instanceof undici.BalancedPool) {
166
+ delete req.origin
158
167
  } else if (!baseUrl && opts.url.protocol.startsWith('unix')) {
159
168
  done(new Error('unix socket not supported with undici yet'))
160
169
  return
package/lib/utils.js CHANGED
@@ -58,6 +58,7 @@ function stripHttp1ConnectionHeaders (headers) {
58
58
 
59
59
  // issue ref: https://github.com/fastify/fast-proxy/issues/42
60
60
  function buildURL (source, reqBase) {
61
+ if (Array.isArray(reqBase)) reqBase = reqBase[0]
61
62
  let baseOrigin = reqBase ? new URL(reqBase).href : undefined
62
63
 
63
64
  // To make sure we don't accidentally override the base path
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastify/reply-from",
3
- "version": "12.1.0",
3
+ "version": "12.2.0",
4
4
  "description": "forward your HTTP request to another server, for fastify",
5
5
  "main": "index.js",
6
6
  "type": "commonjs",
@@ -9,7 +9,7 @@
9
9
  "lint": "eslint",
10
10
  "lint:fix": "eslint --fix",
11
11
  "test": "npm run test:unit && npm run test:typescript",
12
- "test:unit": "tap",
12
+ "test:unit": "c8 node --test",
13
13
  "test:typescript": "tsd"
14
14
  },
15
15
  "repository": {
@@ -62,26 +62,24 @@
62
62
  "@fastify/multipart": "^9.0.0",
63
63
  "@fastify/pre-commit": "^2.1.0",
64
64
  "@sinonjs/fake-timers": "^14.0.0",
65
- "@types/node": "^22.0.0",
65
+ "@types/node": "^24.0.8",
66
66
  "@types/tap": "^18.0.0",
67
+ "c8": "^10.1.3",
67
68
  "eslint": "^9.17.0",
68
69
  "fastify": "^5.0.0",
69
70
  "form-data": "^4.0.0",
70
- "got": "^11.8.6",
71
71
  "h2url": "^0.2.0",
72
72
  "neostandard": "^0.12.0",
73
73
  "nock": "^14.0.0",
74
74
  "proxy": "^2.1.1",
75
75
  "proxyquire": "^2.1.3",
76
- "simple-get": "^4.0.1",
77
76
  "split2": "^4.2.0",
78
- "tap": "^18.7.2",
79
- "tsd": "^0.31.0"
77
+ "tsd": "^0.32.0"
80
78
  },
81
79
  "dependencies": {
82
80
  "@fastify/error": "^4.0.0",
83
81
  "end-of-stream": "^1.4.4",
84
- "fast-content-type-parse": "^2.0.0",
82
+ "fast-content-type-parse": "^3.0.0",
85
83
  "fast-querystring": "^1.1.2",
86
84
  "fastify-plugin": "^5.0.1",
87
85
  "toad-cache": "^3.7.0",
@@ -1,50 +1,47 @@
1
1
  'use strict'
2
2
 
3
- const t = require('tap')
3
+ const t = require('node:test')
4
4
  const Fastify = require('fastify')
5
5
  const From = require('..')
6
6
  const http = require('node:http')
7
- const get = require('simple-get').concat
7
+ const { request } = require('undici')
8
8
 
9
9
  const instance = Fastify()
10
10
 
11
- t.plan(11)
12
- t.teardown(instance.close.bind(instance))
13
-
14
- const target = http.createServer((req, res) => {
15
- t.pass('request proxied')
16
- t.equal(req.method, 'GET')
17
- t.equal(req.url, '/')
18
- res.statusCode = 205
19
- res.setHeader('Content-Type', 'text/plain')
20
- res.setHeader('x-my-header', 'hello!')
21
- res.end('hello world')
22
- })
11
+ t.test('async route handler', async (t) => {
12
+ t.plan(8)
13
+ t.after(() => instance.close())
14
+
15
+ const target = http.createServer((req, res) => {
16
+ t.assert.ok('request proxied')
17
+ t.assert.strictEqual(req.method, 'GET')
18
+ t.assert.strictEqual(req.url, '/')
19
+ res.statusCode = 205
20
+ res.setHeader('Content-Type', 'text/plain')
21
+ res.setHeader('x-my-header', 'hello!')
22
+ res.end('hello world')
23
+ })
23
24
 
24
- instance.get('/', async (_request, reply) => {
25
- const p = reply.from()
26
- t.equal(p, reply)
27
- return p
28
- })
25
+ instance.get('/', async (_request, reply) => {
26
+ const p = reply.from()
27
+ t.assert.strictEqual(p, reply)
28
+ return p
29
+ })
29
30
 
30
- t.teardown(target.close.bind(target))
31
+ t.after(() => target.close())
31
32
 
32
- target.listen({ port: 0 }, (err) => {
33
- t.error(err)
33
+ await new Promise(resolve => target.listen({ port: 0 }, resolve))
34
34
 
35
35
  instance.register(From, {
36
36
  base: `http://localhost:${target.address().port}`
37
37
  })
38
38
 
39
- instance.listen({ port: 0 }, (err) => {
40
- t.error(err)
39
+ await new Promise(resolve => instance.listen({ port: 0 }, resolve))
41
40
 
42
- get(`http://localhost:${instance.server.address().port}`, (err, res, data) => {
43
- t.error(err)
44
- t.equal(res.headers['content-type'], 'text/plain')
45
- t.equal(res.headers['x-my-header'], 'hello!')
46
- t.equal(res.statusCode, 205)
47
- t.equal(data.toString(), 'hello world')
48
- })
49
- })
41
+ const result = await request(`http://localhost:${instance.server.address().port}`)
42
+
43
+ t.assert.strictEqual(result.headers['content-type'], 'text/plain')
44
+ t.assert.strictEqual(result.headers['x-my-header'], 'hello!')
45
+ t.assert.strictEqual(result.statusCode, 205)
46
+ t.assert.strictEqual(await result.body.text(), 'hello world')
50
47
  })
@@ -0,0 +1,48 @@
1
+ 'use strict'
2
+ const t = require('node:test')
3
+ const http = require('node:http')
4
+ const Fastify = require('fastify')
5
+ const From = require('..')
6
+ const { request } = require('undici')
7
+
8
+ t.test('undici balanced pool http', async t => {
9
+ const hit = [0, 0]
10
+ const makeTarget = idx => http.createServer((req, res) => {
11
+ hit[idx]++
12
+ res.statusCode = 200
13
+ res.end('hello world')
14
+ })
15
+ const target1 = makeTarget(0)
16
+ const target2 = makeTarget(1)
17
+
18
+ await Promise.all([
19
+ new Promise(resolve => target1.listen(0, resolve)),
20
+ new Promise(resolve => target2.listen(0, resolve))
21
+ ])
22
+ const p1 = target1.address().port
23
+ const p2 = target2.address().port
24
+
25
+ const proxy = Fastify()
26
+ proxy.register(From, {
27
+ base: [`http://localhost:${p1}`, `http://localhost:${p2}`]
28
+ })
29
+ proxy.get('*', (_req, reply) => {
30
+ reply.from()
31
+ })
32
+
33
+ t.after(() => {
34
+ proxy.close()
35
+ target1.close()
36
+ target2.close()
37
+ })
38
+
39
+ await proxy.listen({ port: 0 })
40
+ const proxyPort = proxy.server.address().port
41
+
42
+ for (let i = 0; i < 10; i++) {
43
+ const res = await request(`http://localhost:${proxyPort}/hello`)
44
+ t.assert.strictEqual(res.statusCode, 200)
45
+ t.assert.strictEqual(await res.body.text(), 'hello world')
46
+ }
47
+ t.assert.ok(hit[0] > 0 && hit[1] > 0, `load distribution OK => [${hit[0]}, ${hit[1]}]`)
48
+ })
@@ -1,48 +1,45 @@
1
1
  'use strict'
2
2
 
3
- const t = require('tap')
3
+ const t = require('node:test')
4
4
  const Fastify = require('fastify')
5
+ const { request } = require('undici')
5
6
  const From = require('..')
6
7
  const http = require('node:http')
7
- const get = require('simple-get').concat
8
8
 
9
9
  const instance = Fastify()
10
10
 
11
- t.plan(10)
12
- t.teardown(instance.close.bind(instance))
13
-
14
- const target = http.createServer((req, res) => {
15
- t.pass('request proxied')
16
- t.equal(req.method, 'GET')
17
- t.equal(req.url, '/')
18
- res.statusCode = 205
19
- res.setHeader('Content-Type', 'text/plain')
20
- res.setHeader('x-my-header', 'hello!')
21
- res.end('hello world')
22
- })
11
+ t.test('base get', async (t) => {
12
+ t.plan(7)
13
+ t.after(() => instance.close())
14
+
15
+ const target = http.createServer((req, res) => {
16
+ t.assert.ok('request proxied')
17
+ t.assert.strictEqual(req.method, 'GET')
18
+ t.assert.strictEqual(req.url, '/')
19
+ res.statusCode = 205
20
+ res.setHeader('Content-Type', 'text/plain')
21
+ res.setHeader('x-my-header', 'hello!')
22
+ res.end('hello world')
23
+ })
23
24
 
24
- instance.get('/', (_request, reply) => {
25
- reply.from()
26
- })
25
+ instance.get('/', (_request, reply) => {
26
+ reply.from()
27
+ })
27
28
 
28
- t.teardown(target.close.bind(target))
29
+ t.after(() => target.close())
29
30
 
30
- target.listen({ port: 0 }, (err) => {
31
- t.error(err)
31
+ await new Promise((resolve) => target.listen({ port: 0 }, resolve))
32
32
 
33
33
  instance.register(From, {
34
34
  base: `http://localhost:${target.address().port}`
35
35
  })
36
36
 
37
- instance.listen({ port: 0 }, (err) => {
38
- t.error(err)
37
+ await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
39
38
 
40
- get(`http://localhost:${instance.server.address().port}`, (err, res, data) => {
41
- t.error(err)
42
- t.equal(res.headers['content-type'], 'text/plain')
43
- t.equal(res.headers['x-my-header'], 'hello!')
44
- t.equal(res.statusCode, 205)
45
- t.equal(data.toString(), 'hello world')
46
- })
47
- })
39
+ const result = await request(`http://localhost:${instance.server.address().port}`)
40
+
41
+ t.assert.strictEqual(result.headers['content-type'], 'text/plain')
42
+ t.assert.strictEqual(result.headers['x-my-header'], 'hello!')
43
+ t.assert.strictEqual(result.statusCode, 205)
44
+ t.assert.strictEqual(await result.body.text(), 'hello world')
48
45
  })
@@ -1,38 +1,41 @@
1
1
  'use strict'
2
2
 
3
- const t = require('tap')
3
+ const t = require('node:test')
4
4
  const Fastify = require('fastify')
5
+ const { request, Agent } = require('undici')
5
6
  const From = require('..')
6
- const get = require('simple-get').concat
7
7
  const nock = require('nock')
8
8
 
9
- const instance = Fastify()
9
+ t.test('base path', async (t) => {
10
+ const instance = Fastify()
10
11
 
11
- nock('http://httpbin.org')
12
- .get('/ip')
13
- .reply(200, function () {
14
- t.equal(this.req.headers.host, 'httpbin.org')
15
- return { origin: '127.0.0.1' }
16
- })
12
+ nock('http://httpbin.org')
13
+ .get('/ip')
14
+ .reply(200, function () {
15
+ t.assert.strictEqual(this.req.headers.host, 'httpbin.org')
16
+ return { origin: '127.0.0.1' }
17
+ })
17
18
 
18
- t.plan(6)
19
- t.teardown(instance.close.bind(instance))
19
+ t.plan(4)
20
+ t.after(() => instance.close())
20
21
 
21
- instance.get('/', (_request, reply) => {
22
- reply.from('http://httpbin.org/ip')
23
- })
22
+ instance.get('/', (_request, reply) => {
23
+ reply.from('http://httpbin.org/ip')
24
+ })
24
25
 
25
- instance.register(From, {
26
- undici: false
27
- })
26
+ instance.register(From, {
27
+ undici: false
28
+ })
28
29
 
29
- instance.listen({ port: 0 }, (err) => {
30
- t.error(err)
30
+ await new Promise(resolve => instance.listen({ port: 0 }, resolve))
31
31
 
32
- get(`http://localhost:${instance.server.address().port}`, (err, res, data) => {
33
- t.error(err)
34
- t.equal(res.statusCode, 200)
35
- t.equal(res.headers['content-type'], 'application/json')
36
- t.equal(typeof JSON.parse(data).origin, 'string')
32
+ const result = await request(`http://localhost:${instance.server.address().port}`, {
33
+ dispatcher: new Agent({
34
+ pipelining: 0
35
+ })
37
36
  })
37
+
38
+ t.assert.strictEqual(result.statusCode, 200)
39
+ t.assert.strictEqual(result.headers['content-type'], 'application/json')
40
+ t.assert.strictEqual(typeof (await result.body.json()).origin, 'string')
38
41
  })
@@ -1,48 +1,45 @@
1
1
  'use strict'
2
2
 
3
- const t = require('tap')
3
+ const t = require('node:test')
4
4
  const Fastify = require('fastify')
5
+ const { request } = require('undici')
5
6
  const From = require('..')
6
7
  const http = require('node:http')
7
- const get = require('simple-get').concat
8
8
 
9
9
  const instance = Fastify()
10
10
 
11
- t.plan(10)
12
- t.teardown(instance.close.bind(instance))
13
-
14
- const target = http.createServer((req, res) => {
15
- t.pass('request proxied')
16
- t.equal(req.method, 'GET')
17
- t.equal(req.url, '/hello?a=b')
18
- res.statusCode = 205
19
- res.setHeader('Content-Type', 'text/plain')
20
- res.setHeader('x-my-header', 'hello!')
21
- res.end('hello world')
22
- })
11
+ t.test('base querystring', async (t) => {
12
+ t.plan(7)
13
+ t.after(() => instance.close())
14
+
15
+ const target = http.createServer((req, res) => {
16
+ t.assert.ok('request proxied')
17
+ t.assert.strictEqual(req.method, 'GET')
18
+ t.assert.strictEqual(req.url, '/hello?a=b')
19
+ res.statusCode = 205
20
+ res.setHeader('Content-Type', 'text/plain')
21
+ res.setHeader('x-my-header', 'hello!')
22
+ res.end('hello world')
23
+ })
23
24
 
24
- instance.get('/hello', (_request, reply) => {
25
- reply.from()
26
- })
25
+ instance.get('/hello', (_request, reply) => {
26
+ reply.from()
27
+ })
27
28
 
28
- t.teardown(target.close.bind(target))
29
+ t.after(() => target.close())
29
30
 
30
- target.listen({ port: 0 }, (err) => {
31
- t.error(err)
31
+ await new Promise(resolve => target.listen({ port: 0 }, resolve))
32
32
 
33
33
  instance.register(From, {
34
34
  base: `http://localhost:${target.address().port}`
35
35
  })
36
36
 
37
- instance.listen({ port: 0 }, (err) => {
38
- t.error(err)
37
+ await new Promise(resolve => instance.listen({ port: 0 }, resolve))
39
38
 
40
- get(`http://localhost:${instance.server.address().port}/hello?a=b`, (err, res, data) => {
41
- t.error(err)
42
- t.equal(res.headers['content-type'], 'text/plain')
43
- t.equal(res.headers['x-my-header'], 'hello!')
44
- t.equal(res.statusCode, 205)
45
- t.equal(data.toString(), 'hello world')
46
- })
47
- })
39
+ const result = await request(`http://localhost:${instance.server.address().port}/hello?a=b`)
40
+
41
+ t.assert.strictEqual(result.headers['content-type'], 'text/plain')
42
+ t.assert.strictEqual(result.headers['x-my-header'], 'hello!')
43
+ t.assert.strictEqual(result.statusCode, 205)
44
+ t.assert.strictEqual(await result.body.text(), 'hello world')
48
45
  })