@fastify/reply-from 8.3.0 → 8.4.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/.taprc +5 -2
- package/README.md +31 -4
- package/index.js +20 -7
- package/lib/request.js +16 -4
- package/package.json +6 -6
- package/test/get-upstream-cache.js +72 -0
- package/test/http-global-agent.js +57 -0
- package/test/http2-invalid-base.js +13 -0
- package/test/http2-unix-socket.js +16 -0
- package/test/https-global-agent.js +69 -0
- package/test/post-with-octet-stream.js +131 -0
- package/test/undici-global-agent.js +66 -0
- package/test/utils-filter-pseudo-headers.js +18 -0
- package/types/index.d.ts +62 -53
- package/types/index.test-d.ts +2 -1
package/.taprc
CHANGED
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @fastify/reply-from
|
|
2
2
|
|
|
3
|
-
[](https://github.com/fastify/fastify-reply-from/actions/workflows/ci.yml)
|
|
4
4
|
[](https://www.npmjs.com/package/@fastify/reply-from)
|
|
5
5
|
[](https://standardjs.com/)
|
|
6
6
|
|
|
@@ -166,7 +166,7 @@ proxy.register(require('@fastify/reply-from'), {
|
|
|
166
166
|
sessionOptions: { // HTTP/2 session connect options, pass in any options from https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener
|
|
167
167
|
rejectUnauthorized: true
|
|
168
168
|
},
|
|
169
|
-
|
|
169
|
+
requestOptions: { // HTTP/2 request options, pass in any options from https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options
|
|
170
170
|
endStream: true
|
|
171
171
|
}
|
|
172
172
|
}
|
|
@@ -203,8 +203,7 @@ This only applies when a custom [`body`](#body) is not passed in. Defaults to:
|
|
|
203
203
|
|
|
204
204
|
```js
|
|
205
205
|
[
|
|
206
|
-
'application/json'
|
|
207
|
-
'application/x-www-form-urlencoded'
|
|
206
|
+
'application/json'
|
|
208
207
|
]
|
|
209
208
|
```
|
|
210
209
|
|
|
@@ -217,6 +216,34 @@ By default: `['GET', 'HEAD', 'OPTIONS', 'TRACE' ]`
|
|
|
217
216
|
|
|
218
217
|
This plugin will always retry on 503 errors, _unless_ `retryMethods` does not contain `GET`.
|
|
219
218
|
|
|
219
|
+
#### `globalAgent`
|
|
220
|
+
|
|
221
|
+
Enables the possibility to explictly opt-in for global agents.
|
|
222
|
+
|
|
223
|
+
Usage for undici global agent:
|
|
224
|
+
```js
|
|
225
|
+
import { setGlobalDispatcher, ProxyAgent } from 'undici'
|
|
226
|
+
|
|
227
|
+
const proxyAgent = new ProxyAgent('my.proxy.server')
|
|
228
|
+
setGlobalDispatcher(proxyAgent)
|
|
229
|
+
|
|
230
|
+
fastify.register(FastifyReplyFrom, {
|
|
231
|
+
base: 'http://localhost:3001/',
|
|
232
|
+
globalAgent: true
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Usage for http/https global agent:
|
|
237
|
+
```js
|
|
238
|
+
fastify.register(FastifyReplyFrom, {
|
|
239
|
+
base: 'http://localhost:3001/',
|
|
240
|
+
// http and https is allowed to use http.globalAgent or https.globalAgent
|
|
241
|
+
globalAgent: true,
|
|
242
|
+
http: {
|
|
243
|
+
}
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
220
247
|
---
|
|
221
248
|
|
|
222
249
|
#### `maxRetriesOn503`
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fp = require('fastify-plugin')
|
|
4
|
-
const lru = require('tiny-lru')
|
|
4
|
+
const { lru } = require('tiny-lru')
|
|
5
5
|
const querystring = require('fast-querystring')
|
|
6
6
|
const Stream = require('stream')
|
|
7
7
|
const buildRequest = require('./lib/request')
|
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
InternalServerError
|
|
22
22
|
} = require('./lib/errors')
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
25
25
|
const contentTypesToEncode = new Set([
|
|
26
26
|
'application/json',
|
|
27
27
|
...(opts.contentTypesToEncode || [])
|
|
@@ -33,12 +33,18 @@ module.exports = fp(function from (fastify, opts, next) {
|
|
|
33
33
|
|
|
34
34
|
const cache = opts.disableCache ? undefined : lru(opts.cacheURLs || 100)
|
|
35
35
|
const base = opts.base
|
|
36
|
-
const
|
|
36
|
+
const requestBuilt = buildRequest({
|
|
37
37
|
http: opts.http,
|
|
38
38
|
http2: opts.http2,
|
|
39
39
|
base,
|
|
40
|
-
undici: opts.undici
|
|
40
|
+
undici: opts.undici,
|
|
41
|
+
globalAgent: opts.globalAgent
|
|
41
42
|
})
|
|
43
|
+
if (requestBuilt instanceof Error) {
|
|
44
|
+
next(requestBuilt)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
const { request, close, retryOnError } = requestBuilt
|
|
42
48
|
const disableRequestLogging = opts.disableRequestLogging || false
|
|
43
49
|
|
|
44
50
|
fastify.decorateReply('from', function (source, opts) {
|
|
@@ -60,8 +66,9 @@ module.exports = fp(function from (fastify, opts, next) {
|
|
|
60
66
|
const dest = getUpstream(req, base)
|
|
61
67
|
let url
|
|
62
68
|
if (cache) {
|
|
63
|
-
|
|
64
|
-
cache.
|
|
69
|
+
const cacheKey = dest + source
|
|
70
|
+
url = cache.get(cacheKey) || buildURL(source, dest)
|
|
71
|
+
cache.set(cacheKey, url)
|
|
65
72
|
} else {
|
|
66
73
|
url = buildURL(source, dest)
|
|
67
74
|
}
|
|
@@ -234,7 +241,9 @@ function onErrorDefault (reply, { error }) {
|
|
|
234
241
|
}
|
|
235
242
|
|
|
236
243
|
function isFastifyMultipartRegistered (fastify) {
|
|
237
|
-
|
|
244
|
+
// TODO: remove fastify.hasContentTypeParser('multipart') in next major
|
|
245
|
+
// It is used to be compatible with @fastify/multipart@<=7.3.0
|
|
246
|
+
return (fastify.hasContentTypeParser('multipart') || fastify.hasContentTypeParser('multipart/form-data')) && fastify.hasRequestDecorator('multipart')
|
|
238
247
|
}
|
|
239
248
|
|
|
240
249
|
function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, maxRetriesOn503) {
|
|
@@ -274,3 +283,7 @@ function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, max
|
|
|
274
283
|
|
|
275
284
|
return requestRetry
|
|
276
285
|
}
|
|
286
|
+
|
|
287
|
+
module.exports = fastifyReplyFrom
|
|
288
|
+
module.exports.default = fastifyReplyFrom
|
|
289
|
+
module.exports.fastifyReplyFrom = fastifyReplyFrom
|
package/lib/request.js
CHANGED
|
@@ -41,21 +41,27 @@ function buildRequest (opts) {
|
|
|
41
41
|
const httpOpts = getHttpOpts(opts)
|
|
42
42
|
const baseUrl = opts.base && new URL(opts.base).origin
|
|
43
43
|
const undiciOpts = opts.undici || {}
|
|
44
|
+
const globalAgent = opts.globalAgent
|
|
44
45
|
let http2Client
|
|
45
46
|
let undiciAgent
|
|
46
47
|
let undiciInstance
|
|
47
48
|
let agents
|
|
48
49
|
|
|
49
50
|
if (isHttp2) {
|
|
50
|
-
if (!opts.base)
|
|
51
|
+
if (!opts.base) return new Error('Option base is required when http2 is true')
|
|
51
52
|
if (opts.base.startsWith('unix+')) {
|
|
52
|
-
|
|
53
|
+
return new Error('Unix socket destination is not supported when http2 is true')
|
|
53
54
|
}
|
|
54
|
-
} else {
|
|
55
|
+
} else if (!globalAgent) {
|
|
55
56
|
agents = httpOpts.agents || {
|
|
56
57
|
'http:': new http.Agent(httpOpts.agentOptions),
|
|
57
58
|
'https:': new https.Agent(httpOpts.agentOptions)
|
|
58
59
|
}
|
|
60
|
+
} else {
|
|
61
|
+
agents = {
|
|
62
|
+
'http:': http.globalAgent,
|
|
63
|
+
'https:': https.globalAgent
|
|
64
|
+
}
|
|
59
65
|
}
|
|
60
66
|
|
|
61
67
|
if (isHttp2) {
|
|
@@ -68,8 +74,10 @@ function buildRequest (opts) {
|
|
|
68
74
|
undiciInstance = new undici.Pool(protocol + '://localhost', undiciOpts)
|
|
69
75
|
} else if (isUndiciInstance(opts.undici)) {
|
|
70
76
|
undiciInstance = opts.undici
|
|
71
|
-
} else {
|
|
77
|
+
} else if (!globalAgent) {
|
|
72
78
|
undiciAgent = new undici.Agent(getUndiciOptions(opts.undici))
|
|
79
|
+
} else {
|
|
80
|
+
undiciAgent = undici.getGlobalDispatcher()
|
|
73
81
|
}
|
|
74
82
|
return { request: handleUndici, close, retryOnError: 'UND_ERR_SOCKET' }
|
|
75
83
|
} else {
|
|
@@ -77,6 +85,10 @@ function buildRequest (opts) {
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
function close () {
|
|
88
|
+
if (globalAgent) {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
if (hasUndiciOptions) {
|
|
81
93
|
undiciAgent && undiciAgent.destroy()
|
|
82
94
|
undiciInstance && undiciInstance.destroy()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/reply-from",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "forward your HTTP request to another server, for fastify",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/fastify/fastify-reply-from#readme",
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@fastify/formbody": "^7.0
|
|
32
|
-
"@fastify/multipart": "^7.
|
|
31
|
+
"@fastify/formbody": "^7.4.0",
|
|
32
|
+
"@fastify/multipart": "^7.4.0",
|
|
33
33
|
"@fastify/pre-commit": "^2.0.2",
|
|
34
|
-
"@sinonjs/fake-timers": "^
|
|
34
|
+
"@sinonjs/fake-timers": "^10.0.0",
|
|
35
35
|
"@types/node": "^18.0.0",
|
|
36
36
|
"@types/tap": "^15.0.7",
|
|
37
37
|
"fastify": "^4.0.2",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"split2": "^4.1.0",
|
|
47
47
|
"standard": "^17.0.0",
|
|
48
48
|
"tap": "^16.2.0",
|
|
49
|
-
"tsd": "^0.
|
|
49
|
+
"tsd": "^0.25.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@fastify/error": "^3.0.0",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"fast-querystring": "^1.0.0",
|
|
55
55
|
"fastify-plugin": "^4.0.0",
|
|
56
56
|
"pump": "^3.0.0",
|
|
57
|
-
"tiny-lru": "^
|
|
57
|
+
"tiny-lru": "^10.0.0",
|
|
58
58
|
"undici": "^5.5.1"
|
|
59
59
|
},
|
|
60
60
|
"pre-commit": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('..')
|
|
6
|
+
|
|
7
|
+
async function createTarget (i) {
|
|
8
|
+
const target = Fastify({
|
|
9
|
+
keepAliveTimeout: 1
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
target.get('/test', async () => {
|
|
13
|
+
return `Hello from target ${i}`
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
t.teardown(() => target.close())
|
|
17
|
+
await target.listen({ port: 3000 + i })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
t.plan(4)
|
|
21
|
+
|
|
22
|
+
async function run () {
|
|
23
|
+
await Promise.all([
|
|
24
|
+
createTarget(1),
|
|
25
|
+
createTarget(2)
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const instance = Fastify({
|
|
29
|
+
keepAliveTimeout: 1
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
instance.register(From, {
|
|
33
|
+
base: 'http://localhost',
|
|
34
|
+
http: true
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
instance.get('/', (req, reply) => {
|
|
38
|
+
const hostNumber = parseInt(req.headers['x-host-number'])
|
|
39
|
+
const port = 3000 + hostNumber
|
|
40
|
+
|
|
41
|
+
reply.from('/test', {
|
|
42
|
+
getUpstream () {
|
|
43
|
+
return `http://localhost:${port}`
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
t.teardown(() => instance.close())
|
|
49
|
+
await instance.listen({ port: 3000 })
|
|
50
|
+
|
|
51
|
+
const res1 = await instance.inject({
|
|
52
|
+
method: 'GET',
|
|
53
|
+
url: '/',
|
|
54
|
+
headers: {
|
|
55
|
+
'x-host-number': 1
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
t.equal(res1.statusCode, 200)
|
|
59
|
+
t.equal(res1.body, 'Hello from target 1')
|
|
60
|
+
|
|
61
|
+
const res2 = await instance.inject({
|
|
62
|
+
method: 'GET',
|
|
63
|
+
url: '/',
|
|
64
|
+
headers: {
|
|
65
|
+
'x-host-number': 2
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
t.equal(res2.statusCode, 200)
|
|
69
|
+
t.equal(res2.body, 'Hello from target 2')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
run()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('..')
|
|
6
|
+
const http = require('http')
|
|
7
|
+
const get = require('simple-get').concat
|
|
8
|
+
|
|
9
|
+
test('http global agent is used, but not destroyed', async (t) => {
|
|
10
|
+
http.globalAgent.destroy = () => {
|
|
11
|
+
t.fail()
|
|
12
|
+
}
|
|
13
|
+
const instance = Fastify()
|
|
14
|
+
t.teardown(instance.close.bind(instance))
|
|
15
|
+
instance.get('/', (request, reply) => {
|
|
16
|
+
reply.from()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const target = http.createServer((req, res) => {
|
|
20
|
+
t.pass('request proxied')
|
|
21
|
+
t.equal(req.method, 'GET')
|
|
22
|
+
t.equal(req.url, '/')
|
|
23
|
+
res.statusCode = 200
|
|
24
|
+
res.end()
|
|
25
|
+
})
|
|
26
|
+
t.teardown(target.close.bind(target))
|
|
27
|
+
|
|
28
|
+
const executionFlow = () => new Promise((resolve) => {
|
|
29
|
+
target.listen({ port: 0 }, (err) => {
|
|
30
|
+
t.error(err)
|
|
31
|
+
|
|
32
|
+
instance.register(From, {
|
|
33
|
+
base: `http://localhost:${target.address().port}`,
|
|
34
|
+
globalAgent: true,
|
|
35
|
+
http: {
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
instance.listen({ port: 0 }, (err) => {
|
|
40
|
+
t.error(err)
|
|
41
|
+
|
|
42
|
+
get(
|
|
43
|
+
`http://localhost:${instance.server.address().port}`,
|
|
44
|
+
(err, res) => {
|
|
45
|
+
t.error(err)
|
|
46
|
+
t.equal(res.statusCode, 200)
|
|
47
|
+
resolve()
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
await executionFlow()
|
|
55
|
+
|
|
56
|
+
target.close()
|
|
57
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('../index')
|
|
6
|
+
|
|
7
|
+
test('http2 invalid base', async (t) => {
|
|
8
|
+
const instance = Fastify()
|
|
9
|
+
|
|
10
|
+
await t.rejects(instance.register(From, {
|
|
11
|
+
http2: { requestTimeout: 100 }
|
|
12
|
+
}), new Error('Option base is required when http2 is true'))
|
|
13
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('../index')
|
|
6
|
+
|
|
7
|
+
test('throw an error if http2 is used with a Unix socket destination', async t => {
|
|
8
|
+
t.plan(1)
|
|
9
|
+
|
|
10
|
+
const instance = Fastify()
|
|
11
|
+
|
|
12
|
+
await t.rejects(instance.register(From, {
|
|
13
|
+
base: 'unix+http://localhost:1337',
|
|
14
|
+
http2: { requestTimeout: 100 }
|
|
15
|
+
}), new Error('Unix socket destination is not supported when http2 is true'))
|
|
16
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('..')
|
|
6
|
+
const https = require('https')
|
|
7
|
+
const get = require('simple-get').concat
|
|
8
|
+
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const certs = {
|
|
12
|
+
key: fs.readFileSync(path.join(__dirname, 'fixtures', 'fastify.key')),
|
|
13
|
+
cert: fs.readFileSync(path.join(__dirname, 'fixtures', 'fastify.cert'))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
test('https global agent is used, but not destroyed', async (t) => {
|
|
17
|
+
https.globalAgent.destroy = () => {
|
|
18
|
+
t.fail()
|
|
19
|
+
}
|
|
20
|
+
const instance = Fastify({
|
|
21
|
+
https: certs
|
|
22
|
+
})
|
|
23
|
+
t.teardown(instance.close.bind(instance))
|
|
24
|
+
instance.get('/', (request, reply) => {
|
|
25
|
+
reply.from()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const target = https.createServer(certs, (req, res) => {
|
|
29
|
+
t.pass('request proxied')
|
|
30
|
+
t.equal(req.method, 'GET')
|
|
31
|
+
t.equal(req.url, '/')
|
|
32
|
+
res.statusCode = 200
|
|
33
|
+
res.end()
|
|
34
|
+
})
|
|
35
|
+
t.teardown(target.close.bind(target))
|
|
36
|
+
|
|
37
|
+
const executionFlow = () => new Promise((resolve) => {
|
|
38
|
+
target.listen({ port: 0 }, (err) => {
|
|
39
|
+
t.error(err)
|
|
40
|
+
|
|
41
|
+
instance.register(From, {
|
|
42
|
+
base: `https://localhost:${target.address().port}`,
|
|
43
|
+
globalAgent: true,
|
|
44
|
+
http: {
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
instance.listen({ port: 0 }, (err) => {
|
|
49
|
+
t.error(err)
|
|
50
|
+
|
|
51
|
+
get(
|
|
52
|
+
{
|
|
53
|
+
url: `https://localhost:${instance.server.address().port}`,
|
|
54
|
+
rejectUnauthorized: false
|
|
55
|
+
},
|
|
56
|
+
(err, res) => {
|
|
57
|
+
t.error(err)
|
|
58
|
+
t.equal(res.statusCode, 200)
|
|
59
|
+
resolve()
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
await executionFlow()
|
|
67
|
+
|
|
68
|
+
target.close()
|
|
69
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const test = require('tap').test
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const From = require('../index')
|
|
6
|
+
const http = require('http')
|
|
7
|
+
const get = require('simple-get').concat
|
|
8
|
+
const { parse } = require('querystring')
|
|
9
|
+
|
|
10
|
+
test('with explicitly set content-type application/octet-stream', t => {
|
|
11
|
+
const instance = Fastify()
|
|
12
|
+
instance.register(From, {
|
|
13
|
+
contentTypesToEncode: ['application/octet-stream']
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
instance.addContentTypeParser(
|
|
17
|
+
'application/octet-stream',
|
|
18
|
+
{ parseAs: 'buffer', bodyLimit: 1000 },
|
|
19
|
+
(req, body, done) => done(null, parse(body.toString()))
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
t.plan(9)
|
|
23
|
+
t.teardown(instance.close.bind(instance))
|
|
24
|
+
|
|
25
|
+
const target = http.createServer((req, res) => {
|
|
26
|
+
t.pass('request proxied')
|
|
27
|
+
t.equal(req.method, 'POST')
|
|
28
|
+
t.equal(req.headers['content-type'], 'application/octet-stream')
|
|
29
|
+
let data = ''
|
|
30
|
+
req.setEncoding('utf8')
|
|
31
|
+
req.on('data', (d) => {
|
|
32
|
+
data += d
|
|
33
|
+
})
|
|
34
|
+
req.on('end', () => {
|
|
35
|
+
const str = data.toString()
|
|
36
|
+
t.same(JSON.parse(data), { some: 'info', another: 'detail' })
|
|
37
|
+
res.statusCode = 200
|
|
38
|
+
res.setHeader('content-type', 'application/octet-stream')
|
|
39
|
+
res.end(str)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
instance.post('/', (request, reply) => {
|
|
44
|
+
reply.from(`http://localhost:${target.address().port}`)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
t.teardown(target.close.bind(target))
|
|
48
|
+
|
|
49
|
+
instance.listen({ port: 0 }, (err) => {
|
|
50
|
+
t.error(err)
|
|
51
|
+
|
|
52
|
+
target.listen({ port: 0 }, (err) => {
|
|
53
|
+
t.error(err)
|
|
54
|
+
|
|
55
|
+
get({
|
|
56
|
+
url: `http://localhost:${instance.server.address().port}`,
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'content-type': 'application/octet-stream' },
|
|
59
|
+
body: 'some=info&another=detail'
|
|
60
|
+
}, (err, res, data) => {
|
|
61
|
+
t.error(err)
|
|
62
|
+
t.equal(res.headers['content-type'], 'application/octet-stream')
|
|
63
|
+
t.same(JSON.parse(data), { some: 'info', another: 'detail' })
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('with implicit content-type application/octet-stream', t => {
|
|
70
|
+
const instance = Fastify()
|
|
71
|
+
instance.register(From, {
|
|
72
|
+
contentTypesToEncode: ['application/octet-stream']
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
instance.addContentTypeParser(
|
|
76
|
+
'application/octet-stream',
|
|
77
|
+
{ parseAs: 'buffer', bodyLimit: 1000 },
|
|
78
|
+
(req, body, done) => done(null, parse(body.toString()))
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
instance.addContentTypeParser(
|
|
82
|
+
'*',
|
|
83
|
+
{ parseAs: 'buffer', bodyLimit: 1000 },
|
|
84
|
+
(req, body, done) => done(null, parse(body.toString()))
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
t.plan(9)
|
|
88
|
+
t.teardown(instance.close.bind(instance))
|
|
89
|
+
|
|
90
|
+
const target = http.createServer((req, res) => {
|
|
91
|
+
t.pass('request proxied')
|
|
92
|
+
t.equal(req.method, 'POST')
|
|
93
|
+
t.equal(req.headers['content-type'], 'application/octet-stream')
|
|
94
|
+
let data = ''
|
|
95
|
+
req.setEncoding('utf8')
|
|
96
|
+
req.on('data', (d) => {
|
|
97
|
+
data += d
|
|
98
|
+
})
|
|
99
|
+
req.on('end', () => {
|
|
100
|
+
const str = data.toString()
|
|
101
|
+
t.same(JSON.parse(data), { some: 'info', another: 'detail' })
|
|
102
|
+
res.statusCode = 200
|
|
103
|
+
res.setHeader('content-type', 'application/octet-stream')
|
|
104
|
+
res.end(str)
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
instance.post('/', (request, reply) => {
|
|
109
|
+
reply.from(`http://localhost:${target.address().port}`)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
t.teardown(target.close.bind(target))
|
|
113
|
+
|
|
114
|
+
instance.listen({ port: 0 }, (err) => {
|
|
115
|
+
t.error(err)
|
|
116
|
+
|
|
117
|
+
target.listen({ port: 0 }, (err) => {
|
|
118
|
+
t.error(err)
|
|
119
|
+
|
|
120
|
+
get({
|
|
121
|
+
url: `http://localhost:${instance.server.address().port}`,
|
|
122
|
+
method: 'POST',
|
|
123
|
+
body: 'some=info&another=detail'
|
|
124
|
+
}, (err, res, data) => {
|
|
125
|
+
t.error(err)
|
|
126
|
+
t.equal(res.headers['content-type'], 'application/octet-stream')
|
|
127
|
+
t.same(JSON.parse(data), { some: 'info', another: 'detail' })
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { test } = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const http = require('http')
|
|
6
|
+
const get = require('simple-get').concat
|
|
7
|
+
const undici = require('undici')
|
|
8
|
+
const From = require('..')
|
|
9
|
+
|
|
10
|
+
test('undici global agent is used, but not destroyed', async (t) => {
|
|
11
|
+
const mockAgent = new undici.Agent()
|
|
12
|
+
mockAgent.destroy = () => {
|
|
13
|
+
t.fail()
|
|
14
|
+
}
|
|
15
|
+
undici.setGlobalDispatcher(mockAgent)
|
|
16
|
+
const instance = Fastify()
|
|
17
|
+
|
|
18
|
+
t.teardown(instance.close.bind(instance))
|
|
19
|
+
|
|
20
|
+
const target = http.createServer((req, res) => {
|
|
21
|
+
res.statusCode = 200
|
|
22
|
+
res.end()
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
instance.get('/', (request, reply) => {
|
|
26
|
+
reply.from()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
t.teardown(target.close.bind(target))
|
|
30
|
+
|
|
31
|
+
const executionFlow = () => new Promise((resolve) => {
|
|
32
|
+
target.listen({ port: 0 }, (err) => {
|
|
33
|
+
t.error(err)
|
|
34
|
+
|
|
35
|
+
instance.register(From, {
|
|
36
|
+
base: `http://localhost:${target.address().port}`,
|
|
37
|
+
globalAgent: true
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
instance.listen({ port: 0 }, (err) => {
|
|
41
|
+
t.error(err)
|
|
42
|
+
|
|
43
|
+
get(
|
|
44
|
+
`http://localhost:${instance.server.address().port}`,
|
|
45
|
+
(err, res) => {
|
|
46
|
+
t.error(err)
|
|
47
|
+
t.equal(res.statusCode, 200)
|
|
48
|
+
|
|
49
|
+
get(
|
|
50
|
+
`http://localhost:${instance.server.address().port}`,
|
|
51
|
+
(err, res) => {
|
|
52
|
+
t.error(err)
|
|
53
|
+
t.equal(res.statusCode, 200)
|
|
54
|
+
resolve()
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
await executionFlow()
|
|
64
|
+
|
|
65
|
+
target.close()
|
|
66
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const test = require('tap').test
|
|
4
|
+
const filterPseudoHeaders = require('../lib/utils').filterPseudoHeaders
|
|
5
|
+
|
|
6
|
+
test('filterPseudoHeaders', t => {
|
|
7
|
+
t.plan(1)
|
|
8
|
+
const headers = {
|
|
9
|
+
accept: '*/*',
|
|
10
|
+
'Content-Type': 'text/html; charset=UTF-8',
|
|
11
|
+
':method': 'GET'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
t.strictSame(filterPseudoHeaders(headers), {
|
|
15
|
+
accept: '*/*',
|
|
16
|
+
'content-type': 'text/html; charset=UTF-8'
|
|
17
|
+
})
|
|
18
|
+
})
|
package/types/index.d.ts
CHANGED
|
@@ -30,68 +30,77 @@ import {
|
|
|
30
30
|
SecureClientSessionOptions,
|
|
31
31
|
} from "http2";
|
|
32
32
|
import { Pool } from 'undici'
|
|
33
|
-
type QueryStringFunction = (search: string | undefined, reqUrl: string) => string;
|
|
34
|
-
export interface FastifyReplyFromHooks {
|
|
35
|
-
queryString?: { [key: string]: unknown } | QueryStringFunction;
|
|
36
|
-
contentType?: string;
|
|
37
|
-
onResponse?: (
|
|
38
|
-
request: FastifyRequest<RequestGenericInterface, RawServerBase>,
|
|
39
|
-
reply: FastifyReply<RawServerBase>,
|
|
40
|
-
res: RawReplyDefaultExpression<RawServerBase>
|
|
41
|
-
) => void;
|
|
42
|
-
onError?: (
|
|
43
|
-
reply: FastifyReply<RawServerBase>,
|
|
44
|
-
error: { error: Error }
|
|
45
|
-
) => void;
|
|
46
|
-
body?: unknown;
|
|
47
|
-
rewriteHeaders?: (
|
|
48
|
-
headers: Http2IncomingHttpHeaders | IncomingHttpHeaders,
|
|
49
|
-
req?: Http2ServerRequest | IncomingMessage
|
|
50
|
-
) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
|
|
51
|
-
rewriteRequestHeaders?: (
|
|
52
|
-
req: Http2ServerRequest | IncomingMessage,
|
|
53
|
-
headers: Http2IncomingHttpHeaders | IncomingHttpHeaders
|
|
54
|
-
) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
|
|
55
|
-
getUpstream?: (
|
|
56
|
-
req: Http2ServerRequest | IncomingMessage,
|
|
57
|
-
base: string
|
|
58
|
-
) => string;
|
|
59
|
-
}
|
|
60
33
|
|
|
61
34
|
declare module "fastify" {
|
|
62
35
|
interface FastifyReply {
|
|
63
36
|
from(
|
|
64
37
|
source?: string,
|
|
65
|
-
opts?: FastifyReplyFromHooks
|
|
66
|
-
):
|
|
38
|
+
opts?: fastifyReplyFrom.FastifyReplyFromHooks
|
|
39
|
+
): this;
|
|
67
40
|
}
|
|
68
41
|
}
|
|
69
42
|
|
|
70
|
-
|
|
71
|
-
sessionTimeout?: number;
|
|
72
|
-
requestTimeout?: number;
|
|
73
|
-
sessionOptions?: ClientSessionOptions | SecureClientSessionOptions;
|
|
74
|
-
requestOptions?: ClientSessionRequestOptions;
|
|
75
|
-
}
|
|
43
|
+
type FastifyReplyFrom = FastifyPluginCallback<fastifyReplyFrom.FastifyReplyFromOptions>
|
|
76
44
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
45
|
+
declare namespace fastifyReplyFrom {
|
|
46
|
+
type QueryStringFunction = (search: string | undefined, reqUrl: string) => string;
|
|
47
|
+
export interface FastifyReplyFromHooks {
|
|
48
|
+
queryString?: { [key: string]: unknown } | QueryStringFunction;
|
|
49
|
+
contentType?: string;
|
|
50
|
+
onResponse?: (
|
|
51
|
+
request: FastifyRequest<RequestGenericInterface, RawServerBase>,
|
|
52
|
+
reply: FastifyReply<RawServerBase>,
|
|
53
|
+
res: RawReplyDefaultExpression<RawServerBase>
|
|
54
|
+
) => void;
|
|
55
|
+
onError?: (
|
|
56
|
+
reply: FastifyReply<RawServerBase>,
|
|
57
|
+
error: { error: Error }
|
|
58
|
+
) => void;
|
|
59
|
+
body?: unknown;
|
|
60
|
+
rewriteHeaders?: (
|
|
61
|
+
headers: Http2IncomingHttpHeaders | IncomingHttpHeaders,
|
|
62
|
+
req?: Http2ServerRequest | IncomingMessage
|
|
63
|
+
) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
|
|
64
|
+
rewriteRequestHeaders?: (
|
|
65
|
+
req: Http2ServerRequest | IncomingMessage,
|
|
66
|
+
headers: Http2IncomingHttpHeaders | IncomingHttpHeaders
|
|
67
|
+
) => Http2IncomingHttpHeaders | IncomingHttpHeaders;
|
|
68
|
+
getUpstream?: (
|
|
69
|
+
req: Http2ServerRequest | IncomingMessage,
|
|
70
|
+
base: string
|
|
71
|
+
) => string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
interface Http2Options {
|
|
75
|
+
sessionTimeout?: number;
|
|
76
|
+
requestTimeout?: number;
|
|
77
|
+
sessionOptions?: ClientSessionOptions | SecureClientSessionOptions;
|
|
78
|
+
requestOptions?: ClientSessionRequestOptions;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface HttpOptions {
|
|
82
|
+
agentOptions?: AgentOptions | SecureAgentOptions;
|
|
83
|
+
requestOptions?: RequestOptions | SecureRequestOptions;
|
|
84
|
+
agents?: { 'http:': Agent, 'https:': SecureAgent }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface FastifyReplyFromOptions {
|
|
88
|
+
base?: string;
|
|
89
|
+
cacheURLs?: number;
|
|
90
|
+
disableCache?: boolean;
|
|
91
|
+
http?: HttpOptions;
|
|
92
|
+
http2?: Http2Options | boolean;
|
|
93
|
+
undici?: Pool.Options;
|
|
94
|
+
contentTypesToEncode?: string[];
|
|
95
|
+
retryMethods?: (HTTPMethods | 'TRACE')[];
|
|
96
|
+
maxRetriesOn503?: number;
|
|
97
|
+
disableRequestLogging?: boolean;
|
|
98
|
+
globalAgent?: boolean;
|
|
99
|
+
}
|
|
82
100
|
|
|
83
|
-
export
|
|
84
|
-
|
|
85
|
-
cacheURLs?: number;
|
|
86
|
-
disableCache?: boolean;
|
|
87
|
-
http?: HttpOptions;
|
|
88
|
-
http2?: Http2Options | boolean;
|
|
89
|
-
undici?: Pool.Options;
|
|
90
|
-
contentTypesToEncode?: string[];
|
|
91
|
-
retryMethods?: (HTTPMethods | 'TRACE')[];
|
|
92
|
-
maxRetriesOn503?: number;
|
|
93
|
-
disableRequestLogging?: boolean;
|
|
101
|
+
export const fastifyReplyFrom: FastifyReplyFrom
|
|
102
|
+
export { fastifyReplyFrom as default }
|
|
94
103
|
}
|
|
95
104
|
|
|
96
|
-
declare
|
|
97
|
-
export
|
|
105
|
+
declare function fastifyReplyFrom(...params: Parameters<FastifyReplyFrom>): ReturnType<FastifyReplyFrom>
|
|
106
|
+
export = fastifyReplyFrom
|
package/types/index.test-d.ts
CHANGED
|
@@ -45,6 +45,7 @@ const fullOptions: FastifyReplyFromOptions = {
|
|
|
45
45
|
retryMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'],
|
|
46
46
|
maxRetriesOn503: 10,
|
|
47
47
|
disableRequestLogging: false,
|
|
48
|
+
globalAgent: false,
|
|
48
49
|
};
|
|
49
50
|
tap.autoend(false);
|
|
50
51
|
|
|
@@ -60,7 +61,7 @@ async function main() {
|
|
|
60
61
|
server.register(replyFrom, fullOptions);
|
|
61
62
|
|
|
62
63
|
server.get("/v1", (request, reply) => {
|
|
63
|
-
reply.from();
|
|
64
|
+
expectType<FastifyReply>(reply.from());
|
|
64
65
|
});
|
|
65
66
|
server.get("/v3", (request, reply) => {
|
|
66
67
|
reply.from("/v3", {
|