diff --git a/src/agents.ts b/src/agents.ts index 55f19b9..cfce7a9 100644 --- a/src/agents.ts +++ b/src/agents.ts @@ -16,19 +16,25 @@ import {Agent as HTTPAgent} from 'http'; import {Agent as HTTPSAgent} from 'https'; +import {parse} from 'url'; import {Options} from './'; -const pool = new Map(); +export const pool = new Map(); + +export type HttpAnyAgent = HTTPAgent | HTTPSAgent; /** * Returns a custom request Agent if one is found, otherwise returns undefined * which will result in the global http(s) Agent being used. * @private * @param {string} uri The request uri - * @param {object} reqOpts The request options - * @returns {Agent|undefined} + * @param {Options} reqOpts The request options + * @returns {HttpAnyAgent|undefined} */ -export function getAgent(uri: string, reqOpts: Options): HTTPAgent | undefined { +export function getAgent( + uri: string, + reqOpts: Options +): HttpAnyAgent | undefined { const isHttp = uri.startsWith('http://'); const proxy = reqOpts.proxy || @@ -37,13 +43,16 @@ export function getAgent(uri: string, reqOpts: Options): HTTPAgent | undefined { process.env.HTTPS_PROXY || process.env.https_proxy; + const poolOptions = Object.assign({}, reqOpts.pool); + if (proxy) { // tslint:disable-next-line variable-name const Agent = isHttp ? require('http-proxy-agent') : require('https-proxy-agent'); - return new Agent(proxy) as HTTPAgent; + const proxyOpts = {...parse(proxy), ...poolOptions}; + return new Agent(proxyOpts); } let key = isHttp ? 'http' : 'https'; @@ -54,7 +63,7 @@ export function getAgent(uri: string, reqOpts: Options): HTTPAgent | undefined { if (!pool.has(key)) { // tslint:disable-next-line variable-name const Agent = isHttp ? HTTPAgent : HTTPSAgent; - pool.set(key, new Agent({keepAlive: true})); + pool.set(key, new Agent({...poolOptions, keepAlive: true})); } } diff --git a/src/index.ts b/src/index.ts index 29c1fba..a419769 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import {Agent} from 'https'; +import {Agent, AgentOptions as HttpsAgentOptions} from 'https'; +import {AgentOptions as HttpAgentOptions} from 'http'; import fetch, * as f from 'node-fetch'; import {PassThrough, Readable} from 'stream'; import * as uuid from 'uuid'; @@ -35,6 +36,7 @@ export interface CoreOptions { proxy?: string; multipart?: RequestPart[]; forever?: boolean; + pool?: HttpsAgentOptions | HttpAgentOptions; } export interface OptionsWithUri extends CoreOptions { diff --git a/test/agents.ts b/test/agents.ts index 9524d16..250b57c 100644 --- a/test/agents.ts +++ b/test/agents.ts @@ -17,8 +17,9 @@ import * as assert from 'assert'; import {describe, it, afterEach} from 'mocha'; import * as http from 'http'; +import * as https from 'https'; import * as sinon from 'sinon'; -import {getAgent} from '../src/agents'; +import {getAgent, pool} from '../src/agents'; // tslint:disable-next-line variable-name const HttpProxyAgent = require('http-proxy-agent'); @@ -30,7 +31,10 @@ describe('agents', () => { const httpsUri = 'https://example.com'; const sandbox = sinon.createSandbox(); - afterEach(() => sandbox.restore()); + afterEach(() => { + sandbox.restore(); + pool.clear(); + }); describe('getAgent', () => { const defaultOptions = {uri: httpUri}; @@ -51,11 +55,22 @@ describe('agents', () => { describe('http', () => { const uri = httpUri; const proxy = 'http://hello.there:8080'; + const proxyExpected = { + hostname: 'hello.there', + port: 8080, + protocol: 'http:', + }; it('should respect the proxy option', () => { const options = Object.assign({proxy}, defaultOptions); const agent = getAgent(uri, options); assert(agent instanceof HttpProxyAgent); + + // tslint:disable-next-line:no-any + const {proxy: proxyActual}: any = agent!; + assert.strictEqual(proxyActual.protocol, proxyExpected.protocol); + assert.strictEqual(proxyActual.hostname, proxyExpected.hostname); + assert.strictEqual(proxyActual.port, proxyExpected.port); }); envVars.forEach(envVar => { @@ -71,11 +86,22 @@ describe('agents', () => { describe('https', () => { const uri = httpsUri; const proxy = 'https://hello.there:8080'; + const proxyExpected = { + hostname: 'hello.there', + port: 8080, + protocol: 'https:', + }; it('should respect the proxy option', () => { const options = Object.assign({proxy}, defaultOptions); const agent = getAgent(uri, options); assert(agent instanceof HttpsProxyAgent); + + // tslint:disable-next-line:no-any + const {proxy: proxyActual}: any = agent!; + assert.strictEqual(proxyActual.protocol, proxyExpected.protocol); + assert.strictEqual(proxyActual.hostname, proxyExpected.hostname); + assert.strictEqual(proxyActual.port, proxyExpected.port); }); envVars.forEach(envVar => { @@ -107,12 +133,12 @@ describe('agents', () => { }); describe('https', () => { - const uri = httpUri; + const uri = httpsUri; const options = Object.assign({forever: true}, defaultOptions); it('should return an http Agent', () => { const agent = getAgent(uri, options)!; - assert(agent instanceof http.Agent); + assert(agent instanceof https.Agent); }); it('should cache the agent', () => { @@ -122,5 +148,71 @@ describe('agents', () => { }); }); }); + + describe('pool', () => { + describe('http', () => { + const uri = httpUri; + + it('should pass AgentOptions from pool config when providing agent', () => { + const options = Object.assign( + { + forever: true, + pool: { + maxSockets: 1000, + }, + }, + defaultOptions + ); + const agent = getAgent(uri, options); + assert.strictEqual(agent!.maxSockets, 1000); + }); + + it('should not set global AgentOptions from only pool config', () => { + const options = Object.assign( + { + pool: { + maxSockets: 1000, + }, + }, + defaultOptions + ); + const agent = getAgent(uri, options); + assert.strictEqual(agent, undefined); + assert.notStrictEqual(http.globalAgent.maxSockets, 1000); + }); + }); + + describe('https', () => { + const uri = httpsUri; + + it('should pass AgentOptions from pool config when providing agent', () => { + const options = Object.assign( + { + forever: true, + pool: { + maxSockets: 1000, + }, + }, + defaultOptions + ); + const agent = getAgent(uri, options); + assert.strictEqual(agent!.maxSockets, 1000); + }); + + it('should not set global AgentOptions from only pool config', () => { + const options = Object.assign( + { + pool: { + maxSockets: 1000, + }, + }, + defaultOptions + ); + const agent = getAgent(uri, options); + assert.strictEqual(agent, undefined); + assert.notStrictEqual(https.globalAgent.maxSockets, 1000); + }); + }); + }); }); }); diff --git a/test/index.ts b/test/index.ts index 0e25b74..bb3dfa8 100644 --- a/test/index.ts +++ b/test/index.ts @@ -20,6 +20,7 @@ import * as nock from 'nock'; import {Readable, PassThrough} from 'stream'; import * as sinon from 'sinon'; import {teenyRequest} from '../src'; +import {pool} from '../src/agents'; // tslint:disable-next-line variable-name const HttpProxyAgent = require('http-proxy-agent'); @@ -38,6 +39,7 @@ function mockJson() { describe('teeny', () => { const sandbox = sinon.createSandbox(); afterEach(() => { + pool.clear(); sandbox.restore(); nock.cleanAll(); });