Skip to content
This repository has been archived by the owner on Apr 21, 2020. It is now read-only.

Commit

Permalink
Begin big dig of making proxies stoppable and startable.
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanbreen committed Feb 2, 2015
1 parent 3631b24 commit 8ec2aba
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 105 deletions.
97 changes: 2 additions & 95 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
var fs = require('fs');
var path = require('path');

var Proxy = require('./proxy');
var ProxyConfig = require('./proxy/config.js');
var proxy_manager = require('./proxy_manager.js');

var logger = require('./logger.js').getLogger();

Expand All @@ -14,98 +10,9 @@ var logger = require('./logger.js').getLogger();
* files and restarting proxies. TODO: Should there be?
*/
exports.init = function(config_dir, cb) {

if (!config_dir) return cb("Failed to open directory " + config_dir);

fs.stat(config_dir, function(err, stat) {
/* istanbul ignore next */
if (err) return cb("Failed to open directory " + config_dir);

if (!stat.isDirectory()) return cb('oauth_reverse_proxy config dir is not a directory');

// Load all proxy configurations.
loadConfigFiles(config_dir, cb);
});
proxy_manager.init(config_dir, cb);
};

/**
* Each proxy is defined by a JSON file that stores the configuration of the proxy.
*/
function loadConfigFiles(config_dir, cb) {

logger.info("Config dir is %s", config_dir);

// Stores all proxies created from configuration files in config_dir. If a proxy can not be loaded
// the config file name will map to the error message instead of a proxy object.
var proxies = {};

fs.readdir(config_dir, function(err, files) {
/* istanbul ignore if */
if (err) return cb(err);

// Fire a callback only once all config files have been processed.
var countdown = files.length;
var wrapped_cb = function() {
--countdown;
if (countdown <= 0) return cb(null, proxies);
};

files.forEach(function(file) {
logger.info('Loading proxy configuration file %s', file);
fs.readFile(config_dir + path.sep + file, {'encoding':'utf8'}, function(err, data) {
try {
// Parse the configuration into an object, create a ProxyConfig around it, and validate
// that the configuration meets our viability requirements.
var config = JSON.parse(data);
var proxy_config = new ProxyConfig(config);

// If the proxy configuration is incorrect, consider the proxy failed.
// CONTROVERSIAL STATEMENT ALERT: we do not consider a configuration error with a
// proxy to be a fatal error for oauth_reverse_proxy. As long as at least 1 configuration
// file is valid, we will proceed with proxy creation. This is to prevent a single
// busted configuration file from DOSing any other proxies by preventing their startup.
var proxy_error = proxy_config.isInvalid();
if (proxy_error) {
logger.error("Failed to load proxy %s due to %s", file, proxy_error);
proxies[file] = proxy_error;
return wrapped_cb();
}
} catch(e) {
logger.error("Failed to load proxy %s due to %s", file, e);
proxies[file] = e.message;
return wrapped_cb();
}

try {
// Create and start a proxy around a validated config.
var proxy = new Proxy(proxy_config);
proxy.start(function(err) {
/* istanbul ignore if */
if (err) {
// CONTROVERSIAL STATEMENT ALERT: we do not consider a startup error with a
// proxy to be a fatal error for oauth_reverse_proxy. As long as at least 1
// proxy starts properly, we will proceed with proxy creation. This is to prevent
// a single busted configuration file from DOSing any other proxies by preventing
// their startup.
proxies[file] = "Failed to start proxy " + proxy_config.service_name + " due to " + err;
return wrapped_cb();
}

logger.info("Started proxy %s", proxy_config.service_name);
proxies[file] = proxy;
wrapped_cb();
});
} catch(e) {
/* istanbul ignore next */
proxies[file] = "Uncaught exception starting proxy " + proxy_config.service_name + ": " + e + "\n" + e.stack;
/* istanbul ignore next */
wrapped_cb();
}
});
});
});
}

// Register the catch-all exception handler. We want to ignore this line for code coverage purposes,
// which the instanbul ignore line accomplishes.
process.on('uncaughtException', /* istanbul ignore next */ function(err) {
Expand Down
2 changes: 1 addition & 1 deletion lib/logger.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var bunyan = require('bunyan');
var default_config = {
name: 'oauth_reverse_proxy',
streams: [{
level: process.env.OAUTH_REVERSE_PROXY_LOG_LEVEL || "warn",
level: process.env.OAUTH_REVERSE_PROXY_LOG_LEVEL || "trace",
stream: process.stdout
}]
};
Expand Down
9 changes: 9 additions & 0 deletions lib/proxy/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
var fs = require('fs');
var util = require('util');

var _ = require('underscore');

var logger = require('../logger.js').getLogger();

/**
Expand Down Expand Up @@ -48,6 +50,13 @@ function ProxyConfig(config) {
Object.defineProperty(this_obj, 'whitelist', { 'value': whitelist, writable: false });
}

/**
* Performs a deep comparison of proxy configs and returns true iff configurations are identical.
*/
ProxyConfig.prototype.equals = function(other_proxy_config) {
return _.isEqual(this, other_proxy_config);
};

/**
* Returns a string matching the first validation that failed when evaluating this proxy config or returns
* undefined if the configuration is valid.
Expand Down
27 changes: 21 additions & 6 deletions lib/proxy/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,27 +145,42 @@ Proxy.prototype.start = function(cb) {

// If the proxy config specifically asks for https, use https. Otherwise, use http.
if (this_obj.config.https) {
var ipv4_server = https.createServer({
this_obj.ipv4_server = https.createServer({
key: fs.readFileSync(this_obj.config.https_key_file),
cert: fs.readFileSync(this_obj.config.https_cert_file)
}, app);
var ipv6_server = https.createServer({
this_obj.ipv6_server = https.createServer({
key: fs.readFileSync(this_obj.config.https_key_file),
cert: fs.readFileSync(this_obj.config.https_cert_file)
}, app);
} else {
var ipv4_server = http.createServer(app);
var ipv6_server = http.createServer(app);
this_obj.ipv4_server = http.createServer(app);
this_obj.ipv6_server = http.createServer(app);
}

// Listen on our 2 servers. If we attempt to listen on a single server, the results will be non-deterministic.
// It works on node 0.10.30 but not on 0.10.35, for example. Separating the two servers appears to work everywhere.
ipv4_server.listen(this_obj.config.from_port, '0.0.0.0');
ipv6_server.listen(this_obj.config.from_port, '::');
this_obj.ipv4_server.listen(this_obj.config.from_port, '0.0.0.0');
this_obj.ipv6_server.listen(this_obj.config.from_port, '::');

cb(null, this_obj);
});
};

/**
* Stop this proxy, shutting down its servers. Note that the servers will continue to hold existing connections until
* they complete: we make no effort to forcibly terminate connections.
*/
Proxy.prototype.stop = function() {

if (this.ipv4_server) {
this.ipv4_server.stop();
}

if (this.ipv6_server) {
this.ipv6_server.stop();
}
};

// Expose Proxy class.
module.exports = Proxy;
2 changes: 1 addition & 1 deletion lib/proxy/keystore.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ ProxyKeystore.prototype.setupWatcher = function() {
keystore_reload_pending = true;
logger.debug("Entering quiet period for file updates in %s", this_obj.oauth_secret_dir);
setTimeout(function() {
// Once the settimeout has fired, allow keystore reloads to be queued again.
// Once the setTimeout has fired, allow keystore reloads to be queued again.
keystore_reload_pending = false;
// We don't care what type of event happened in the key directory. We do a full
// reload of the keystore regardless.
Expand Down

0 comments on commit 8ec2aba

Please sign in to comment.