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..6a7b31b 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -25,6 +25,37 @@ 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 {boolean} + */ +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 +78,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..7b28068 100644 --- a/test/agents.ts +++ b/test/agents.ts @@ -53,6 +53,8 @@ describe('agents', () => { 'HTTPS_PROXY', ]; + const noProxyEnvVars = ['no_proxy', 'NO_PROXY']; + describe('http', () => { const uri = httpUri; const proxy = 'http://hello.there:8080'; @@ -114,6 +116,53 @@ describe('agents', () => { }); }); }); + + describe('no_proxy', () => { + const uri = httpsUri; + const proxy = 'https://hello.there:8080'; + + beforeEach(() => { + sandbox.stub(process, 'env').value({}); + }); + + 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', () => {