@fastify/reply-from 12.0.2 → 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.
- package/.github/workflows/ci.yml +6 -0
- package/CLAUDE.md +66 -0
- package/LICENSE +4 -1
- package/README.md +24 -0
- package/index.js +12 -1
- package/lib/request.js +16 -6
- package/lib/utils.js +1 -0
- package/package.json +6 -8
- package/test/async-route-handler.test.js +29 -32
- package/test/balanced-pool.test.js +48 -0
- package/test/base-get.test.js +27 -30
- package/test/base-path.test.js +27 -24
- package/test/base-querystring.test.js +27 -30
- package/test/build-url.test.js +17 -15
- package/test/core-with-path-in-base.test.js +30 -33
- package/test/custom-undici-instance.test.js +28 -36
- package/test/disable-request-logging.test.js +38 -52
- package/test/fastify-multipart-incompatibility.test.js +43 -49
- package/test/fix-GHSA-v2v2-hph8-q5xp.test.js +3 -2
- package/test/full-delete-http2.test.js +12 -11
- package/test/full-get-test.test.js +27 -30
- package/test/full-https-get.test.js +30 -30
- package/test/full-post-extended-content-type.test.js +40 -44
- package/test/full-post-http2.test.js +18 -13
- package/test/full-post-stream-core.test.js +39 -43
- package/test/full-post-stream.test.js +39 -43
- package/test/full-post.test.js +36 -38
- package/test/full-querystring-rewrite-option-complex.test.js +27 -31
- package/test/full-querystring-rewrite-option-function-request.test.js +30 -36
- package/test/full-querystring-rewrite-option-function.test.js +30 -33
- package/test/full-querystring-rewrite-option.test.js +28 -31
- package/test/full-querystring-rewrite-string.test.js +27 -30
- package/test/full-querystring-rewrite.test.js +27 -30
- package/test/full-querystring.test.js +27 -30
- package/test/full-rewrite-body-content-type.test.js +37 -41
- package/test/full-rewrite-body-http.test.js +39 -43
- package/test/full-rewrite-body-to-empty-string.test.js +37 -41
- package/test/full-rewrite-body-to-null.test.js +37 -41
- package/test/full-rewrite-body.test.js +39 -43
- package/test/get-upstream-cache.test.js +12 -11
- package/test/get-upstream-http.test.js +35 -41
- package/test/get-upstream-type.test.js +24 -27
- package/test/get-upstream-undici.test.js +23 -26
- package/test/host-header.test.js +22 -18
- package/test/http-agents.test.js +27 -30
- package/test/http-global-agent.test.js +20 -33
- package/test/http-http2.test.js +13 -18
- package/test/http-invalid-target.test.js +13 -17
- package/test/http-retry.test.js +22 -29
- package/test/http-timeout.test.js +87 -27
- package/test/http2-canceled-streams-cleanup.test.js +117 -0
- package/test/http2-http2.test.js +8 -8
- package/test/http2-https.test.js +29 -27
- package/test/http2-invalid-base.test.js +2 -2
- package/test/http2-invalid-target.test.js +13 -17
- package/test/http2-target-crash.test.js +16 -20
- package/test/http2-target-multi-crash.test.js +22 -27
- package/test/http2-timeout-disabled.test.js +20 -15
- package/test/http2-timeout.test.js +94 -43
- package/test/http2-unix-socket.test.js +2 -2
- package/test/https-agents.test.js +31 -30
- package/test/https-global-agent.test.js +22 -31
- package/test/method.test.js +37 -42
- package/test/modifyCoreObjects-false.test.js +27 -30
- package/test/no-body-opts-with-get.test.js +25 -31
- package/test/no-body-opts-with-head.test.js +28 -32
- package/test/no-stream-body-option.test.js +34 -36
- package/test/on-error.test.js +21 -23
- package/test/on-invalid-upstream-response.test.js +30 -37
- package/test/onResponse.test.js +25 -32
- package/test/padded-body.test.js +38 -43
- package/test/post-formbody.test.js +35 -40
- package/test/post-plain-text.test.js +35 -39
- package/test/post-with-custom-encoded-contenttype.test.js +37 -39
- package/test/post-with-octet-stream.test.js +18 -88
- package/test/retry-on-503.test.js +27 -26
- package/test/retry-with-a-custom-handler.test.js +33 -40
- package/test/rewrite-headers-type.test.js +24 -29
- package/test/rewrite-headers.test.js +31 -36
- package/test/rewrite-request-headers-type.test.js +25 -28
- package/test/rewrite-request-headers.test.js +26 -30
- package/test/transform-body.test.js +37 -43
- package/test/undici-agent.test.js +27 -31
- package/test/undici-body.test.js +37 -42
- package/test/undici-chaining.test.js +35 -42
- package/test/undici-connect-timeout.test.js +13 -13
- package/test/undici-custom-dispatcher.test.js +11 -11
- package/test/undici-global-agent.test.js +14 -33
- package/test/undici-no-destroy.test.js +2 -2
- package/test/undici-options.test.js +21 -24
- package/test/undici-proxy-agent.test.js +4 -3
- package/test/undici-retry.test.js +24 -31
- package/test/undici-timeout-body-partial.test.js +14 -16
- package/test/undici-timeout-body.test.js +17 -19
- package/test/undici-timeout.test.js +86 -23
- package/test/undici-with-path-in-base.test.js +28 -31
- package/test/undici.test.js +28 -31
- package/test/unexpected-error.test.js +13 -18
- package/test/unix-http-undici-from.test.js +22 -31
- package/test/unix-http-undici.test.js +32 -40
- package/test/unix-http.test.js +31 -39
- package/test/unix-https-undici.test.js +30 -35
- package/test/unix-https.test.js +35 -40
- package/types/index.d.ts +13 -5
- package/types/index.test-d.ts +6 -6
- package/test/get-with-body.test.js +0 -55
- package/test/head-with-body.test.js +0 -57
package/.github/workflows/ci.yml
CHANGED
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
|
|
@@ -477,6 +497,10 @@ By default: 0 (disabled)
|
|
|
477
497
|
Override the `'Content-Type'` header of the forwarded request, if we are
|
|
478
498
|
already overriding the [`body`](#body).
|
|
479
499
|
|
|
500
|
+
#### `timeout`
|
|
501
|
+
|
|
502
|
+
Set a specific timeout for the request. Override options `http.requestOptions.timeout`, `http2.requestOptions.timeout`, `undici.headersTimeout` and `undici.bodyTimeout` from the plugin config.
|
|
503
|
+
|
|
480
504
|
### Combining with [@fastify/formbody](https://github.com/fastify/fastify-formbody)
|
|
481
505
|
|
|
482
506
|
`formbody` expects the body to be returned as a string and not an object.
|
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',
|
|
@@ -55,6 +58,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
55
58
|
opts = opts || {}
|
|
56
59
|
const req = this.request.raw
|
|
57
60
|
const method = opts.method || req.method
|
|
61
|
+
const timeout = opts.timeout
|
|
58
62
|
const onResponse = opts.onResponse
|
|
59
63
|
const rewriteHeaders = opts.rewriteHeaders || headersNoOp
|
|
60
64
|
const rewriteRequestHeaders = opts.rewriteRequestHeaders || requestHeadersNoOp
|
|
@@ -169,7 +173,7 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
169
173
|
requestImpl = createRequestRetry(request, this, getDefaultDelay)
|
|
170
174
|
}
|
|
171
175
|
|
|
172
|
-
requestImpl({ method, url, qs, headers: requestHeaders, body }, (err, res) => {
|
|
176
|
+
requestImpl({ method, url, qs, headers: requestHeaders, body, timeout }, (err, res) => {
|
|
173
177
|
if (err) {
|
|
174
178
|
this.request.log.warn(err, 'response errored')
|
|
175
179
|
if (!this.sent) {
|
|
@@ -205,6 +209,13 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
205
209
|
onError(this, { error: new BadGatewayError() })
|
|
206
210
|
this.request.log.warn(err, 'response has invalid status code')
|
|
207
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
|
+
}
|
|
208
219
|
if (onResponse) {
|
|
209
220
|
onResponse(this.request, this, res)
|
|
210
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 (
|
|
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'
|
|
@@ -120,7 +127,8 @@ function buildRequest (opts) {
|
|
|
120
127
|
hostname: opts.url.hostname,
|
|
121
128
|
headers: opts.headers,
|
|
122
129
|
agent: agents[opts.url.protocol.replace(/^unix:/, '')],
|
|
123
|
-
...httpOpts.requestOptions
|
|
130
|
+
...httpOpts.requestOptions,
|
|
131
|
+
timeout: opts.timeout ?? httpOpts.requestOptions.timeout
|
|
124
132
|
})
|
|
125
133
|
req.on('error', done)
|
|
126
134
|
req.on('response', res => {
|
|
@@ -146,14 +154,16 @@ function buildRequest (opts) {
|
|
|
146
154
|
method: opts.method,
|
|
147
155
|
headers: Object.assign({}, opts.headers),
|
|
148
156
|
body: opts.body,
|
|
149
|
-
headersTimeout: undiciOpts.headersTimeout,
|
|
150
|
-
bodyTimeout: undiciOpts.bodyTimeout
|
|
157
|
+
headersTimeout: opts.timeout ?? undiciOpts.headersTimeout,
|
|
158
|
+
bodyTimeout: opts.timeout ?? undiciOpts.bodyTimeout
|
|
151
159
|
}
|
|
152
160
|
|
|
153
161
|
let pool
|
|
154
162
|
|
|
155
163
|
if (undiciInstance) {
|
|
156
164
|
pool = undiciInstance
|
|
165
|
+
} else if (pool instanceof undici.BalancedPool) {
|
|
166
|
+
delete req.origin
|
|
157
167
|
} else if (!baseUrl && opts.url.protocol.startsWith('unix')) {
|
|
158
168
|
done(new Error('unix socket not supported with undici yet'))
|
|
159
169
|
return
|
|
@@ -212,7 +222,7 @@ function buildRequest (opts) {
|
|
|
212
222
|
if (!isGet && !isDelete) {
|
|
213
223
|
end(req, opts.body, done)
|
|
214
224
|
}
|
|
215
|
-
req.setTimeout(http2Opts.requestTimeout, () => {
|
|
225
|
+
req.setTimeout(opts.timeout ?? http2Opts.requestTimeout, () => {
|
|
216
226
|
const err = new Http2RequestTimeoutError()
|
|
217
227
|
req.close(http2.constants.NGHTTP2_CANCEL)
|
|
218
228
|
done(err)
|
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.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": "
|
|
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": "^
|
|
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
|
-
"
|
|
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": "^
|
|
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('
|
|
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
|
|
7
|
+
const { request } = require('undici')
|
|
8
8
|
|
|
9
9
|
const instance = Fastify()
|
|
10
10
|
|
|
11
|
-
t.
|
|
12
|
-
t.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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.
|
|
31
|
+
t.after(() => target.close())
|
|
31
32
|
|
|
32
|
-
target.listen({ port: 0 },
|
|
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 },
|
|
40
|
-
t.error(err)
|
|
39
|
+
await new Promise(resolve => instance.listen({ port: 0 }, resolve))
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
+
})
|
package/test/base-get.test.js
CHANGED
|
@@ -1,48 +1,45 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const t = require('
|
|
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.
|
|
12
|
-
t.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
})
|
|
25
|
+
instance.get('/', (_request, reply) => {
|
|
26
|
+
reply.from()
|
|
27
|
+
})
|
|
27
28
|
|
|
28
|
-
t.
|
|
29
|
+
t.after(() => target.close())
|
|
29
30
|
|
|
30
|
-
target.listen({ port: 0 },
|
|
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 },
|
|
38
|
-
t.error(err)
|
|
37
|
+
await new Promise((resolve) => instance.listen({ port: 0 }, resolve))
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
})
|
package/test/base-path.test.js
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const t = require('
|
|
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
|
-
|
|
9
|
+
t.test('base path', async (t) => {
|
|
10
|
+
const instance = Fastify()
|
|
10
11
|
|
|
11
|
-
nock('http://httpbin.org')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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(
|
|
19
|
-
t.
|
|
19
|
+
t.plan(4)
|
|
20
|
+
t.after(() => instance.close())
|
|
20
21
|
|
|
21
|
-
instance.get('/', (_request, reply) => {
|
|
22
|
-
|
|
23
|
-
})
|
|
22
|
+
instance.get('/', (_request, reply) => {
|
|
23
|
+
reply.from('http://httpbin.org/ip')
|
|
24
|
+
})
|
|
24
25
|
|
|
25
|
-
instance.register(From, {
|
|
26
|
-
|
|
27
|
-
})
|
|
26
|
+
instance.register(From, {
|
|
27
|
+
undici: false
|
|
28
|
+
})
|
|
28
29
|
|
|
29
|
-
instance.listen({ port: 0 },
|
|
30
|
-
t.error(err)
|
|
30
|
+
await new Promise(resolve => instance.listen({ port: 0 }, resolve))
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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('
|
|
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.
|
|
12
|
-
t.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
26
|
-
})
|
|
25
|
+
instance.get('/hello', (_request, reply) => {
|
|
26
|
+
reply.from()
|
|
27
|
+
})
|
|
27
28
|
|
|
28
|
-
t.
|
|
29
|
+
t.after(() => target.close())
|
|
29
30
|
|
|
30
|
-
target.listen({ port: 0 },
|
|
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 },
|
|
38
|
-
t.error(err)
|
|
37
|
+
await new Promise(resolve => instance.listen({ port: 0 }, resolve))
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
})
|