From f30ed328bd6fdc406fa4f6e545029f12e7130607 Mon Sep 17 00:00:00 2001 From: Nathan Rajlich Date: Thu, 4 May 2023 15:04:13 -0700 Subject: [PATCH] [proxy-agent] Use `HttpProxyAgent` for "http" and `HttpsProxyAgent` for "https" --- .changeset/smooth-toes-brush.md | 5 +++ packages/agent-base/README.md | 8 ++-- packages/proxy-agent/README.md | 8 ++-- packages/proxy-agent/src/index.ts | 40 +++++++++-------- packages/proxy-agent/test/test.ts | 71 +++++++++++++++++++++++-------- turbo.json | 1 + 6 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 .changeset/smooth-toes-brush.md diff --git a/.changeset/smooth-toes-brush.md b/.changeset/smooth-toes-brush.md new file mode 100644 index 00000000..33b5c0da --- /dev/null +++ b/.changeset/smooth-toes-brush.md @@ -0,0 +1,5 @@ +--- +'proxy-agent': patch +--- + +Use `HttpProxyAgent` for "http" and `HttpsProxyAgent` for "https" diff --git a/packages/agent-base/README.md b/packages/agent-base/README.md index e1192f12..9922e61c 100644 --- a/packages/agent-base/README.md +++ b/packages/agent-base/README.md @@ -88,8 +88,8 @@ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -[http-proxy-agent]: https://github.com/TooTallNate/node-http-proxy-agent -[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent -[pac-proxy-agent]: https://github.com/TooTallNate/node-pac-proxy-agent -[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent +[http-proxy-agent]: ../http-proxy-agent +[https-proxy-agent]: ../https-proxy-agent +[pac-proxy-agent]: ../pac-proxy-agent +[socks-proxy-agent]: ../socks-proxy-agent [http.Agent]: https://nodejs.org/api/http.html#http_class_http_agent diff --git a/packages/proxy-agent/README.md b/packages/proxy-agent/README.md index 03c00dfa..c3cd7aac 100644 --- a/packages/proxy-agent/README.md +++ b/packages/proxy-agent/README.md @@ -81,7 +81,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -[http-proxy-agent]: https://github.com/TooTallNate/node-http-proxy-agent -[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent -[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent -[pac-proxy-agent]: https://github.com/TooTallNate/node-pac-proxy-agent +[http-proxy-agent]: ../http-proxy-agent +[https-proxy-agent]: ../https-proxy-agent +[socks-proxy-agent]: ../socks-proxy-agent +[pac-proxy-agent]: ../pac-proxy-agent diff --git a/packages/proxy-agent/src/index.ts b/packages/proxy-agent/src/index.ts index 2bdf38b3..29ade631 100644 --- a/packages/proxy-agent/src/index.ts +++ b/packages/proxy-agent/src/index.ts @@ -19,24 +19,26 @@ const PROTOCOLS = [ type ValidProtocol = (typeof PROTOCOLS)[number]; +type AgentConstructor = new (...args: never[]) => Agent; + /** * Supported proxy types. */ export const proxies: { - [P in ValidProtocol]: new (...args: never[]) => Agent; + [P in ValidProtocol]: [AgentConstructor, AgentConstructor]; } = { - http: HttpProxyAgent, - https: HttpsProxyAgent, - socks: SocksProxyAgent, - socks4: SocksProxyAgent, - socks4a: SocksProxyAgent, - socks5: SocksProxyAgent, - socks5h: SocksProxyAgent, - 'pac-data': PacProxyAgent, - 'pac-file': PacProxyAgent, - 'pac-ftp': PacProxyAgent, - 'pac-http': PacProxyAgent, - 'pac-https': PacProxyAgent, + http: [HttpProxyAgent, HttpsProxyAgent], + https: [HttpProxyAgent, HttpsProxyAgent], + socks: [SocksProxyAgent, SocksProxyAgent], + socks4: [SocksProxyAgent, SocksProxyAgent], + socks4a: [SocksProxyAgent, SocksProxyAgent], + socks5: [SocksProxyAgent, SocksProxyAgent], + socks5h: [SocksProxyAgent, SocksProxyAgent], + 'pac-data': [PacProxyAgent, PacProxyAgent], + 'pac-file': [PacProxyAgent, PacProxyAgent], + 'pac-ftp': [PacProxyAgent, PacProxyAgent], + 'pac-http': [PacProxyAgent, PacProxyAgent], + 'pac-https': [PacProxyAgent, PacProxyAgent], }; function isValidProtocol(v: string): v is ValidProtocol { @@ -91,31 +93,33 @@ export class ProxyAgent extends Agent { req: http.ClientRequest, opts: AgentConnectOpts ): Promise { - const protocol = opts.secureEndpoint ? 'https:' : 'http:'; + const { secureEndpoint } = opts; + const protocol = secureEndpoint ? 'https:' : 'http:'; const host = req.getHeader('host'); const url = new URL(req.path, `${protocol}//${host}`).href; const proxy = getProxyForUrl(url); if (!proxy) { debug('Proxy not enabled for URL: %o', url); - return opts.secureEndpoint ? this.httpsAgent : this.httpAgent; + return secureEndpoint ? this.httpsAgent : this.httpAgent; } debug('Request URL: %o', url); debug('Proxy URL: %o', proxy); // attempt to get a cached `http.Agent` instance first - let agent = this.cache.get(proxy); + const cacheKey = `${protocol}+${proxy}`; + let agent = this.cache.get(cacheKey); if (!agent) { const proxyUrl = new URL(proxy); const proxyProto = proxyUrl.protocol.replace(':', ''); if (!isValidProtocol(proxyProto)) { throw new Error(`Unsupported protocol for proxy URL: ${proxy}`); } - const ctor = proxies[proxyProto]; + const ctor = proxies[proxyProto][secureEndpoint ? 1 : 0]; // @ts-expect-error meh… agent = new ctor(proxy, this.connectOpts); - this.cache.set(proxy, agent); + this.cache.set(cacheKey, agent); } else { debug('Cache hit for proxy URL: %o', proxy); } diff --git a/packages/proxy-agent/test/test.ts b/packages/proxy-agent/test/test.ts index 8b490f57..8d565397 100644 --- a/packages/proxy-agent/test/test.ts +++ b/packages/proxy-agent/test/test.ts @@ -1,6 +1,7 @@ import * as fs from 'fs'; import * as http from 'http'; import * as https from 'https'; +import { once } from 'events'; import assert from 'assert'; import { json, req } from 'agent-base'; import { ProxyServer, createProxy } from 'proxy'; @@ -8,7 +9,8 @@ import { ProxyServer, createProxy } from 'proxy'; import socks from 'socksv5'; import { listen } from 'async-listen'; import { ProxyAgent } from '../src'; -import { once } from 'events'; +import { HttpProxyAgent } from 'http-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; const sslOptions = { key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'), @@ -194,25 +196,60 @@ describe('ProxyAgent', () => { assert.equal(httpsServerUrl.host, body.host); }); - describe('over "socks" proxy', () => { - it('should work', async () => { - let gotReq = false; - httpsServer.once('request', function (req, res) { - gotReq = true; - res.end(JSON.stringify(req.headers)); - }); + it('should work over "socks" proxy', async () => { + let gotReq = false; + httpsServer.once('request', function (req, res) { + gotReq = true; + res.end(JSON.stringify(req.headers)); + }); - process.env.HTTP_PROXY = `socks://localhost:${socksPort}`; - const agent = new ProxyAgent(); + process.env.HTTP_PROXY = `socks://localhost:${socksPort}`; + const agent = new ProxyAgent(); - const res = await req(new URL('/test', httpsServerUrl), { - agent, - rejectUnauthorized: false, - }); - const body = await json(res); - assert(gotReq); - assert.equal(httpsServerUrl.host, body.host); + const res = await req(new URL('/test', httpsServerUrl), { + agent, + rejectUnauthorized: false, + }); + const body = await json(res); + assert(gotReq); + assert.equal(httpsServerUrl.host, body.host); + }); + + it('should use `HttpProxyAgent` for "http" and `HttpsProxyAgent` for "https"', async () => { + let gotHttpReq = false; + httpServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + gotHttpReq = true; + }); + + let gotHttpsReq = false; + httpsServer.once('request', function (req, res) { + res.end(JSON.stringify(req.headers)); + gotHttpsReq = true; + }); + + process.env.ALL_PROXY = httpsProxyServerUrl.href; + const agent = new ProxyAgent({ rejectUnauthorized: false }); + + const res = await req(httpServerUrl, { + agent, + }); + const body = await json(res); + assert(gotHttpReq); + assert.equal(httpServerUrl.host, body.host); + expect(agent.cache.size).toEqual(1); + expect([...agent.cache.values()][0]).toBeInstanceOf(HttpProxyAgent); + + const res2 = await req(httpsServerUrl, { + agent, }); + const body2 = await json(res2); + assert(gotHttpsReq); + assert.equal(httpsServerUrl.host, body2.host); + expect(agent.cache.size).toEqual(2); + expect([...agent.cache.values()][0]).toBeInstanceOf( + HttpsProxyAgent + ); }); }); }); diff --git a/turbo.json b/turbo.json index 879851b0..64a60436 100644 --- a/turbo.json +++ b/turbo.json @@ -3,6 +3,7 @@ "globalEnv": [ "HTTP_PROXY", "HTTPS_PROXY", + "ALL_PROXY", "NO_PROXY", "NORDVPN_USERNAME", "NORDVPN_PASSWORD"