Skip to content

Commit

Permalink
SNOW-734920: Cache all types of HTTPS agent
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-dprzybysz authored and sfc-gh-pmotacki committed Sep 20, 2023
1 parent 472c467 commit bdf6b3c
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 160 deletions.
132 changes: 66 additions & 66 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,71 +41,71 @@ jobs:
name: artifacts
path: artifacts

test-mac:
needs: build
name: Tests on Mac
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
cloud: [ 'AWS', 'AZURE', 'GCP' ]
nodeVersion: [ '14.x', '16.x', '18.x']
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.nodeVersion }}
- uses: actions/setup-python@v1
with:
python-version: '3.7'
- name: Download Build Artifacts
uses: actions/download-artifact@v1
with:
name: artifacts
- name: Install Homebrew Bash
shell: bash
run: brew install bash
- name: Tests
shell: bash
env:
PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}
CLOUD_PROVIDER: ${{ matrix.cloud }}
run: /usr/local/bin/bash ./ci/test_mac.sh
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
with:
# without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557
token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }}
fail_ci_if_error: true

test-windows:
needs: build
name: Tests on Windows
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
cloud: [ 'AWS', 'AZURE', 'GCP' ]
nodeVersion: [ '14.x', '16.x', '18.x']
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.nodeVersion }}
- uses: actions/setup-python@v1
with:
python-version: '3.7'
architecture: 'x64'
- name: Download Build Artifacts
uses: actions/download-artifact@v1
with:
name: artifacts
- name: Tests
shell: cmd
env:
PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}
CLOUD_PROVIDER: ${{ matrix.cloud }}
run: ci\\test_windows.bat
# test-mac:
# needs: build
# name: Tests on Mac
# runs-on: macos-latest
# strategy:
# fail-fast: false
# matrix:
# cloud: [ 'AWS', 'AZURE', 'GCP' ]
# nodeVersion: [ '14.x', '16.x', '18.x']
# steps:
# - uses: actions/checkout@v1
# - uses: actions/setup-node@v1
# with:
# node-version: ${{ matrix.nodeVersion }}
# - uses: actions/setup-python@v1
# with:
# python-version: '3.7'
# - name: Download Build Artifacts
# uses: actions/download-artifact@v1
# with:
# name: artifacts
# - name: Install Homebrew Bash
# shell: bash
# run: brew install bash
# - name: Tests
# shell: bash
# env:
# PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}
# CLOUD_PROVIDER: ${{ matrix.cloud }}
# run: /usr/local/bin/bash ./ci/test_mac.sh
# - name: Upload coverage reports to Codecov
# uses: codecov/codecov-action@v3
# with:
# # without the token code cov may fail because of Github limits https://github.com/codecov/codecov-action/issues/557
# token: ${{ secrets.CODE_COV_UPLOAD_TOKEN }}
# fail_ci_if_error: true
#
# test-windows:
# needs: build
# name: Tests on Windows
# runs-on: windows-latest
# strategy:
# fail-fast: false
# matrix:
# cloud: [ 'AWS', 'AZURE', 'GCP' ]
# nodeVersion: [ '14.x', '16.x', '18.x']
# steps:
# - uses: actions/checkout@v1
# - uses: actions/setup-node@v1
# with:
# node-version: ${{ matrix.nodeVersion }}
# - uses: actions/setup-python@v1
# with:
# python-version: '3.7'
# architecture: 'x64'
# - name: Download Build Artifacts
# uses: actions/download-artifact@v1
# with:
# name: artifacts
# - name: Tests
# shell: cmd
# env:
# PARAMETERS_SECRET: ${{ secrets.PARAMETERS_SECRET }}
# CLOUD_PROVIDER: ${{ matrix.cloud }}
# run: ci\\test_windows.bat

