Skip to content

Commit 90d45fa

Browse files
Add clientFactory option to ProxyAgent (#2003)
* Add clientFactory option to ProxyAgent The default use of a Client means that HTTP CONNECT requests to the Proxy will block successive requests. Giving consumers the option to supply a factory allows them use a Pool which ensures that these requests will not block. fixes: #2001 * fixup! Use pool by default and change clientFactory return type * Add clientFactory to ProxyAgent docs --------- Co-authored-by: Andrew Fecenko <afecenko@atlassian.com>
1 parent 1bbcfa2 commit 90d45fa

File tree

5 files changed

+59
-4
lines changed

5 files changed

+59
-4
lines changed

docs/api/ProxyAgent.md

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Extends: [`AgentOptions`](Agent.md#parameter-agentoptions)
1919
* **uri** `string` (required) - It can be passed either by a string or a object containing `uri` as string.
2020
* **token** `string` (optional) - It can be passed by a string of token for authentication.
2121
* **auth** `string` (**deprecated**) - Use token.
22+
* **clientFactory** `(origin: URL, opts: Object) => Dispatcher` - Default: `(origin, opts) => new Pool(origin, opts)`
2223

2324
Examples:
2425

lib/proxy-agent.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
const { kProxy, kClose, kDestroy, kInterceptors } = require('./core/symbols')
44
const { URL } = require('url')
55
const Agent = require('./agent')
6-
const Client = require('./client')
6+
const Pool = require('./pool')
77
const DispatcherBase = require('./dispatcher-base')
88
const { InvalidArgumentError, RequestAbortedError } = require('./core/errors')
99
const buildConnector = require('./core/connect')
@@ -34,6 +34,10 @@ function buildProxyOptions (opts) {
3434
}
3535
}
3636

37+
function defaultFactory (origin, opts) {
38+
return new Pool(origin, opts)
39+
}
40+
3741
class ProxyAgent extends DispatcherBase {
3842
constructor (opts) {
3943
super(opts)
@@ -51,6 +55,12 @@ class ProxyAgent extends DispatcherBase {
5155
throw new InvalidArgumentError('Proxy opts.uri is mandatory')
5256
}
5357

58+
const { clientFactory = defaultFactory } = opts
59+
60+
if (typeof clientFactory !== 'function') {
61+
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
62+
}
63+
5464
this[kRequestTls] = opts.requestTls
5565
this[kProxyTls] = opts.proxyTls
5666
this[kProxyHeaders] = opts.headers || {}
@@ -69,7 +79,7 @@ class ProxyAgent extends DispatcherBase {
6979

7080
const connect = buildConnector({ ...opts.proxyTls })
7181
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
72-
this[kClient] = new Client(resolvedUrl, { connect })
82+
this[kClient] = clientFactory(resolvedUrl, { connect })
7383
this[kAgent] = new Agent({
7484
...opts,
7585
connect: async (opts, callback) => {

test/proxy-agent.js

+40
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { nodeMajor } = require('../lib/core/util')
77
const { readFileSync } = require('fs')
88
const { join } = require('path')
99
const ProxyAgent = require('../lib/proxy-agent')
10+
const Pool = require('../lib/pool')
1011
const { createServer } = require('http')
1112
const https = require('https')
1213
const proxy = require('proxy')
@@ -72,6 +73,45 @@ test('use proxy-agent to connect through proxy', async (t) => {
7273
proxyAgent.close()
7374
})
7475

76+
test('use proxy agent to connect through proxy using Pool', async (t) => {
77+
t.plan(3)
78+
const server = await buildServer()
79+
const proxy = await buildProxy()
80+
let resolveFirstConnect
81+
let connectCount = 0
82+
83+
proxy.authenticate = async function (req, fn) {
84+
if (++connectCount === 2) {
85+
t.pass('second connect should arrive while first is still inflight')
86+
resolveFirstConnect()
87+
fn(null, true)
88+
} else {
89+
await new Promise((resolve) => {
90+
resolveFirstConnect = resolve
91+
})
92+
fn(null, true)
93+
}
94+
}
95+
96+
server.on('request', (req, res) => {
97+
res.end()
98+
})
99+
100+
const serverUrl = `http://localhost:${server.address().port}`
101+
const proxyUrl = `http://localhost:${proxy.address().port}`
102+
const clientFactory = (url, options) => {
103+
return new Pool(url, options)
104+
}
105+
const proxyAgent = new ProxyAgent({ auth: Buffer.from('user:pass').toString('base64'), uri: proxyUrl, clientFactory })
106+
const firstRequest = request(`${serverUrl}`, { dispatcher: proxyAgent })
107+
const secondRequest = await request(`${serverUrl}`, { dispatcher: proxyAgent })
108+
t.equal((await firstRequest).statusCode, 200)
109+
t.equal(secondRequest.statusCode, 200)
110+
server.close()
111+
proxy.close()
112+
proxyAgent.close()
113+
})
114+
75115
test('use proxy-agent to connect through proxy using path with params', async (t) => {
76116
t.plan(6)
77117
const server = await buildServer()

test/types/proxy-agent.test-d.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expectAssignable } from 'tsd'
22
import { URL } from 'url'
3-
import { ProxyAgent, setGlobalDispatcher, getGlobalDispatcher, Agent } from '../..'
3+
import { ProxyAgent, setGlobalDispatcher, getGlobalDispatcher, Agent, Pool } from '../..'
44

55
expectAssignable<ProxyAgent>(new ProxyAgent(''))
66
expectAssignable<ProxyAgent>(new ProxyAgent({ uri: '' }))
@@ -25,7 +25,8 @@ expectAssignable<ProxyAgent>(
2525
cert: '',
2626
servername: '',
2727
timeout: 1
28-
}
28+
},
29+
clientFactory: (origin: URL, opts: object) => new Pool(origin, opts)
2930
})
3031
)
3132

types/proxy-agent.d.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import Agent from './agent'
22
import buildConnector from './connector';
3+
import Client from './client'
34
import Dispatcher from './dispatcher'
45
import { IncomingHttpHeaders } from './header'
6+
import Pool from './pool'
57

68
export default ProxyAgent
79

@@ -23,5 +25,6 @@ declare namespace ProxyAgent {
2325
headers?: IncomingHttpHeaders;
2426
requestTls?: buildConnector.BuildOptions;
2527
proxyTls?: buildConnector.BuildOptions;
28+
clientFactory?(origin: URL, opts: object): Dispatcher;
2629
}
2730
}

0 commit comments

Comments
 (0)