diff --git a/README.md b/README.md index b5a4224b..96c518ce 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ proxy.register(require('@fastify/reply-from'), { #### `undici` -By default, [undici](https://github.com/mcollina/undici) will be used to perform the HTTP/1.1 +By default, [undici](https://github.com/nodejs/undici) will be used to perform the HTTP/1.1 requests. Enabling this flag should guarantee 20-50% more throughput. @@ -102,6 +102,18 @@ proxy.register(require('@fastify/reply-from'), { } }) ``` + +You can also include a proxy for the undici client: + +```js +proxy.register(require('@fastify/reply-from'), { + base: 'http://localhost:3001/', + undici: { + proxy: 'http://my.proxy.server:8080', + } +}) +``` + See undici own options for more configurations. You can also pass the plugin a custom instance: diff --git a/lib/request.js b/lib/request.js index ca7b4b88..ef5fa909 100644 --- a/lib/request.js +++ b/lib/request.js @@ -83,7 +83,11 @@ function buildRequest (opts) { } else if (isUndiciInstance(opts.undici)) { undiciInstance = opts.undici } else if (!globalAgent) { - undiciAgent = new undici.Agent(getUndiciOptions(opts.undici)) + if (undiciOpts.proxy) { + undiciAgent = new undici.ProxyAgent(getUndiciProxyOptions(opts.undici)) + } else { + undiciAgent = new undici.Agent(getUndiciOptions(opts.undici)) + } } else { undiciAgent = undici.getGlobalDispatcher() } @@ -304,6 +308,13 @@ function getAgentOptions (opts) { } } +function getUndiciProxyOptions ({ proxy, ...opts }) { + if (typeof proxy === 'string' || proxy instanceof URL) { + return getUndiciOptions({ uri: proxy, ...opts }) + } + return getUndiciOptions({ ...proxy, ...opts }) +} + function getUndiciOptions (opts = {}) { const res = { pipelining: 1, diff --git a/package.json b/package.json index 2839c687..c47a974f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "types": "types/index.d.ts", "scripts": { "lint": "standard | snazzy", - "lint:fix": "standard --fix", + "lint:fix": "standard --fix | snazzy", "test": "npm run test:unit && npm run test:typescript", "test:unit": "tap", "test:typescript": "tsd" @@ -41,6 +41,7 @@ "h2url": "^0.2.0", "msgpack5": "^6.0.1", "nock": "^13.2.6", + "proxy": "^2.1.1", "proxyquire": "^2.1.3", "semver": "^7.5.1", "simple-get": "^4.0.1", diff --git a/test/undici-proxy-agent.test.js b/test/undici-proxy-agent.test.js new file mode 100644 index 00000000..37e04f78 --- /dev/null +++ b/test/undici-proxy-agent.test.js @@ -0,0 +1,79 @@ +'use strict' + +const { test } = require('tap') +const { createServer } = require('node:http') +const Fastify = require('fastify') +const get = require('simple-get').concat +const { createProxy } = require('proxy') +const From = require('..') + +const configFormat = { + string: (value) => value, + 'url instance': (value) => new URL(value), + object: (value) => ({ uri: value }) +} + +for (const [description, format] of Object.entries(configFormat)) { + test(`use undici ProxyAgent to connect through proxy - configured via ${description}`, async (t) => { + t.plan(5) + const target = await buildServer() + const proxy = await buildProxy() + t.teardown(target.close.bind(target)) + t.teardown(proxy.close.bind(proxy)) + + const targetUrl = `http://localhost:${target.address().port}` + const proxyUrl = `http://localhost:${proxy.address().port}` + + proxy.on('connect', () => { + t.ok(true, 'should connect to proxy') + }) + + target.on('request', (req, res) => { + res.setHeader('content-type', 'application/json') + res.end(JSON.stringify({ hello: 'world' })) + }) + + const instance = Fastify() + t.teardown(instance.close.bind(instance)) + + instance.register(From, { + base: targetUrl, + undici: { + proxy: format(proxyUrl) + } + }) + + instance.get('/', (request, reply) => { + reply.from() + }) + + const executionFlow = () => new Promise((resolve) => { + instance.listen({ port: 0 }, err => { + t.error(err) + + get(`http://localhost:${instance.server.address().port}`, (err, res, data) => { + t.error(err) + t.same(res.statusCode, 200) + t.match(JSON.parse(data.toString()), { hello: 'world' }) + resolve() + }) + }) + }) + + await executionFlow() + }) +} + +function buildServer () { + return new Promise((resolve) => { + const server = createServer() + server.listen(0, () => resolve(server)) + }) +} + +function buildProxy () { + return new Promise((resolve) => { + const server = createProxy(createServer()) + server.listen(0, () => resolve(server)) + }) +} diff --git a/types/index.d.ts b/types/index.d.ts index 7ccb2fd9..04cc99f9 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -28,6 +28,7 @@ import { RequestOptions as SecureRequestOptions } from "https"; import { Pool } from 'undici'; +import { ProxyAgent } from 'undici'; declare module "fastify" { interface FastifyReply { @@ -103,7 +104,7 @@ declare namespace fastifyReplyFrom { disableCache?: boolean; http?: HttpOptions; http2?: Http2Options | boolean; - undici?: Pool.Options; + undici?: Pool.Options & { proxy?: string | URL | ProxyAgent.Options }; contentTypesToEncode?: string[]; retryMethods?: (HTTPMethods | 'TRACE')[]; maxRetriesOn503?: number; diff --git a/types/index.test-d.ts b/types/index.test-d.ts index f814949a..91dd7d05 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -38,7 +38,8 @@ const fullOptions: FastifyReplyFromOptions = { disableCache: false, undici: { connections: 100, - pipelining: 10 + pipelining: 10, + proxy: 'http://example2.com:8080' }, contentTypesToEncode: ['application/x-www-form-urlencoded'], retryMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'], @@ -60,6 +61,10 @@ async function main() { server.register(replyFrom, fullOptions); + server.register(replyFrom, { undici: { proxy: new URL('http://example2.com:8080') } }); + + server.register(replyFrom, { undici: { proxy: { uri: 'http://example2.com:8080' } } }); + server.get("/v1", (request, reply) => { expectType(reply.from()); });