Skip to content

Commit

Permalink
feat(http): support HTTPS_PROXY and NO_PROXY environment variables
Browse files Browse the repository at this point in the history
By default, use the system-defined HTTPS proxy settings when connecting
to the Cloudflare API. This module will use the proxy defined by
HTTPS_PROXY unless the Cloudflare API host is matched by the pattern
specified in NO_PROXY.

Bug: cloudflare#36
  • Loading branch information
terinjokes committed Jan 11, 2018
1 parent 6b8177a commit fdd155a
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 5 deletions.
34 changes: 32 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const prototypal = require('es-class');
const auto = require('autocreate');

const Client = require('./lib/Client');
const proxy = require('./lib/proxy');

/* eslint-disable global-require */
const resources = {
Expand All @@ -23,6 +24,31 @@ const resources = {
};
/* eslint-enable global-require */

/**
* withEnvProxy configures an HTTPS proxy if required to reach the Cloudflare API.
*
* @private
* @param {Object} opts - The current Cloudflare options
*/
const withEnvProxy = function withEnvProxy(opts) {
/* eslint-disable no-process-env */
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
const noProxy = process.env.NO_PROXY || process.env.no_proxy;
/* eslint-enable no-process-env */

if (httpsProxy) {
const agent = proxy.proxyAgent(
httpsProxy,
noProxy,
'https://api.cloudflare.com'
);

if (agent) {
opts.agent = agent;
}
}
};

/**
* Constructs and returns a new Cloudflare API client with the specified authentication.
*
Expand All @@ -41,10 +67,14 @@ const resources = {
const Cloudflare = auto(
prototypal({
constructor: function constructor(auth) {
const client = new Client({
const opts = {
email: auth && auth.email,
key: auth && auth.key,
});
};

withEnvProxy(opts);

const client = new Client(opts);

Object.defineProperty(this, '_client', {
value: client,
Expand Down
4 changes: 4 additions & 0 deletions lib/Getter.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ const assign = require('object-assign');
const got = require('got');

module.exports = prototypal({
constructor: function constructor(options) {
this._agent = options.agent;
},
got(uri, options) {
options = assign({}, options);
options.agent = this._agent;

return got(uri, options);
},
Expand Down
39 changes: 39 additions & 0 deletions lib/proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright (C) 2014-present Cloudflare, Inc.
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

'use strict';

const shouldProxy = require('should-proxy');
const HttpsProxyAgent = require('https-proxy-agent');

/**
* proxyAgent returns an HTTPS agent to use to access the base URL.
*
* @private
* @param {string} httpsProxy - HTTPS Proxy URL
* @param {string} noProxy - URLs that should be excluded from proxying
* @param {string} base - The client base URL
* @returns {https.Agent|null} - The HTTPS agent, if required to access the base URL.
*/
const proxyAgent = function proxyAgent(httpsProxy, noProxy, base) {
if (!httpsProxy) {
return null;
}
noProxy = noProxy || '';

const ok = shouldProxy(base, {
no_proxy: noProxy, // eslint-disable-line camelcase
});

if (!ok) {
return null;
}

return new HttpsProxyAgent(httpsProxy);
};

module.exports.proxyAgent = proxyAgent;
39 changes: 36 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
"autocreate": "^1.1.0",
"es-class": "^2.1.1",
"got": "^6.3.0",
"https-proxy-agent": "^2.1.1",
"object-assign": "^4.1.0",
"should-proxy": "^1.0.4",
"url-pattern": "^1.0.3"
},
"devDependencies": {
Expand Down
76 changes: 76 additions & 0 deletions test/proxy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (C) 2014-present Cloudflare, Inc.
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/

'use strict';

const assert = require('power-assert');
const mocha = require('mocha');

const proxy = require('../lib/proxy');

const describe = mocha.describe;
const it = mocha.it;

describe('proxy agents', () => {
it('should not return an agent when parameters are not set', done => {
const tests = [
['', '', 'example.com'],
[null, null, 'http://example.com/'],
];

tests.forEach(test => {
const agent = proxy.proxyAgent(test[0], test[1], test[2]);

assert.ok(!agent, 'agent was unexpected');
});

done();
});

it('should not return an agent when noProxy matches base', done => {
const tests = [
['http://10.0.0.1:1234', 'example.com', 'http://example.com'],
['http://10.0.0.1:1234', '.example.com', 'http://example.com'],
['http://10.0.0.1:1234', 'foobar.com,.example.com', 'http://example.com'],
];

tests.forEach(test => {
const agent = proxy.proxyAgent(test[0], test[1], test[2]);

assert.ok(!agent, 'agent was unexpected');
});

done();
});

it('should return an agent when noProxy is not set', done => {
const tests = [
['http://10.0.0.1:1234', null, 'http://example.com'],
['http://10.0.0.1:1234', '', 'http://example.com'],
];

tests.forEach(test => {
const agent = proxy.proxyAgent(test[0], test[1], test[2]);

assert.ok(agent, 'expected an agent');
});

done();
});

it("should return an agent when noProxy doesn't match", done => {
const agent = proxy.proxyAgent(
'http://10.0.0.1:1234',
'.example.com',
'https://example.org'
);

assert.ok(agent, 'expected an agent');

done();
});
});

0 comments on commit fdd155a

Please sign in to comment.