diff --git a/README.md b/README.md index bfd0500d4c..e212803d02 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,27 @@ const client = require('twilio')(accountSid, authToken, { }); ``` +### Set HTTP Agent Options + +`twilio-node` allows you to set HTTP Agent Options in the Request Client. This feature allows you to re-use your connections. To enable this feature, instantiate the Twilio client with the `keepAlive` flag set to `true`. + +Optionally, the socket timeout and maximum number of sockets can also be set. See the example below: + +```javascript +const accountSid = process.env.TWILIO_ACCOUNT_SID; +const authToken = process.env.TWILIO_AUTH_TOKEN; + +const client = require('twilio')(accountSid, authToken, { + timeout: 30000, // HTTPS agent's socket timeout in milliseconds, default is 30000 + keepAlive: true, // https.Agent keepAlive option, default is false + keepAliveMsecs: 1000, // https.Agent keepAliveMsecs option in milliseconds, default is 1000 + maxSockets: 20, // https.Agent maxSockets option, default is 20 + maxTotalSockets: 100, // https.Agent maxTotalSockets option, default is 100 + maxFreeSockets: 5, // https.Agent maxFreeSockets option, default is 5 + scheduling: "lifo", // https.Agent scheduling option, default is 'lifo' +}); +``` + ### Specify Region and/or Edge To take advantage of Twilio's [Global Infrastructure](https://www.twilio.com/docs/global-infrastructure), specify the target Region and/or Edge for the client: diff --git a/spec/unit/base/RequestClient.spec.js b/spec/unit/base/RequestClient.spec.js index 310b92ef37..b16e697fae 100644 --- a/spec/unit/base/RequestClient.spec.js +++ b/spec/unit/base/RequestClient.spec.js @@ -57,19 +57,17 @@ describe("RequestClient constructor", function () { 30000 ); expect(requestClient.axios.defaults.httpsAgent.options.keepAlive).toBe( - undefined + true ); expect(requestClient.axios.defaults.httpsAgent.options.keepAliveMsecs).toBe( undefined ); - expect(requestClient.axios.defaults.httpsAgent.options.maxSockets).toBe( - undefined - ); + expect(requestClient.axios.defaults.httpsAgent.options.maxSockets).toBe(20); expect( requestClient.axios.defaults.httpsAgent.options.maxTotalSockets - ).toBe(undefined); + ).toBe(100); expect(requestClient.axios.defaults.httpsAgent.options.maxFreeSockets).toBe( - undefined + 5 ); expect(requestClient.axios.defaults.httpsAgent.options.scheduling).toBe( undefined @@ -156,14 +154,12 @@ describe("RequestClient constructor", function () { expect(requestClient.axios.defaults.httpsAgent.options.keepAliveMsecs).toBe( undefined ); - expect(requestClient.axios.defaults.httpsAgent.options.maxSockets).toBe( - undefined - ); + expect(requestClient.axios.defaults.httpsAgent.options.maxSockets).toBe(20); expect( requestClient.axios.defaults.httpsAgent.options.maxTotalSockets ).toEqual(1500); expect(requestClient.axios.defaults.httpsAgent.options.maxFreeSockets).toBe( - undefined + 5 ); expect(requestClient.axios.defaults.httpsAgent.options.scheduling).toEqual( "lifo" @@ -222,7 +218,7 @@ describe("lastResponse and lastRequest defined", function () { expect(client.lastRequest.headers).toEqual({ "test-header-key": "test-header-value", Authorization: "Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk", - Connection: "close", + Connection: "keep-alive", }); expect(client.lastRequest.data).toEqual({ "test-data-key": "test-data-value", @@ -288,7 +284,7 @@ describe("lastRequest defined, lastResponse undefined", function () { expect(client.lastRequest.headers).toEqual({ "test-header-key": "test-header-value", Authorization: "Basic dGVzdC11c2VybmFtZTp0ZXN0LXBhc3N3b3Jk", - Connection: "close", + Connection: "keep-alive", }); expect(client.lastRequest.data).toEqual({ "test-data-key": "test-data-value", diff --git a/src/base/BaseTwilio.ts b/src/base/BaseTwilio.ts index f6651c8a7c..8cac1826d1 100644 --- a/src/base/BaseTwilio.ts +++ b/src/base/BaseTwilio.ts @@ -19,7 +19,20 @@ namespace Twilio { logLevel?: string; userAgentExtensions?: string[]; autoRetry?: boolean; + maxRetryDelay?: number; maxRetries?: number; + + /** + https.Agent options + */ + timeout?: number; + keepAlive?: boolean; + keepAliveMsecs?: number; + maxSockets?: number; + maxTotalSockets?: number; + maxFreeSockets?: number; + scheduling?: "fifo" | "lifo" | undefined; + ca?: string | Buffer; } export interface RequestOpts { @@ -51,8 +64,22 @@ namespace Twilio { edge?: string; region?: string; logLevel?: string; - autoRetry: boolean; + autoRetry?: boolean; + maxRetryDelay?: number; maxRetries?: number; + + /** + https.Agent options + */ + timeout?: number; + keepAlive?: boolean; + keepAliveMsecs?: number; + maxSockets?: number; + maxTotalSockets?: number; + maxFreeSockets?: number; + scheduling?: "fifo" | "lifo" | undefined; + ca?: string | Buffer; + userAgentExtensions?: string[]; _httpClient?: RequestClient; @@ -99,7 +126,17 @@ namespace Twilio { this.opts.logLevel ?? this.env.TWILIO_LOG_LEVEL ?? process.env.TWILIO_LOG_LEVEL; + + this.timeout = this.opts.timeout; + this.keepAlive = this.opts.keepAlive; + this.keepAliveMsecs = this.opts.keepAliveMsecs; + this.maxSockets = this.opts.maxSockets; + this.maxTotalSockets = this.opts.maxTotalSockets; + this.maxFreeSockets = this.opts.maxFreeSockets; + this.scheduling = this.opts.scheduling; + this.ca = this.opts.ca; this.autoRetry = this.opts.autoRetry || false; + this.maxRetryDelay = this.opts.maxRetryDelay; this.maxRetries = this.opts.maxRetries; this.userAgentExtensions = this.opts.userAgentExtensions || []; this._httpClient = this.opts.httpClient; @@ -120,7 +157,16 @@ namespace Twilio { get httpClient() { if (!this._httpClient) { this._httpClient = new RequestClient({ + timeout: this.timeout, + keepAlive: this.keepAlive, + keepAliveMsecs: this.keepAliveMsecs, + maxSockets: this.maxSockets, + maxTotalSockets: this.maxTotalSockets, + maxFreeSockets: this.maxFreeSockets, + scheduling: this.scheduling, + ca: this.ca, autoRetry: this.autoRetry, + maxRetryDelay: this.maxRetryDelay, maxRetries: this.maxRetries, }); } diff --git a/src/base/RequestClient.ts b/src/base/RequestClient.ts index a7d1c5531b..02c9134fe9 100644 --- a/src/base/RequestClient.ts +++ b/src/base/RequestClient.ts @@ -15,6 +15,9 @@ const DEFAULT_TIMEOUT = 30000; const DEFAULT_INITIAL_RETRY_INTERVAL_MILLIS = 100; const DEFAULT_MAX_RETRY_DELAY = 3000; const DEFAULT_MAX_RETRIES = 3; +const DEFAULT_MAX_SOCKETS = 20; +const DEFAULT_MAX_FREE_SOCKETS = 5; +const DEFAULT_MAX_TOTAL_SOCKETS = 100; interface BackoffAxiosRequestConfig extends AxiosRequestConfig { /** @@ -73,6 +76,7 @@ class RequestClient { autoRetry: boolean; maxRetryDelay: number; maxRetries: number; + keepAlive: boolean; /** * Make http request @@ -94,15 +98,16 @@ class RequestClient { this.autoRetry = opts.autoRetry || false; this.maxRetryDelay = opts.maxRetryDelay || DEFAULT_MAX_RETRY_DELAY; this.maxRetries = opts.maxRetries || DEFAULT_MAX_RETRIES; + this.keepAlive = opts.keepAlive !== false; // construct an https agent let agentOpts: https.AgentOptions = { timeout: this.defaultTimeout, - keepAlive: opts.keepAlive, + keepAlive: this.keepAlive, keepAliveMsecs: opts.keepAliveMsecs, - maxSockets: opts.maxSockets, - maxTotalSockets: opts.maxTotalSockets, - maxFreeSockets: opts.maxFreeSockets, + maxSockets: opts.maxSockets || DEFAULT_MAX_SOCKETS, // no of sockets open per host + maxTotalSockets: opts.maxTotalSockets || DEFAULT_MAX_TOTAL_SOCKETS, // no of sockets open in total + maxFreeSockets: opts.maxFreeSockets || DEFAULT_MAX_FREE_SOCKETS, // no of free sockets open per host scheduling: opts.scheduling, ca: opts.ca, }; @@ -165,11 +170,8 @@ class RequestClient { var headers = opts.headers || {}; - if (!headers.Connection && !headers.connection && opts.forever) { - headers.Connection = "keep-alive"; - } else if (!headers.Connection && !headers.connection) { - headers.Connection = "close"; - } + if (!headers.Connection && !headers.connection) + headers.Connection = this.keepAlive ? "keep-alive" : "close"; let auth = undefined;