'use strict'
const { tspl } = require('@matteo.collina/tspl')
const assert = require('node:assert')
const { test, after } = require('node:test')
const diagnosticsChannel = require('node:diagnostics_channel')
const { request, fetch, Headers, setGlobalDispatcher, getGlobalDispatcher } = require('..')
const { InvalidArgumentError, ConnectTimeoutError, SecureProxyConnectionError } = require('../lib/core/errors')
const ProxyAgent = require('../lib/dispatcher/proxy-agent')
const Pool = require('../lib/dispatcher/pool')
const { createServer } = require('node:http')
const https = require('node:https')
const http2 = require('node:http2')
const net = require('node:net')
const { Socket } = require('node:net')
const { createProxy } = require('proxy')
const certs = (() => {
const forge = require('node-forge')
const createCert = (cn, issuer, keyLength = 2048) => {
const keys = forge.pki.rsa.generateKeyPair(keyLength)
const cert = forge.pki.createCertificate()
cert.publicKey = keys.publicKey
cert.serialNumber = '' + Date.now()
cert.validity.notBefore = new Date()
cert.validity.notAfter = new Date()
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10)
const attrs = [{
name: 'commonName',
value: cn
}]
cert.setSubject(attrs)
const isCa = issuer === undefined
cert.setExtensions([{
name: 'basicConstraints',
cA: isCa
}, {
name: 'keyUsage',
keyCertSign: true,
digitalSignature: true,
nonRepudiation: true,
keyEncipherment: true,
dataEncipherment: true
}, {
name: 'extKeyUsage',
serverAuth: true,
clientAuth: true,
codeSigning: true,
emailProtection: true,
timeStamping: true
}, {
name: 'nsCertType',
client: true,
server: true,
email: true,
objsign: true,
sslCA: isCa,
emailCA: isCa,
objCA: isCa
}])
const alg = forge.md.sha256.create()
if (issuer !== undefined) {
cert.setIssuer(issuer.certificate.subject.attributes)
cert.sign(issuer.privateKey, alg)
} else {
cert.setIssuer(attrs)
cert.sign(keys.privateKey, alg)
}
return {
privateKey: keys.privateKey,
publicKey: keys.publicKey,
certificate: cert
}
}
const root = createCert('CA')
const server = createCert('agent1', root)
const client = createCert('client', root)
const proxy = createCert('proxy', root)
return {
root: {
key: forge.pki.privateKeyToPem(root.privateKey),
crt: forge.pki.certificateToPem(root.certificate)
},
server: {
key: forge.pki.privateKeyToPem(server.privateKey),
crt: forge.pki.certificateToPem(server.certificate)
},
client: {
key: forge.pki.privateKeyToPem(client.privateKey),
crt: forge.pki.certificateToPem(client.certificate)
},
proxy: {
key: forge.pki.privateKeyToPem(proxy.privateKey),
crt: forge.pki.certificateToPem(proxy.certificate)
}
}
})()
test('should throw error when no uri is provided', (t) => {
t = tspl(t, { plan: 2 })
t.throws(() => new ProxyAgent(), InvalidArgumentError)
t.throws(() => new ProxyAgent({}), InvalidArgumentError)
})
test('using auth in combination with token should throw', (t) => {
t = tspl(t, { plan: 1 })
t.throws(() => new ProxyAgent({
auth: 'foo',
token: 'Bearer bar',
uri: 'http://example.com'
}),
InvalidArgumentError
)
})
test('should accept string, URL and object as options', (t) => {
t = tspl(t, { plan: 3 })
t.doesNotThrow(() => new ProxyAgent('http://example.com'))
t.doesNotThrow(() => new ProxyAgent(new URL('http://example.com')))
t.doesNotThrow(() => new ProxyAgent({ uri: 'http://example.com' }))
})
test('ProxyAgent forwards connectTimeout to the proxy connector', async (t) => {
t = tspl(t, { plan: 4 })
const originalConnect = net.connect
let connect
let socket
const proxyAgent = new ProxyAgent({
uri: 'http://localhost:9000',
connectTimeout: 1e3,
clientFactory (_origin, options) {
connect = options.connect
return {
close () {
return Promise.resolve()
},
destroy () {
return Promise.resolve()
}
}
}
})
after(() => proxyAgent.close())
try {
net.connect = function (options) {
return new net.Socket(options)
}
t.ok(typeof connect === 'function')
const timeout = setTimeout(() => {
if (socket && !socket.destroyed) {
socket.destroy()
}
t.fail('connectTimeout was not forwarded to the proxy connector')
}, 2e3)
await new Promise((resolve, reject) => {
socket = connect({ hostname: 'localhost', protocol: 'http:', port: 9000 }, (err) => {
try {
t.ok(err instanceof ConnectTimeoutError)
t.strictEqual(err.code, 'UND_ERR_CONNECT_TIMEOUT')
t.strictEqual(err.message, 'Connect Timeout Error (attempted address: localhost:9000, timeout: 1000ms)')
clearTimeout(timeout)
resolve()
} catch (error) {
clearTimeout(timeout)
reject(error)
}
})
})
} finally {
net.connect = originalConnect
if (socket && !socket.destroyed) {
socket.destroy()
}
}
})
test('use proxy-agent to connect through proxy (keep alive)', async (t) => {
t = tspl(t, { plan: 10 })
const server = await buildServer()
const proxy = await buildProxy()
delete proxy.authenticate
let _socket, _connectParams
diagnosticsChannel.channel('undici:proxy:connected').subscribe(({ socket, connectParams }) => {
_socket = socket
_connectParams = connectParams
})
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTunnel: true
})
const parsedOrigin = new URL(serverUrl)
proxy.on('connect', (msg) => {
t.strictEqual(msg.headers['proxy-connection'], 'keep-alive')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl, { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
t.ok(_socket instanceof Socket)
t.equal(_connectParams.origin, proxyUrl)
t.equal(_connectParams.path, serverUrl.replace('http://', ''))
t.equal(_connectParams.headers['proxy-connection'], 'keep-alive')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
delete proxy.authenticate
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const parsedOrigin = new URL(serverUrl)
proxy.on('connect', () => {
t.ok(true, 'should connect to proxy')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl, { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy agent to connect through proxy using Pool', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildProxy()
let resolveFirstConnect
let connectCount = 0
proxy.authenticate = async function (req) {
if (++connectCount === 2) {
t.ok(true, 'second connect should arrive while first is still inflight')
resolveFirstConnect()
return true
} else {
await new Promise((resolve) => {
resolveFirstConnect = resolve
})
return true
}
}
server.on('request', (req, res) => {
res.end()
})
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const clientFactory = (url, options) => {
return new Pool(url, options)
}
const proxyAgent = new ProxyAgent({ auth: Buffer.from('user:pass').toString('base64'), uri: proxyUrl, clientFactory })
const firstRequest = request(`${serverUrl}`, { dispatcher: proxyAgent })
const secondRequest = await request(`${serverUrl}`, { dispatcher: proxyAgent })
t.strictEqual((await firstRequest).statusCode, 200)
t.strictEqual(secondRequest.statusCode, 200)
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy using path with params', async (t) => {
t = tspl(t, { plan: 5 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: false })
const parsedOrigin = new URL(serverUrl)
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy using path with params with tunneling enabled', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const parsedOrigin = new URL(serverUrl)
proxy.on('connect', () => {
t.ok(true, 'should call proxy')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy with basic auth in URL', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = new URL(`http://user:pass@localhost:${proxy.address().port}`)
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: false })
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req, fn) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy with username-only auth in URL', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = new URL(`http://user:@localhost:${proxy.address().port}`)
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: false })
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:').toString('base64')}`
}
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy with username-only auth in URL without colon', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = new URL(`http://user@localhost:${proxy.address().port}`)
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: false })
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:').toString('base64')}`
}
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy with basic auth in URL with tunneling enabled', async (t) => {
t = tspl(t, { plan: 7 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = new URL(`http://user:pass@localhost:${proxy.address().port}`)
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req, fn) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.ok(true, 'proxy should be called')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent to connect through proxy with username-only auth in URL with tunneling enabled', async (t) => {
t = tspl(t, { plan: 7 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = new URL(`http://user:@localhost:${proxy.address().port}`)
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:').toString('base64')}`
}
proxy.on('connect', () => {
t.ok(true, 'proxy should be called')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with auth', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
auth: Buffer.from('user:pass').toString('base64'),
uri: proxyUrl,
proxyTunnel: false
})
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with auth with tunneling enabled', async (t) => {
t = tspl(t, { plan: 7 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
auth: Buffer.from('user:pass').toString('base64'),
uri: proxyUrl,
proxyTunnel: true
})
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Basic ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.ok(true, 'proxy should be called')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with token', async (t) => {
t = tspl(t, { plan: 6 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
token: `Bearer ${Buffer.from('user:pass').toString('base64')}`,
uri: proxyUrl,
proxyTunnel: false
})
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Bearer ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with token with tunneling enabled', async (t) => {
t = tspl(t, { plan: 7 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
token: `Bearer ${Buffer.from('user:pass').toString('base64')}`,
uri: proxyUrl,
proxyTunnel: true
})
const parsedOrigin = new URL(serverUrl)
proxy.authenticate = function (req) {
t.ok(true, 'authentication should be called')
return req.headers['proxy-authorization'] === `Bearer ${Buffer.from('user:pass').toString('base64')}`
}
proxy.on('connect', () => {
t.ok(true, 'proxy should be called')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar', { dispatcher: proxyAgent })
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with custom headers', async (t) => {
t = tspl(t, { plan: 1 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTunnel: false,
headers: {
'User-Agent': 'Foobar/1.0.0'
}
})
proxy.on('connect', (req) => {
t.fail('proxy tunnel should not be established')
})
server.on('request', (req, res) => {
t.strictEqual(req.headers['user-agent'], 'BarBaz/1.0.0')
res.end()
})
await request(serverUrl + '/hello?foo=bar', {
headers: { 'user-agent': 'BarBaz/1.0.0' },
dispatcher: proxyAgent
})
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with custom headers with tunneling enabled', async (t) => {
t = tspl(t, { plan: 2 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
headers: {
'User-Agent': 'Foobar/1.0.0'
},
proxyTunnel: true
})
proxy.on('connect', (req) => {
t.strictEqual(req.headers['user-agent'], 'Foobar/1.0.0')
})
server.on('request', (req, res) => {
t.strictEqual(req.headers['user-agent'], 'BarBaz/1.0.0')
res.end()
})
await request(serverUrl + '/hello?foo=bar', {
headers: { 'user-agent': 'BarBaz/1.0.0' },
dispatcher: proxyAgent
})
server.close()
proxy.close()
proxyAgent.close()
})
for (const proxyTunnel of [false, true]) {
test(`use proxy-agent with a headers object reused across origins (proxyTunnel: ${proxyTunnel})`, async (t) => {
const serverA = await buildServer()
const serverB = await buildServer()
const proxy = await buildProxy()
const proxyAgent = new ProxyAgent({ uri: `http://localhost:${proxy.address().port}`, proxyTunnel })
t.after(() => {
serverA.close()
serverB.close()
proxy.close()
return proxyAgent.close()
})
const hosts = []
const onRequest = (req, res) => {
hosts.push(req.headers.host)
res.end()
}
serverA.on('request', onRequest)
serverB.on('request', onRequest)
const headers = { 'x-foo': 'bar' }
const hostA = `localhost:${serverA.address().port}`
const hostB = `localhost:${serverB.address().port}`
for (const host of [hostA, hostB]) {
const { body } = await request(`http://${host}/`, { dispatcher: proxyAgent, headers })
await body.dump()
}
assert.deepStrictEqual(hosts, [hostA, hostB])
assert.deepStrictEqual(headers, { 'x-foo': 'bar' })
})
test(`use proxy-agent with a Host header in any case (proxyTunnel: ${proxyTunnel})`, async (t) => {
const server = await buildServer()
const proxy = await buildProxy()
const proxyAgent = new ProxyAgent({ uri: `http://localhost:${proxy.address().port}`, proxyTunnel })
t.after(() => {
server.close()
proxy.close()
return proxyAgent.close()
})
const host = `localhost:${server.address().port}`
server.on('request', (req, res) => {
res.end(req.headers.host)
})
const { body } = await request(`http://${host}/`, {
dispatcher: proxyAgent,
headers: { HOST: 'custom.example' }
})
assert.strictEqual(await body.text(), 'custom.example')
})
}
test('use proxy-agent with repeated iterable headers', async (t) => {
t = tspl(t, { plan: 1 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent(proxyUrl)
server.on('request', (req, res) => {
t.strictEqual(req.headers['x-duplicate'], 'first, second')
res.end()
})
const headers = {
* [Symbol.iterator] () {
yield ['x-duplicate', 'first']
yield ['x-duplicate', 'second']
}
}
await request(serverUrl, { dispatcher: proxyAgent, headers })
server.close()
proxy.close()
proxyAgent.close()
})
test('sending proxy-authorization in request headers should throw', async (t) => {
t = tspl(t, { plan: 5 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent(proxyUrl)
server.on('request', (req, res) => {
res.end(JSON.stringify({ hello: 'world' }))
})
await t.rejects(
request(
serverUrl + '/hello?foo=bar',
{
dispatcher: proxyAgent,
headers: {
'proxy-authorization': Buffer.from('user:pass').toString('base64')
}
}
),
'Proxy-Authorization should be sent in ProxyAgent'
)
await t.rejects(
request(
serverUrl + '/hello?foo=bar',
{
dispatcher: proxyAgent,
headers: {
'PROXY-AUTHORIZATION': Buffer.from('user:pass').toString('base64')
}
}
),
'Proxy-Authorization should be sent in ProxyAgent'
)
await t.rejects(
request(
serverUrl + '/hello?foo=bar',
{
dispatcher: proxyAgent,
headers: {
'Proxy-Authorization': Buffer.from('user:pass').toString('base64')
}
}
),
'Proxy-Authorization should be sent in ProxyAgent'
)
// Iterable containers (Map/Headers) must not bypass the guard (GHSA-6cv7-626c-qhqw)
await t.rejects(
request(
serverUrl + '/hello?foo=bar',
{
dispatcher: proxyAgent,
headers: new Map([['proxy-authorization', Buffer.from('user:pass').toString('base64')]])
}
),
'Proxy-Authorization should be sent in ProxyAgent'
)
await t.rejects(
request(
serverUrl + '/hello?foo=bar',
{
dispatcher: proxyAgent,
headers: new Headers({ 'proxy-authorization': Buffer.from('user:pass').toString('base64') })
}
),
'Proxy-Authorization should be sent in ProxyAgent'
)
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with setGlobalDispatcher', async (t) => {
t = tspl(t, { plan: 5 })
const defaultDispatcher = getGlobalDispatcher()
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl })
const parsedOrigin = new URL(serverUrl)
setGlobalDispatcher(proxyAgent)
after(() => setGlobalDispatcher(defaultDispatcher))
proxy.on('connect', () => {
// proxyTunnel must be set to true in order to tunnel into the endpoint for HTTP->HTTP proxy connections
t.fail(true, 'connect to proxy should unreachable by default for HTTP->HTTP proxy connections')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar')
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('use proxy-agent with setGlobalDispatcher with tunneling enabled', async (t) => {
t = tspl(t, { plan: 6 })
const defaultDispatcher = getGlobalDispatcher()
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const parsedOrigin = new URL(serverUrl)
setGlobalDispatcher(proxyAgent)
after(() => setGlobalDispatcher(defaultDispatcher))
proxy.on('connect', () => {
t.ok(true, 'should call proxy')
})
server.on('request', (req, res) => {
t.strictEqual(req.url, '/hello?foo=bar')
t.strictEqual(req.headers.host, parsedOrigin.host, 'should not use proxyUrl as host')
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ hello: 'world' }))
})
const {
statusCode,
headers,
body
} = await request(serverUrl + '/hello?foo=bar')
const json = await body.json()
t.strictEqual(statusCode, 200)
t.deepStrictEqual(json, { hello: 'world' })
t.strictEqual(headers.connection, 'keep-alive', 'should remain the connection open')
server.close()
proxy.close()
proxyAgent.close()
})
test('ProxyAgent correctly sends headers when using fetch - #1355, #1623', async (t) => {
t = tspl(t, { plan: 1 })
const defaultDispatcher = getGlobalDispatcher()
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl })
setGlobalDispatcher(proxyAgent)
after(() => setGlobalDispatcher(defaultDispatcher))
const expectedHeaders = {
host: `localhost:${server.address().port}`,
connection: 'keep-alive',
'test-header': 'value',
accept: '*/*',
'accept-language': '*',
'sec-fetch-mode': 'cors',
'user-agent': 'undici',
'accept-encoding': 'gzip, deflate'
}
proxy.on('connect', (req, res) => {
// proxyTunnel must be set to true in order to tunnel into the endpoint for HTTP->HTTP proxy connections
t.fail(true, 'connect to proxy should unreachable by default for HTTP->HTTP proxy connections')
})
server.on('request', (req, res) => {
// The `proxy` package will add a "via" and "x-forwarded-for" header for non-tunneled Proxy requests
for (const header of ['via', 'x-forwarded-for']) {
delete req.headers[header]
}
t.deepStrictEqual({ ...req.headers }, expectedHeaders)
res.end('goodbye')
})
await fetch(serverUrl, {
headers: { 'Test-header': 'value' }
})
server.close()
proxy.close()
proxyAgent.close()
t.end()
})
test('ProxyAgent correctly sends headers when using fetch - #1355, #1623 (with proxy tunneling enabled)', async (t) => {
t = tspl(t, { plan: 2 })
const defaultDispatcher = getGlobalDispatcher()
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
setGlobalDispatcher(proxyAgent)
after(() => setGlobalDispatcher(defaultDispatcher))
const expectedHeaders = {
host: `localhost:${server.address().port}`,
connection: 'keep-alive',
'test-header': 'value',
accept: '*/*',
'accept-language': '*',
'sec-fetch-mode': 'cors',
'user-agent': 'undici',
'accept-encoding': 'gzip, deflate'
}
const expectedProxyHeaders = {
'proxy-connection': 'keep-alive',
host: `localhost:${server.address().port}`,
connection: 'close'
}
proxy.on('connect', (req, res) => {
t.deepStrictEqual({ ...req.headers }, expectedProxyHeaders)
})
server.on('request', (req, res) => {
t.deepStrictEqual({ ...req.headers }, expectedHeaders)
res.end('goodbye')
})
await fetch(serverUrl, {
headers: { 'Test-header': 'value' }
})
server.close()
proxy.close()
proxyAgent.close()
t.end()
})
test('should throw when proxy does not return 200', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
proxy.on('connect', () => {
// proxyTunnel must be set to true in order to tunnel into the endpoint for HTTP->HTTP proxy connections
t.fail(true, 'connect to proxy should unreachable by default for HTTP->HTTP proxy connections')
})
proxy.authenticate = function (_req) {
t.ok(true, 'should call authenticate')
return false
}
const proxyAgent = new ProxyAgent({ uri: proxyUrl })
try {
await request(serverUrl, { dispatcher: proxyAgent })
t.fail()
} catch (e) {
t.ok(true, 'pass')
t.ok(e)
}
server.close()
proxy.close()
proxyAgent.close()
await t.completed
})
test('should throw when proxy does not return 200 with tunneling enabled', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
proxy.authenticate = function (_req) {
t.ok(true, 'should call authenticate')
return false
}
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
try {
await request(serverUrl, { dispatcher: proxyAgent })
t.fail()
} catch (e) {
t.ok(true, 'pass')
t.ok(e)
}
server.close()
proxy.close()
proxyAgent.close()
await t.completed
})
test('pass ProxyAgent proxy status code error when using fetch - #2161', async (t) => {
t = tspl(t, { plan: 2 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
proxy.authenticate = function (_req) {
t.ok(true, 'should call authenticate')
return false
}
const proxyAgent = new ProxyAgent(proxyUrl)
try {
await fetch(serverUrl, { dispatcher: proxyAgent })
} catch (e) {
t.ok('cause' in e)
}
server.close()
proxy.close()
proxyAgent.close()
await t.completed
})
test('Proxy via HTTP to HTTPS endpoint', async (t) => {
t = tspl(t, { plan: 4 })
const server = await buildSSLServer()
const proxy = await buildProxy()
const serverUrl = `https://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
requestTls: {
ca: [
certs.root.crt
],
servername: 'agent1'
}
})
server.on('request', function (req, res) {
t.ok(req.connection.encrypted)
res.end(JSON.stringify(req.headers))
})
server.on('secureConnection', () => {
t.ok(true, 'server should be connected secured')
})
proxy.on('secureConnection', () => {
t.fail('proxy over http should not call secureConnection')
})
proxy.on('connect', function () {
t.ok(true, 'proxy should be connected')
})
proxy.on('request', function () {
t.fail('proxy should never receive requests')
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTPS to HTTPS endpoint', async (t) => {
t = tspl(t, { plan: 5 })
const server = await buildSSLServer()
const proxy = await buildSSLProxy()
const serverUrl = `https://localhost:${server.address().port}`
const proxyUrl = `https://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTls: {
ca: [
certs.root.crt
],
servername: 'proxy'
},
requestTls: {
ca: [
certs.root.crt
],
servername: 'agent1'
}
})
server.on('request', function (req, res) {
t.ok(req.connection.encrypted)
res.end(JSON.stringify(req.headers))
})
server.on('secureConnection', () => {
t.ok(true, 'server should be connected secured')
})
proxy.on('secureConnection', () => {
t.ok(true, 'proxy over http should call secureConnection')
})
proxy.on('connect', function () {
t.ok(true, 'proxy should be connected')
})
proxy.on('request', function () {
t.fail('proxy should never receive requests')
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTPS to HTTP endpoint forwards without tunneling by default', async (t) => {
t = tspl(t, { plan: 4 })
const server = await buildServer()
const proxy = await buildSSLProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `https://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTls: {
ca: [
certs.root.crt
],
servername: 'proxy'
}
})
server.on('request', function (req, res) {
t.ok(!req.connection.encrypted)
const headers = { host: req.headers.host, connection: req.headers.connection }
res.end(JSON.stringify(headers))
})
server.on('secureConnection', () => {
t.fail('server is http')
})
proxy.on('secureConnection', () => {
t.ok(true, 'TLS handshake to the proxy must still happen')
})
proxy.on('connect', () => {
t.fail('should not tunnel plain HTTP through an HTTPS proxy by default')
})
proxy.on('request', function (req) {
const bits = { method: req.method, url: req.url }
t.deepStrictEqual(bits, {
method: 'GET',
url: `${serverUrl}/`
})
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTPS to HTTP endpoint uses HTTP/1.1 when proxy also supports HTTP/2', async (t) => {
t = tspl(t, { plan: 4 })
const proxy = await buildH2CapableSSLProxy()
const serverUrl = 'http://example.com/'
const proxyUrl = `https://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTls: {
ca: [
certs.root.crt
],
servername: 'proxy'
}
})
proxy.on('stream', (stream) => {
t.fail('non-tunneled HTTP forwarding must not negotiate HTTP/2')
stream.close()
})
proxy.on('request', function (req, res) {
t.strictEqual(req.httpVersion, '1.1')
t.strictEqual(req.method, 'GET')
t.strictEqual(req.url, serverUrl)
res.end('ok')
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
t.strictEqual(await data.body.text(), 'ok')
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTPS to HTTP endpoint with tunneling enabled', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildSSLProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `https://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTls: {
ca: [
certs.root.crt
],
servername: 'proxy'
},
proxyTunnel: true
})
server.on('request', function (req, res) {
t.ok(!req.connection.encrypted)
res.end(JSON.stringify(req.headers))
})
server.on('secureConnection', () => {
t.fail('server is http')
})
proxy.on('secureConnection', () => {
t.ok(true, 'proxy over http should call secureConnection')
})
proxy.on('request', function () {
t.fail('proxy should never receive requests')
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTP to HTTP endpoint with tunneling enabled', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
server.on('request', function (req, res) {
t.ok(!req.connection.encrypted)
res.end(JSON.stringify(req.headers))
})
server.on('secureConnection', () => {
t.fail('server is http')
})
proxy.on('secureConnection', () => {
t.fail('proxy is http')
})
proxy.on('connect', () => {
t.ok(true, 'connect to proxy')
})
proxy.on('request', function () {
t.fail('proxy should never receive requests')
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTP to HTTP endpoint with tunneling disabled', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: false })
server.on('request', function (req, res) {
t.ok(!req.connection.encrypted)
const headers = { host: req.headers.host, connection: req.headers.connection }
res.end(JSON.stringify(headers))
})
server.on('secureConnection', () => {
t.fail('server is http')
})
proxy.on('secureConnection', () => {
t.fail('proxy is http')
})
proxy.on('connect', () => {
t.fail(true, 'connect to proxy should unreachable if proxyTunnel is false')
})
proxy.on('request', function (req) {
const bits = { method: req.method, url: req.url }
t.deepStrictEqual(bits, {
method: 'GET',
url: `${serverUrl}/`
})
})
const data = await request(serverUrl, { dispatcher: proxyAgent })
const json = await data.body.json()
t.deepStrictEqual(json, {
host: `localhost:${server.address().port}`,
connection: 'keep-alive'
})
server.close()
proxy.close()
proxyAgent.close()
})
test('Proxy via HTTPS to HTTP fails on wrong SNI', async (t) => {
t = tspl(t, { plan: 3 })
const server = await buildServer()
const proxy = await buildSSLProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `https://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({
uri: proxyUrl,
proxyTls: {
ca: [
certs.root.crt
]
}
})
server.on('request', function (req, res) {
t.ok(!req.connection.encrypted)
res.end(JSON.stringify(req.headers))
})
server.on('secureConnection', () => {
t.fail('server is http')
})
proxy.on('secureConnection', () => {
t.fail('proxy is http')
})
proxy.on('connect', () => {
t.ok(true, 'connect to proxy')
})
proxy.on('request', function () {
t.fail('proxy should never receive requests')
})
try {
await request(serverUrl, { dispatcher: proxyAgent })
throw new Error('should fail')
} catch (e) {
t.ok(e instanceof SecureProxyConnectionError)
t.ok(e.cause instanceof Error)
t.ok(e.cause.code === 'ERR_TLS_CERT_ALTNAME_INVALID')
}
server.close()
proxy.close()
proxyAgent.close()
})
test('ProxyAgent keeps customized host in request headers - #3019', async (t) => {
t = tspl(t, { plan: 2 })
const server = await buildServer()
const proxy = await buildProxy()
const serverUrl = `http://localhost:${server.address().port}`
const proxyUrl = `http://localhost:${proxy.address().port}`
const proxyAgent = new ProxyAgent({ uri: proxyUrl, proxyTunnel: true })
const customHost = 'example.com'
proxy.on('connect', (req) => {
t.strictEqual(req.headers.host, `localhost:${server.address().port}`)
})
server.on('request', (req, res) => {
t.strictEqual(req.headers.host, customHost)
res.end()
})
await request(serverUrl, {
headers: { Host: customHost },
dispatcher: proxyAgent
})
server.close()
proxy.close()
proxyAgent.close()
})
test('ProxyAgent handles multiple concurrent HTTP requests via HTTP proxy', async (t) => {
t = tspl(t, { plan: 20 })
// Start target HTTP server
const server = createServer((req, res) => {
setTimeout(() => {
res.setHeader('content-type', 'application/json')
res.end(JSON.stringify({ url: req.url }))
}, 50)
})
await new Promise(resolve => server.listen(0, resolve))
const targetPort = server.address().port
// Start HTTP proxy server
const proxy = createProxy(createServer())
await new Promise(resolve => proxy.listen(0, resolve))
const proxyPort = proxy.address().port
// Create ProxyAgent (no tunneling, plain HTTP)
const proxyAgent = new ProxyAgent(`http://localhost:${proxyPort}`)
const N = 10
const requests = []
for (let i = 0; i < N; i++) {
requests.push(
request(`http://localhost:${targetPort}/test${i}`, { dispatcher: proxyAgent })
.then(async res => {
t.strictEqual(res.statusCode, 200)
const json = await res.body.json()
t.deepStrictEqual(json, { url: `/test${i}` })
})
)
}
try {
await Promise.all(requests)
} catch (err) {
t.fail(err)
}
server.close()
proxy.close()
proxyAgent.close()
})
function buildServer () {
return new Promise((resolve) => {
const server = createServer({ joinDuplicateHeaders: true })
server.listen(0, () => resolve(server))
})
}
function buildSSLServer () {
const serverOptions = {
ca: [
certs.root.crt
],
key: certs.server.key,
cert: certs.server.crt,
joinDuplicateHeaders: true
}
return new Promise((resolve) => {
const server = https.createServer(serverOptions)
server.listen(0, () => resolve(server))
})
}
function buildProxy (listener) {
return new Promise((resolve) => {
const server = listener
? createProxy(createServer(listener))
: createProxy(createServer({ joinDuplicateHeaders: true }))
server.listen(0, () => resolve(server))
})
}
function buildSSLProxy () {
const serverOptions = {
ca: [
certs.root.crt
],
key: certs.proxy.key,
cert: certs.proxy.crt,
joinDuplicateHeaders: true
}
return new Promise((resolve) => {
const server = createProxy(https.createServer(serverOptions))
server.listen(0, () => resolve(server))
})
}
function buildH2CapableSSLProxy () {
const serverOptions = {
ca: [
certs.root.crt
],
key: certs.proxy.key,
cert: certs.proxy.crt,
allowHTTP1: true,
joinDuplicateHeaders: true
}
return new Promise((resolve) => {
const server = http2.createSecureServer(serverOptions)
server.listen(0, () => resolve(server))
})
}