From ee28ac4f4f11efad3b1d36fdedf0bf27be08f5eb Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Thu, 31 Mar 2022 15:37:51 -0700 Subject: [PATCH 1/3] feat: Add support for `NO_PROXY` --- README.md | 2 +- src/agents.ts | 35 +++++++++++++++++++++++++++++- test/agents.ts | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 80d2307..81bb853 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ let defaultRequest = teenyRequest.defaults({timeout: 60000}); ``` ## Proxy environment variables -If environment variables `HTTP_PROXY` or `HTTPS_PROXY` are set, they are respected. `NO_PROXY` is currently not implemented. +If environment variables `HTTP_PROXY`, `HTTPS_PROXY`, or `NO_PROXY` are set, they are respected. ## Building with Webpack 4+ Since 4.0.0, Webpack uses `javascript/esm` for `.mjs` files which handles ESM more strictly compared to `javascript/auto`. If you get the error `Can't import the named export 'PassThrough' from non EcmaScript module`, please add the following to your Webpack config: diff --git a/src/agents.ts b/src/agents.ts index b6c432f..f12a060 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -25,6 +25,36 @@ export const pool = new Map(); export type HttpAnyAgent = HTTPAgent | HTTPSAgent; +/** + * + * @param uri The request uri + * @returns + */ +function shouldUseProxyForURI(uri: string): boolean { + const noProxyEnv = process.env.NO_PROXY || process.env.no_proxy; + if (!noProxyEnv) { + return true; + } + + const givenURI = new URL(uri); + + for (const noProxyRaw of noProxyEnv.split(',')) { + const noProxy = noProxyRaw.trim(); + + if (noProxy === givenURI.origin || noProxy === givenURI.hostname) { + return false; + } else if (noProxy.startsWith('*.') || noProxy.startsWith('.')) { + const noProxyWildcard = noProxy.replace(/^\*\./, '.'); + + if (givenURI.hostname.endsWith(noProxyWildcard)) { + return false; + } + } + } + + return true; +} + /** * Returns a custom request Agent if one is found, otherwise returns undefined * which will result in the global http(s) Agent being used. @@ -47,7 +77,10 @@ export function getAgent( const poolOptions = Object.assign({}, reqOpts.pool); - if (proxy) { + const manuallyProvidedProxy = !!reqOpts.proxy; + const shouldUseProxy = manuallyProvidedProxy || shouldUseProxyForURI(uri); + + if (proxy && shouldUseProxy) { // tslint:disable-next-line variable-name const Agent = isHttp ? require('http-proxy-agent') diff --git a/test/agents.ts b/test/agents.ts index 5b082aa..ee708bb 100644 --- a/test/agents.ts +++ b/test/agents.ts @@ -53,6 +53,18 @@ describe('agents', () => { 'HTTPS_PROXY', ]; + const noProxyEnvVars = ['no_proxy', 'NO_PROXY']; + + function cleanProxyEnv() { + for (const env of noProxyEnvVars) { + delete process.env[env]; + } + + for (const env of envVars) { + delete process.env[env]; + } + } + describe('http', () => { const uri = httpUri; const proxy = 'http://hello.there:8080'; @@ -114,6 +126,52 @@ describe('agents', () => { }); }); }); + + describe('no_proxy', () => { + const uri = httpsUri; + const proxy = 'https://hello.there:8080'; + + beforeEach(cleanProxyEnv); + afterEach(cleanProxyEnv); + + noProxyEnvVars.forEach(noProxEnvVar => { + it(`should respect the proxy option, even if is in ${noProxEnvVar} env var`, () => { + process.env[noProxEnvVar] = new URL(uri).hostname; + + const options = Object.assign({proxy}, defaultOptions); + const agent = getAgent(uri, options); + assert(agent instanceof HttpsProxyAgent); + }); + }); + + noProxyEnvVars.forEach(noProxEnvVar => { + envVars.forEach(envVar => { + const root = 'example.com'; + const subDomain = 'abc.' + root; + + const uri = new URL(`https://${subDomain}`); + + const cases = [ + {name: '`.` support', value: `.${root}`}, + {name: '`*.` support', value: `*.${root}`}, + {name: 'list support', value: `a, b,${subDomain},.c,*.d`}, + {name: '`.` + list support', value: `a, b,.${root},.c,*.d`}, + {name: '`*.` + list support', value: `a, b,*.${root},.c,*.d`}, + ]; + + for (const {name, value} of cases) { + it(`should respect the ${noProxEnvVar} env var > ${envVar}': ${name}`, () => { + process.env[envVar] = proxy; + + process.env[noProxEnvVar] = value; + const agent = getAgent(uri.toString(), defaultOptions); + assert(!(agent instanceof HttpProxyAgent)); + assert(!(agent instanceof HttpsProxyAgent)); + }); + } + }); + }); + }); }); describe('forever', () => { From 69089e5526ff03e60f26d60c28da960670bb5873 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Thu, 31 Mar 2022 15:42:17 -0700 Subject: [PATCH 2/3] docs: Document `shouldUseProxyForURI` --- src/agents.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/agents.ts b/src/agents.ts index f12a060..6a7b31b 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -26,9 +26,10 @@ export const pool = new Map(); export type HttpAnyAgent = HTTPAgent | HTTPSAgent; /** + * Determines if a proxy should be considered based on the environment. * * @param uri The request uri - * @returns + * @returns {boolean} */ function shouldUseProxyForURI(uri: string): boolean { const noProxyEnv = process.env.NO_PROXY || process.env.no_proxy; From 49ece7e0e979ffcbbc966e4fabf33cf82ddd7161 Mon Sep 17 00:00:00 2001 From: Daniel Bankhead Date: Thu, 31 Mar 2022 16:04:08 -0700 Subject: [PATCH 3/3] test: Use sandbox for the environment --- test/agents.ts | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/test/agents.ts b/test/agents.ts index ee708bb..7b28068 100644 --- a/test/agents.ts +++ b/test/agents.ts @@ -55,16 +55,6 @@ describe('agents', () => { const noProxyEnvVars = ['no_proxy', 'NO_PROXY']; - function cleanProxyEnv() { - for (const env of noProxyEnvVars) { - delete process.env[env]; - } - - for (const env of envVars) { - delete process.env[env]; - } - } - describe('http', () => { const uri = httpUri; const proxy = 'http://hello.there:8080'; @@ -131,8 +121,9 @@ describe('agents', () => { const uri = httpsUri; const proxy = 'https://hello.there:8080'; - beforeEach(cleanProxyEnv); - afterEach(cleanProxyEnv); + beforeEach(() => { + sandbox.stub(process, 'env').value({}); + }); noProxyEnvVars.forEach(noProxEnvVar => { it(`should respect the proxy option, even if is in ${noProxEnvVar} env var`, () => {