From 34d9743fb33d16541ef3ff3f5bca645daa02d881 Mon Sep 17 00:00:00 2001 From: Lloyd Hilaiel <lloyd@hilaiel.com> Date: Thu, 10 Nov 2011 10:21:59 -0700 Subject: [PATCH] add the dbwriter process. it's not yet hooked up, but starts up and runs, and included in health check. --- bin/browserid | 15 ++++- bin/dbwriter | 137 ++++++++++++++++++++++++++++++++++++++++- lib/configuration.js | 6 ++ lib/wsapi.js | 9 ++- scripts/run_locally.js | 5 ++ 5 files changed, 167 insertions(+), 5 deletions(-) diff --git a/bin/browserid b/bin/browserid index 2eb922c05..9671ad3ee 100755 --- a/bin/browserid +++ b/bin/browserid @@ -68,6 +68,12 @@ if (!config.get('keysigner_url')) { process.exit(1); } +// verify that we have a dbwriter configured +if (!config.get('dbwriter_url')) { + logger.error('missing required configuration - url for the dbwriter (DBWRITER_URL in env)'); + process.exit(1); +} + // NOTE: ordering of middleware registration is important in this file, it is the // order in which middleware will be invoked as requests are processed. @@ -75,7 +81,10 @@ if (!config.get('keysigner_url')) { // This is in front of logging on purpose. see issue #537 heartbeat.setup(app, function(cb) { // let's check stuff! first the heartbeat of our keysigner - heartbeat.check(config.get('keysigner_url'), cb); + heartbeat.check(config.get('keysigner_url'), function(rv) { + if (!rv) return cb(rv); + heartbeat.check(config.get('dbwriter_url'), cb); + }); }); // #2 - logging! all requests other than __heartbeat__ are logged @@ -141,7 +150,9 @@ app.use(function(req, resp, next) { config.performSubstitution(app); // #8 - handle /wsapi requests -wsapi.setup(app); +wsapi.setup({ + forward_writes: config.get('dbwriter_url') +}, app); // #9 - handle views for dynamicish content views.setup(app); diff --git a/bin/dbwriter b/bin/dbwriter index 79bfbf63b..91296cae5 100755 --- a/bin/dbwriter +++ b/bin/dbwriter @@ -1,3 +1,138 @@ #!/usr/bin/env node -require('./browserid'); +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla BrowserID. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2011 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const +fs = require('fs'), +path = require('path'), +url = require('url'), +http = require('http'); +urlparse = require('urlparse'), +express = require('express'); + +const +wsapi = require('../lib/wsapi.js'), +httputils = require('../lib/httputils.js'), +secrets = require('../lib/secrets.js'), +db = require('../lib/db.js'), +config = require('../lib/configuration.js'), +heartbeat = require('../lib/heartbeat.js'), +metrics = require('../lib/metrics.js'), +logger = require('../lib/logging.js').logger, +forward = require('../lib/browserid/http_forward'), +shutdown = require('../lib/shutdown'), +views = require('../lib/browserid/views.js'); + +var app = undefined; + +app = express.createServer(); + +logger.info("dbwriter starting up"); + +// Setup health check / heartbeat middleware. +// This is in front of logging on purpose. see issue #537 +heartbeat.setup(app); + +// logging! all requests other than __heartbeat__ are logged +app.use(express.logger({ + format: config.get('express_log_format'), + stream: { + write: function(x) { + logger.info(typeof x === 'string' ? x.trim() : x); + } + } +})); + +// Add Strict-Transport-Security headers if we're serving over SSL +if (config.get('scheme') == 'https') { + app.use(function(req, resp, next) { + // expires in 30 days, include subdomains like www + resp.setHeader("Strict-Transport-Security", "max-age=2592000; includeSubdomains"); + next(); + }); +} + +// prevent framing of everything. content underneath that needs to be +// framed must explicitly remove the x-frame-options +app.use(function(req, resp, next) { + resp.setHeader('x-frame-options', 'DENY'); + next(); +}); + +// verify all JSON responses are objects - prevents regression on issue #217 +app.use(function(req, resp, next) { + var realRespJSON = resp.json; + resp.json = function(obj) { + if (!obj || typeof obj !== 'object') { + logger.error("INTERNAL ERROR! *all* json responses must be objects"); + throw "internal error"; + } + realRespJSON.call(resp, obj); + }; + return next(); +}); + +// handle /wsapi requests +wsapi.setup({ + only_write_apis: true +}, app); + +// calls to /code_update from localhost will restart the daemon, +// this feature is not externally accessible and is only used by +// the update logic +shutdown.installUpdateHandler(app, function(readyForShutdown) { + logger.debug("closing database connection"); + db.close(readyForShutdown) +}); + +// open the databse +db.open(config.get('database'), function (error) { + if (error) { + logger.error("can't open database: " + error); + // let async logging flush, then exit 1 + return setTimeout(function() { process.exit(1); }, 0); + } + + // shut down express gracefully on SIGINT + shutdown.handleTerminationSignals(app, function(readyForShutdown) { + db.close(readyForShutdown) + }); + + var bindTo = config.get('bind_to'); + app.listen(bindTo.port, bindTo.host, function() { + logger.info("running on http://" + app.address().address + ":" + app.address().port); + }); +}); diff --git a/lib/configuration.js b/lib/configuration.js index 87230eea6..5151ffba8 100644 --- a/lib/configuration.js +++ b/lib/configuration.js @@ -158,6 +158,12 @@ if (process.env['KEYSIGNER_URL']) { g_config.keysigner_url = url; } +if (process.env['DBWRITER_URL']) { + var url = urlparse(process.env['DBWRITER_URL']).validate().normalize(); + if (!url.port) url.port = (url.scheme === 'http') ? 80 : 443; + g_config.dbwriter_url = url; +} + // extract smtp params from the environment if (!g_config.smtp) { g_config.smtp = { diff --git a/lib/wsapi.js b/lib/wsapi.js index 3c14d7d53..b768db346 100644 --- a/lib/wsapi.js +++ b/lib/wsapi.js @@ -32,7 +32,6 @@ function clearAuthenticatedUser(session) { }); } - function isAuthed(req) { var who; try { @@ -84,7 +83,7 @@ exports.isAuthed = isAuthed; exports.bcryptPassword = bcryptPassword; exports.setAuthenticatedUser = setAuthenticatedUser; -exports.setup = function(app) { +exports.setup = function(options, app) { // XXX: we can and should make all of the logic below only take effect for POST requests // to /wsapi to reduce code run for other requests (cookie parsing, etc) @@ -182,6 +181,12 @@ exports.setup = function(app) { try { var api = require(path.join(__dirname, 'wsapi', f)); + + // don't register read apis if we are configured as a writer + if (options.only_write_apis && !api.writes_db) return; + + // XXX forward writes if options.forward_writes is defined + wsapis[operation] = api; // set up the argument validator diff --git a/scripts/run_locally.js b/scripts/run_locally.js index 6c48078ec..9127578f7 100755 --- a/scripts/run_locally.js +++ b/scripts/run_locally.js @@ -17,6 +17,10 @@ var daemonsToRun = { PORT: 10003, HOST: HOST }, + dbwriter: { + PORT: 10004, + HOST: HOST + }, example: { path: path.join(__dirname, "..", "scripts", "serve_example.js"), PORT: 10001, @@ -32,6 +36,7 @@ var daemonsToRun = { process.env['LOG_TO_CONSOLE'] = 1; // all spawned processes will communicate with the local browserid +process.env['DBWRITER_URL'] = 'http://' + HOST + ":10004"; process.env['BROWSERID_URL'] = 'http://' + HOST + ":10002"; process.env['VERIFIER_URL'] = 'http://' + HOST + ":10000/verify"; process.env['KEYSIGNER_URL'] = 'http://' + HOST + ":10003"; -- GitLab