Skip to content

Commit

Permalink
feat: added option for undici proxy agent (#363)
Browse files Browse the repository at this point in the history
Closes #159

Signed-off-by: Jamie King <[email protected]>
  • Loading branch information
10xLaCroixDrinker authored Apr 22, 2024
1 parent 25ae131 commit 9a6d1ed
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 5 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:
Expand Down
13 changes: 12 additions & 1 deletion lib/request.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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",
Expand Down
79 changes: 79 additions & 0 deletions test/undici-proxy-agent.test.js
Original file line number Diff line number Diff line change
@@ -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))
})
}
3 changes: 2 additions & 1 deletion types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
RequestOptions as SecureRequestOptions
} from "https";
import { Pool } from 'undici';
import { ProxyAgent } from 'undici';

declare module "fastify" {
interface FastifyReply {
Expand Down Expand Up @@ -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;
Expand Down
7 changes: 6 additions & 1 deletion types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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<FastifyReply>(reply.from());
});
Expand Down

0 comments on commit 9a6d1ed

Please sign in to comment.