Skip to content

Commit fd9a29b

Browse files
authored
Merge pull request #175 from CyberSource/connection-pooling
Using agentkeepalive to re-use old connection
2 parents 34d4f63 + 357ce74 commit fd9a29b

File tree

5 files changed

+290
-77
lines changed

5 files changed

+290
-77
lines changed

generator/cybersource-javascript-template/ApiClient.mustache

Lines changed: 127 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
{{>licenseInfo}}
22
'use strict';
33

4+
// Module-level agent pool for connection reuse
5+
const agentPool = new Map();
6+
47
(function(root, factory) {
58
if (typeof define === 'function' && define.amd) {
69
// AMD. Register as an anonymous module.
7-
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest'], factory);
10+
define(['axios', 'axios-cookiejar-support', 'https-proxy-agent', 'https', 'querystring', 'agentkeepalive', 'crypto', 'Authentication/MerchantConfig', 'Authentication/Logger', 'Authentication/Constants', 'Authentication/Authorization', 'Authentication/PayloadDigest'], factory);
811
} else if (typeof module === 'object' && module.exports) {
912
// CommonJS-like environments that support module.exports, like Node.
10-
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'));
13+
module.exports = factory(require('axios'), require('axios-cookiejar-support'), require('https-proxy-agent'), require('https'), require('querystring'), require('agentkeepalive'), require('crypto'), require('./authentication/core/MerchantConfig'), require('./authentication/logging/Logger'), require('./authentication/util/Constants'), require('./authentication/core/Authorization'), require('./authentication/payloadDigest/DigestGenerator'));
1114
} else {
1215
// Browser globals (root is window)
1316
if (!root.{{moduleName}}) {
1417
root.{{moduleName}} = {};
1518
}
16-
root.{{moduleName}}.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest);
19+
root.{{moduleName}}.ApiClient = factory(root.axios, root.axiosCookieJar, root.httpsProxyAgent, root.https, root.querystring, root.AgentKeepAlive, root.crypto, root.Authentication.MerchantConfig, root.Authentication.Logger, root.Authentication.Constants, root.Authentication.Authorization, root.Authentication.PayloadDigest);
1720
}
18-
}(this, function(axios, axiosCookieJar, { HttpsProxyAgent }, https, querystring, MerchantConfig, Logger, Constants, Authorization, PayloadDigest) {
21+
}(this, function(axios, axiosCookieJar, HttpsProxyAgent, https, querystring, AgentKeepAlive, crypto, MerchantConfig, Logger, Constants, Authorization, PayloadDigest) {
1922
{{#emitJSDoc}} /**
2023
* @module {{#invokerPackage}}{{invokerPackage}}/{{/invokerPackage}}ApiClient
2124
* @version {{projectVersion}}
@@ -440,6 +443,106 @@
440443
return tester;
441444
}
442445

446+
/**
447+
* Generates a configuration hash for agent caching
448+
* @param {Object} config - Configuration object with connection parameters
449+
* @returns {String} - Hash string representing the configuration
450+
*/
451+
function generateConfigHash(config) {
452+
const hashParts = [];
453+
454+
// Add relevant configuration properties to the hash
455+
if (config.useProxy) {
456+
hashParts.push(`proxyUrl:${config.proxyUrl}`);
457+
}
458+
if (config.disableSSLVerification !== undefined) hashParts.push(`sslVerify:${!config.disableSSLVerification}`);
459+
if (config.sslCaCert) hashParts.push(`sslCaCert:${config.sslCaCert}`);
460+
if (config.enableClientCert) hashParts.push(`clientCert:${config.certFile}:${config.keyFile}`);
461+
if (config.maxIdleSockets !== undefined) hashParts.push(`maxIdleSockets:${config.maxIdleSockets}`);
462+
if (config.freeSocketTimeout !== undefined) hashParts.push(`freeSocketTimeout:${config.freeSocketTimeout}`);
463+
464+
const concatenated = hashParts.join('|');
465+
return crypto.createHash('sha256').update(concatenated).digest('hex');
466+
}
467+
468+
/**
469+
* Gets or creates an HTTPS agent based on configuration
470+
* @param {Object} config - Configuration object with connection parameters
471+
* @returns {https.Agent} - HTTPS agent instance
472+
*/
473+
function getOrCreateAgent(config) {
474+
const configHash = generateConfigHash(config);
475+
476+
if (agentPool.has(configHash)) {
477+
return agentPool.get(configHash);
478+
}
479+
480+
const fs = require('fs');
481+
482+
// Create agent options with keepAlive enabled
483+
const agentOptions = {
484+
keepAlive: true, // By default, keepAlive is true in agentkeepalive
485+
maxSockets: config.maxIdleSockets,
486+
freeSocketTimeout: config.freeSocketTimeout,
487+
rejectUnauthorized: !config.disableSSLVerification,
488+
};
489+
490+
// Add SSL certificate if provided
491+
if (config.sslCaCert) {
492+
agentOptions.ca = fs.readFileSync(config.sslCaCert);
493+
}
494+
495+
// Add client certificates if enabled
496+
if (config.enableClientCert) {
497+
agentOptions.cert = fs.readFileSync(config.certFile);
498+
agentOptions.key = fs.readFileSync(config.keyFile);
499+
}
500+
501+
// Use the HttpsAgent from the agentkeepalive package
502+
const agent = new AgentKeepAlive.HttpsAgent(agentOptions);
503+
agentPool.set(configHash, agent);
504+
return agent;
505+
}
506+
507+
/**
508+
* Gets or creates an HTTPS proxy agent based on configuration
509+
* @param {String} proxyAddress - Proxy server address
510+
* @param {Number} proxyPort - Proxy server port
511+
* @param {String} proxyUser - Proxy authentication username (optional)
512+
* @param {String} proxyPassword - Proxy authentication password (optional)
513+
* @returns {HttpsProxyAgent} - HTTPS proxy agent instance
514+
*/
515+
function getOrCreateProxyAgent(proxyAddress, proxyPort, proxyUser, proxyPassword, maxIdleSockets) {
516+
const { HttpsProxyAgent } = require('https-proxy-agent');
517+
518+
let proxyUrl;
519+
if (proxyUser && proxyPassword) {
520+
proxyUrl = `http://${proxyUser}:${proxyPassword}@${proxyAddress}:${proxyPort}`;
521+
} else {
522+
proxyUrl = `http://${proxyAddress}:${proxyPort}`;
523+
}
524+
525+
const configHash = generateConfigHash({
526+
useProxy: true,
527+
proxyUrl: proxyUrl,
528+
maxIdleSockets: maxIdleSockets
529+
});
530+
531+
if (agentPool.has(configHash)) {
532+
return agentPool.get(configHash);
533+
}
534+
535+
// Apply the same connection pooling settings as the regular HTTPS agent
536+
const agentOptions = {
537+
keepAlive: true, // Keep the connection alive
538+
maxSockets: maxIdleSockets
539+
};
540+
541+
const agent = new HttpsProxyAgent(proxyUrl, agentOptions);
542+
agentPool.set(configHash, agent);
543+
return agent;
544+
}
545+
443546
// Code added by Infosys dev team
444547

445548
/**
@@ -590,43 +693,29 @@
590693
headers: {}
591694
};
592695

696+
// Use module-level connection pooling with keepAlive
593697
if (useProxy && (proxyAddress != null && proxyAddress != '')) {
594-
if ((proxyUser != null && proxyUser != '') && (proxyPassword!= null && proxyPassword != '')) {
595-
var proxy = process.env.http_proxy || 'http://' + proxyUser + ':' + proxyPassword + '@' + proxyAddress + ':' + proxyPort;
596-
}
597-
else {
598-
var proxy = process.env.http_proxy || 'http://' + proxyAddress + ':' + proxyPort;
599-
}
600-
601-
var agent = new HttpsProxyAgent(proxy);
602-
axiosConfig.httpsAgent = agent;
698+
// Get or create a proxy agent from the pool
699+
axiosConfig.httpsAgent = getOrCreateProxyAgent(proxyAddress, proxyPort, proxyUser, proxyPassword, this.merchantConfig.getMaxIdleSockets());
603700
} else {
604-
if (sslCaCert) {
605-
axiosConfig.httpsAgent = new https.Agent({
606-
rejectUnauthorized: !isSslVerificationDisabled,
607-
ca: fslib.readFileSync(sslCaCert)
608-
});
609-
} else {
610-
axiosConfig.httpsAgent = new https.Agent({
611-
rejectUnauthorized: !isSslVerificationDisabled
612-
});
613-
}
614-
}
615-
616-
if(enableClientCert) {
617-
var certFile = pathlib.resolve(pathlib.join(this.merchantConfig.getClientCertDir(), this.merchantConfig.getSSLClientCert()));
618-
var keyFile = pathlib.resolve(pathlib.join(this.merchantConfig.getClientCertDir(), this.merchantConfig.getPrivateKey()));
619-
620-
if (axiosConfig.httpsAgent) {
621-
axiosConfig.httpsAgent.cert = fslib.readFileSync(certFile);
622-
axiosConfig.httpsAgent.key = fslib.readFileSync(keyFile);
623-
} else {
624-
axiosConfig.httpsAgent = new https.Agent({
625-
rejectUnauthorized: !isSslVerificationDisabled,
626-
cert: fslib.readFileSync(certFile),
627-
key: fslib.readFileSync(keyFile)
628-
});
701+
// Get or create an HTTPS agent from the pool
702+
var certFile, keyFile;
703+
704+
if (enableClientCert) {
705+
certFile = pathlib.resolve(pathlib.join(this.merchantConfig.getClientCertDir(), this.merchantConfig.getSSLClientCert()));
706+
keyFile = pathlib.resolve(pathlib.join(this.merchantConfig.getClientCertDir(), this.merchantConfig.getPrivateKey()));
629707
}
708+
709+
axiosConfig.httpsAgent = getOrCreateAgent({
710+
disableSSLVerification: isSslVerificationDisabled,
711+
sslCaCert: sslCaCert,
712+
enableClientCert: enableClientCert,
713+
certFile: certFile,
714+
keyFile: keyFile,
715+
useProxy: false,
716+
maxIdleSockets: this.merchantConfig.getMaxIdleSockets(),
717+
freeSocketTimeout: this.merchantConfig.getFreeSocketTimeout()
718+
});
630719
}
631720

632721
// apply authentications

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"axios-cookiejar-support": "4.0.7",
1717
"tough-cookie": "4.1.3",
1818
"https-proxy-agent": "^7.0.0",
19+
"agentkeepalive": "^4.6.0",
1920
"app-root-path": "^3.1.0",
2021
"chai": "^4.3.1",
2122
"chai-as-promised": "^7.1.1",

0 commit comments

Comments
 (0)