Skip to content

Commit

Permalink
[proxy-agent] Use HttpProxyAgent for "http" and HttpsProxyAgent f…
Browse files Browse the repository at this point in the history
…or "https"
  • Loading branch information
TooTallNate committed May 4, 2023
1 parent ff06575 commit f30ed32
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 43 deletions.
5 changes: 5 additions & 0 deletions .changeset/smooth-toes-brush.md
@@ -0,0 +1,5 @@
---
'proxy-agent': patch
---

Use `HttpProxyAgent` for "http" and `HttpsProxyAgent` for "https"
8 changes: 4 additions & 4 deletions packages/agent-base/README.md
Expand Up @@ -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
8 changes: 4 additions & 4 deletions packages/proxy-agent/README.md
Expand Up @@ -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
40 changes: 22 additions & 18 deletions packages/proxy-agent/src/index.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -91,31 +93,33 @@ export class ProxyAgent extends Agent {
req: http.ClientRequest,
opts: AgentConnectOpts
): Promise<http.Agent> {
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);
}
Expand Down
71 changes: 54 additions & 17 deletions packages/proxy-agent/test/test.ts
@@ -1,14 +1,16 @@
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';
// @ts-expect-error no types
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'),
Expand Down Expand Up @@ -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
);
});
});
});
1 change: 1 addition & 0 deletions turbo.json
Expand Up @@ -3,6 +3,7 @@
"globalEnv": [
"HTTP_PROXY",
"HTTPS_PROXY",
"ALL_PROXY",
"NO_PROXY",
"NORDVPN_USERNAME",
"NORDVPN_PASSWORD"
Expand Down

1 comment on commit f30ed32

@vercel
Copy link

@vercel vercel bot commented on f30ed32 May 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

proxy-agents – ./

proxy-agents-tootallnate.vercel.app
proxy-agents-git-main-tootallnate.vercel.app
proxy-agents.vercel.app

Please sign in to comment.