@fastify/reply-from 8.3.1 → 8.4.1
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/README.md +30 -2
- package/index.js +5 -2
- package/lib/request.js +16 -4
- package/lib/utils.js +26 -0
- package/package.json +4 -4
- package/test/http-global-agent.js +57 -0
- package/test/https-global-agent.js +69 -0
- package/test/undici-chaining.js +57 -0
- package/test/undici-global-agent.js +66 -0
- package/types/index.d.ts +1 -0
- package/types/index.test-d.ts +1 -0
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
|
}
|
|
@@ -216,6 +216,34 @@ By default: `['GET', 'HEAD', 'OPTIONS', 'TRACE' ]`
|
|
|
216
216
|
|
|
217
217
|
This plugin will always retry on 503 errors, _unless_ `retryMethods` does not contain `GET`.
|
|
218
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
|
+
|
|
219
247
|
---
|
|
220
248
|
|
|
221
249
|
#### `maxRetriesOn503`
|
package/index.js
CHANGED
|
@@ -37,7 +37,8 @@ const fastifyReplyFrom = fp(function from (fastify, opts, next) {
|
|
|
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
|
})
|
|
42
43
|
if (requestBuilt instanceof Error) {
|
|
43
44
|
next(requestBuilt)
|
|
@@ -240,7 +241,9 @@ function onErrorDefault (reply, { error }) {
|
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
function isFastifyMultipartRegistered (fastify) {
|
|
243
|
-
|
|
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')
|
|
244
247
|
}
|
|
245
248
|
|
|
246
249
|
function createRequestRetry (requestImpl, reply, retriesCount, retryOnError, maxRetriesOn503) {
|
package/lib/request.js
CHANGED
|
@@ -5,7 +5,7 @@ const querystring = require('querystring')
|
|
|
5
5
|
const eos = require('end-of-stream')
|
|
6
6
|
const pump = require('pump')
|
|
7
7
|
const undici = require('undici')
|
|
8
|
-
const { stripHttp1ConnectionHeaders } = require('./utils')
|
|
8
|
+
const { patchUndiciHeaders, stripHttp1ConnectionHeaders } = require('./utils')
|
|
9
9
|
const http2 = require('http2')
|
|
10
10
|
|
|
11
11
|
const {
|
|
@@ -41,6 +41,7 @@ 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
|
|
@@ -51,11 +52,16 @@ function buildRequest (opts) {
|
|
|
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()
|
|
@@ -146,7 +158,7 @@ function buildRequest (opts) {
|
|
|
146
158
|
// using delete, otherwise it will render as an empty string
|
|
147
159
|
delete res.headers['transfer-encoding']
|
|
148
160
|
|
|
149
|
-
done(null, { statusCode: res.statusCode, headers: res.headers, stream: res.body })
|
|
161
|
+
done(null, { statusCode: res.statusCode, headers: patchUndiciHeaders(res.headers), stream: res.body })
|
|
150
162
|
})
|
|
151
163
|
}
|
|
152
164
|
|
package/lib/utils.js
CHANGED
|
@@ -12,6 +12,31 @@ function filterPseudoHeaders (headers) {
|
|
|
12
12
|
return dest
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
// http requires the header to be encoded with latin1
|
|
16
|
+
// undici will convert the latin1 buffer to utf8
|
|
17
|
+
// https://github.com/nodejs/undici/blob/2b260c997ad4efe4ed2064b264b4b546a59e7a67/lib/core/util.js#L216-L229
|
|
18
|
+
// after chaining, the header will be serialised using wrong encoding
|
|
19
|
+
// Buffer.from('', 'latin1').toString('utf8') applied
|
|
20
|
+
//
|
|
21
|
+
// in order to persist the encoding, always encode it
|
|
22
|
+
// back to latin1
|
|
23
|
+
function patchUndiciHeaders (headers) {
|
|
24
|
+
const headersKeys = Object.keys(headers)
|
|
25
|
+
const dist = {}
|
|
26
|
+
|
|
27
|
+
let header
|
|
28
|
+
let i
|
|
29
|
+
|
|
30
|
+
for (i = 0; i < headersKeys.length; i++) {
|
|
31
|
+
header = headersKeys[i]
|
|
32
|
+
if (header.charCodeAt(0) !== 58) { // fast path for indexOf(':') === 0
|
|
33
|
+
dist[header] = Buffer.from(headers[header]).toString('latin1')
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return dist
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
function copyHeaders (headers, reply) {
|
|
16
41
|
const headersKeys = Object.keys(headers)
|
|
17
42
|
|
|
@@ -74,6 +99,7 @@ function buildURL (source, reqBase) {
|
|
|
74
99
|
}
|
|
75
100
|
|
|
76
101
|
module.exports = {
|
|
102
|
+
patchUndiciHeaders,
|
|
77
103
|
copyHeaders,
|
|
78
104
|
stripHttp1ConnectionHeaders,
|
|
79
105
|
filterPseudoHeaders,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fastify/reply-from",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.1",
|
|
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,8 +28,8 @@
|
|
|
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
34
|
"@sinonjs/fake-timers": "^10.0.0",
|
|
35
35
|
"@types/node": "^18.0.0",
|
|
@@ -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",
|
|
@@ -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,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,57 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const t = require('tap')
|
|
4
|
+
const Fastify = require('fastify')
|
|
5
|
+
const get = require('simple-get').concat
|
|
6
|
+
const From = require('..')
|
|
7
|
+
|
|
8
|
+
const header = 'attachment; filename="år.pdf"'
|
|
9
|
+
|
|
10
|
+
t.plan(6)
|
|
11
|
+
|
|
12
|
+
const instance = Fastify()
|
|
13
|
+
t.teardown(instance.close.bind(instance))
|
|
14
|
+
const proxy1 = Fastify()
|
|
15
|
+
t.teardown(proxy1.close.bind(proxy1))
|
|
16
|
+
const proxy2 = Fastify()
|
|
17
|
+
t.teardown(proxy2.close.bind(proxy2))
|
|
18
|
+
|
|
19
|
+
instance.get('/', (request, reply) => {
|
|
20
|
+
reply.header('content-disposition', header).send('OK')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
proxy1.register(From, {
|
|
24
|
+
undici: {
|
|
25
|
+
keepAliveMaxTimeout: 10
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
proxy1.get('/', (request, reply) => {
|
|
29
|
+
return reply.from(`http://localhost:${instance.server.address().port}`)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
proxy2.register(From, {
|
|
33
|
+
undici: {
|
|
34
|
+
keepAliveMaxTimeout: 10
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
proxy2.get('/', (request, reply) => {
|
|
38
|
+
return reply.from(`http://localhost:${proxy1.server.address().port}`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
instance.listen({ port: 0 }, err => {
|
|
42
|
+
t.error(err)
|
|
43
|
+
|
|
44
|
+
proxy1.listen({ port: 0 }, err => {
|
|
45
|
+
t.error(err)
|
|
46
|
+
|
|
47
|
+
proxy2.listen({ port: 0 }, err => {
|
|
48
|
+
t.error(err)
|
|
49
|
+
|
|
50
|
+
get(`http://localhost:${proxy2.server.address().port}`, (err, res, data) => {
|
|
51
|
+
t.error(err)
|
|
52
|
+
t.equal(res.statusCode, 200)
|
|
53
|
+
t.equal(data.toString(), 'OK')
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -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
|
+
})
|
package/types/index.d.ts
CHANGED