test-linux:
needs: build
Expand All @@ -115,7 +115,7 @@ jobs:
fail-fast: false
matrix:
image: [ 'nodejs-centos7-node14']
cloud: [ 'AWS', 'AZURE', 'GCP' ]
cloud: [ 'AWS' ]
steps:
- uses: actions/checkout@v1
- name: Download Build Artifacts
Expand Down
12 changes: 4 additions & 8 deletions lib/http/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,17 @@ HttpClient.prototype.request = function (options)
};

let mock;
if (this._connectionConfig.agentClass)
{
if (this._connectionConfig.agentClass) {
mock = {
agentClass: this._connectionConfig.agentClass
}
}

// add the agent and proxy options
const agentAndProxyOptions = this.getAgentAndProxyOptions(
url, this._connectionConfig.getProxy(),
mock);
const agent = this.getAgent(url, this._connectionConfig.getProxy(), mock);

requestOptions.data = requestOptions.body;
requestOptions.httpsAgent = agentAndProxyOptions.agentClass(agentAndProxyOptions.agentOptions);
requestOptions.httpsAgent.keepAlive = agentAndProxyOptions.agentOptions.keepAlive;
requestOptions.httpsAgent = agent;
requestOptions.retryDelay = this.constructExponentialBackoffStrategy();

request = axios.request(requestOptions).then(response => {
Expand Down Expand Up @@ -210,7 +206,7 @@ HttpClient.prototype.getRequestModule = function ()
*
* @returns {*}
*/
HttpClient.prototype.getAgentAndProxyOptions = function (url, proxy, mock)
HttpClient.prototype.getAgent = function (url, proxy, mock)
{
return null;
};
Expand Down
126 changes: 55 additions & 71 deletions lib/http/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,92 +41,76 @@ function NodeHttpClient(connectionConfig)

Util.inherits(NodeHttpClient, Base);

/**
* @inheritDoc
*/
NodeHttpClient.prototype.getAgentAndProxyOptions = function (url, proxy, mock)
{
const isHttps = Url.parse(url).protocol === 'https:';
let agentClass;
let agentOptions = { keepAlive: true };
let options;
var bypassProxy = false;
const httpsAgentCache = new Map();

function getFromCacheOrCreate(agentClass, options, url) {
const parsed = Url.parse(url);
const protocol = parsed.protocol || 'http:';
const port = parsed.port || (protocol === 'http:' ? '80' : '443');
const agentId = `${protocol}//${parsed.hostname}:${port}`
if (httpsAgentCache.has(agentId)) {
Logger.getInstance().trace(`Get agent ${agentId} from cache`);
return httpsAgentCache.get(agentId);
} else {
agent = agentClass(options)
httpsAgentCache.set(agentId, agent);
Logger.getInstance().trace(`####### CREATE NEW AGENT: ${agentId}`)
Logger.getInstance().trace(`Create and add to cache new agent ${agentId}`);
return agent;
}
}

function prepareProxyAgentOptions(agentOptions, proxy) {
agentOptions.host = proxy.host;
agentOptions.port = proxy.port;
agentOptions.protocol = proxy.protocol;
if (proxy.user && proxy.password) {
agentOptions.user = proxy.user;
agentOptions.password = proxy.password;
}
}

if (proxy && proxy.noProxy)
{
function isBypassProxy(proxy, url, bypassProxy) {
if (proxy && proxy.noProxy) {
var bypassList = proxy.noProxy.split("|");
for (let i = 0; i < bypassList.length; i++)
{
for (let i = 0; i < bypassList.length; i++) {
host = bypassList[i].trim();
host = host.replace("*", ".*?");
var matches = url.match(host);
if (matches) {
Logger.getInstance().debug("bypassing proxy for %s", url);
bypassProxy = true;
return true;
}
}
}
return false;
}

if (isHttps && mock)
{
agentClass = mock.agentClass;
options = {
agentClass: agentClass,
agentOptions: agentOptions
};
}
else if (isHttps)
{
if (proxy && !bypassProxy)
{
agentClass = HttpsProxyAgent;
agentOptions.host = proxy.host;
agentOptions.port = proxy.port;
agentOptions.user = proxy.user;
agentOptions.password = proxy.password;
agentOptions.protocol = proxy.protocol;
}
else
{
agentClass = HttpsAgent;
}
/**
* @inheritDoc
*/
NodeHttpClient.prototype.getAgent = function (url, proxy, mock) {
const isHttps = Url.parse(url).protocol === 'https:';
let agentOptions = {keepAlive: true};
var bypassProxy = isBypassProxy(proxy, url);

options =
{
agentClass: agentClass,
agentOptions: agentOptions
};
if (mock) {
return mock.agentClass(agentOptions)
}
else if (proxy && !bypassProxy)
{
options =
{
proxy:
{
protocol: 'http:',
hostname: proxy.host,
port: proxy.port
},
agentClass: HttpAgent,
agentOptions: agentOptions
};
if (proxy.user && proxy.password)
{
options.proxy.user = proxy.user;
options.proxy.password = proxy.password;

if (isHttps) {
if (proxy && !bypassProxy) {
const proxyAgentOptions = prepareProxyAgentOptions(agentOptions, proxy)
return getFromCacheOrCreate(HttpsProxyAgent, proxyAgentOptions, url);
} else {
return getFromCacheOrCreate(HttpsAgent, agentOptions, url);
}
} else if (proxy && !bypassProxy) {
let proxyAgentOptions = prepareProxyAgentOptions();
return getFromCacheOrCreate(HttpAgent, proxyAgentOptions, url);
} else {
return getFromCacheOrCreate(HttpAgent, agentOptions, url);
}
else
{
options =
{
agentClass: HttpAgent,
agentOptions: agentOptions
};
}
// otherwise, just use the default agent used by request.js

return options || {};
};

module.exports = NodeHttpClient;
32 changes: 19 additions & 13 deletions test/integration/ocsp_mock/testConnectionOcspMock.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const connOption = require('../connectionOptions');
const Errors = require('../../../lib/errors');
const ErrorCodes = Errors.codes;
const HttpsMockAgent = require('./https_ocsp_mock_agent');
const {configureLogger} = require("../../configureLogger");
const testUtil = require("../testUtil");

function cloneConnOption(connOption)
{
Expand All @@ -24,6 +26,11 @@ describe('Connection test with OCSP Mock', function ()
{
const valid = cloneConnOption(connOption.valid);
const isHttps = valid.accessUrl.startsWith("https");
before(async () =>
{
configureLogger('TRACE');
});


function connect(errcode, connection, callback)
{
Expand Down Expand Up @@ -76,37 +83,36 @@ describe('Connection test with OCSP Mock', function ()
);
});

it('Connection failure with OCSP unknown error', function (done)
{
it('Connection failure with OCSP unknown error', function (done) {
valid.agentClass = HttpsMockAgent.HttpsMockAgentOcspUnkwown;
const connection = snowflake.createConnection(valid);

async.series([
function (callback)
{
connect(ErrorCodes.ERR_OCSP_UNKNOWN, connection, callback);
function (callback) {
try {
connect(ErrorCodes.ERR_OCSP_UNKNOWN, connection, callback);
} catch (err){
console.log(`err ${err}`);
}

},
function (callback)
{
function (callback) {
destroy(connection, callback);
}
],
done
);
});

it('Connection failure with invalid validity OCSP error', function (done)
{
it('Connection failure with invalid validity OCSP error', function (done) {
valid.agentClass = HttpsMockAgent.HttpsMockAgentOcspInvalid;
const connection = snowflake.createConnection(valid);

async.series([
function (callback)
{
function (callback) {
connect(ErrorCodes.ERR_OCSP_INVALID_VALIDITY, connection, callback);
},
function (callback)
{
function (callback) {
destroy(connection, callback);
}
],
Expand Down
Loading

0 comments on commit bdf6b3c

Please sign in to comment.