From e6bfd5d70b0feede2ebf2ce76d5e43c6ccb2c0a3 Mon Sep 17 00:00:00 2001
From: Zachary Carter <zack.carter@gmail.com>
Date: Tue, 22 May 2012 11:55:28 -0700
Subject: [PATCH] Initial commit of new router layer

---
 bin/browserid          |  31 ----------
 bin/router             | 132 +++++++++++++++++++++++++++++++++++++++++
 config/local.json      |   1 +
 lib/configuration.js   |   4 ++
 lib/http_forward.js    |   2 +-
 lib/wsapi.js           |   8 ++-
 scripts/run_locally.js |   6 +-
 7 files changed, 149 insertions(+), 35 deletions(-)
 create mode 100755 bin/router

diff --git a/bin/browserid b/bin/browserid
index 03fa955e4..b0109170d 100755
--- a/bin/browserid
+++ b/bin/browserid
@@ -34,18 +34,6 @@ app = express.createServer();
 
 logger.info("browserid server starting up");
 
-// verify that we have a keysigner configured
-if (!config.get('keysigner_url')) {
-  logger.error('missing required configuration - url for the keysigner (KEYSIGNER_URL in env)');
-  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.
 
@@ -102,25 +90,6 @@ app.use(function(req, resp, next) {
   next();
 });
 
-// #5 - redirection!  redirect requests to the "verifier" or to the "dbwriter"
-// processes
-if (config.get('verifier_url')) {
-  var verifier_url = urlparse(config.get('verifier_url')).validate().normalize();
-  app.use(function(req, res, next) {
-    if (/^\/verify$/.test(req.url)) {
-      forward(
-        verifier_url, req, res,
-        function(err) {
-          if (err) {
-            logger.error("error forwarding request:", err);
-          }
-        });
-    } else {
-      return next();
-    }
-  });
-}
-
 // #6 - verify all JSON responses are objects - prevents regression on issue #217
 app.use(function(req, resp, next) {
   var realRespJSON = resp.json;
diff --git a/bin/router b/bin/router
new file mode 100755
index 000000000..fa000fadf
--- /dev/null
+++ b/bin/router
@@ -0,0 +1,132 @@
+#!/usr/bin/env node
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+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'),
+config = require('../lib/configuration.js'),
+heartbeat = require('../lib/heartbeat.js'),
+logger = require('../lib/logging.js').logger,
+forward = require('../lib/http_forward').forward,
+shutdown = require('../lib/shutdown');
+
+
+var app = undefined;
+
+app = express.createServer();
+
+logger.info("router server starting up");
+
+// verify that we have a keysigner configured
+if (!config.get('keysigner_url')) {
+  logger.error('missing required configuration - url for the keysigner (KEYSIGNER_URL in env)');
+  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);
+}
+
+// verify that we have a browserid configured
+if (!config.get('browserid_url')) {
+  logger.error('missing required configuration - url for browserid (BROWSERID_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.
+
+// #1 - Setup health check / heartbeat middleware.
+// This is in front of logging on purpose.  see issue #537
+heartbeat.setup(app);
+
+// #2 - 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);
+    }
+  }
+}));
+
+// limit all content bodies to 10kb, at which point we'll forcefully
+// close down the connection.
+app.use(express.limit("10kb"));
+
+var statsd_config = config.get('statsd');
+if (statsd_config && statsd_config.enabled) {
+  logger_statsd = require("connect-logger-statsd");
+  app.use(logger_statsd({
+    host: statsd_config.hostname || "localhost",
+    port: statsd_config.port || 8125,
+    prefix: statsd_config.prefix || "browserid.router."
+  }));
+}
+
+// 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();
+    });
+}
+
+// redirect requests to the "verifier" processes
+if (config.get('verifier_url')) {
+  var verifier_url = urlparse(config.get('verifier_url')).validate().normalize();
+  app.use(function(req, res, next) {
+    if (/^\/verify$/.test(req.url)) {
+      forward(
+        verifier_url, req, res,
+        function(err) {
+          if (err) {
+            logger.error("error forwarding request:", err);
+          }
+        });
+    } else {
+      return next();
+    }
+  });
+}
+
+// handle /wsapi writes
+wsapi.setup({
+  router_mode: true,
+  forward_writes: urlparse(config.get('dbwriter_url')).validate().normalize().originOnly()
+}, app);
+
+// Forward all leftover requests to browserid
+var browserid_url = urlparse(config.get('browserid_url')).validate().normalize().originOnly();
+app.use(function(req, res, next) {
+  forward(
+    browserid_url+req.url, req, res,
+    function(err) {
+      if (err) {
+        logger.error("error forwarding request:", err);
+      }
+    });
+});
+
+// #11 - 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 () {});
+
+var bindTo = config.get('bind_to');
+app.listen(bindTo.port, bindTo.host, function(conn) {
+  logger.info("running on http://" + app.address().address + ":" + app.address().port);
+});
diff --git a/config/local.json b/config/local.json
index d437b1691..0c89dc8f1 100644
--- a/config/local.json
+++ b/config/local.json
@@ -4,6 +4,7 @@
   "dbwriter": { "bind_to": { "port": 10004 } },
   "proxy": { "bind_to": { "port": 10006 } },
   "browserid": { "bind_to": { "port": 10002 } },
+  "router": { "bind_to": { "port": 10007 } },
   "use_minified_resources": false,
   "database": {
     "driver": "json"
diff --git a/lib/configuration.js b/lib/configuration.js
index 14d7e5e5e..4c4957e57 100644
--- a/lib/configuration.js
+++ b/lib/configuration.js
@@ -198,6 +198,10 @@ var conf = module.exports = convict({
     format: 'string?',
     env: 'DBWRITER_URL'
   },
+  browserid_url: {
+    format: 'string?',
+    env: 'BROWSERID_URL'
+  },
   process_type: 'string',
   email_to_console: 'boolean = false',
   declaration_of_support_timeout_ms: {
diff --git a/lib/http_forward.js b/lib/http_forward.js
index d88cbd85d..d9408f997 100644
--- a/lib/http_forward.js
+++ b/lib/http_forward.js
@@ -39,7 +39,7 @@ exports.forward = function(dest, req, res, cb) {
   var preq = m.request({
     host: u.hostname,
     port: u.port,
-    path: u.pathname,
+    path: u.path,
     method: req.method,
     agent: false
   }, function(pres) {
diff --git a/lib/wsapi.js b/lib/wsapi.js
index 9ec102464..7b12c40e6 100644
--- a/lib/wsapi.js
+++ b/lib/wsapi.js
@@ -200,6 +200,9 @@ exports.setup = function(options, app) {
         return bodyParser(req, resp, function() {
           next();
         });
+      } else if (options.router_mode) {
+        // skip wsapi request, let browserid middleware handle forwards
+        return next();
       } else {
         // this is not a forwarded operation, perform full parsing and validation
         return cookieParser(req, resp, function() {
@@ -324,7 +327,10 @@ exports.setup = function(options, app) {
   app.use(function(req, resp, next) {
     var purl = url.parse(req.url);
 
-    if (purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX) {
+    if (options.router_mode) {
+      // skip wsapi request, let browserid middleware handle forwards
+      return next();
+    } else if (purl.pathname.substr(0, WSAPI_PREFIX.length) === WSAPI_PREFIX) {
       const operation = purl.pathname.substr(WSAPI_PREFIX.length);
 
       // the fake_verification wsapi is implemented elsewhere.
diff --git a/scripts/run_locally.js b/scripts/run_locally.js
index fa12dca0b..0d7f2adbb 100755
--- a/scripts/run_locally.js
+++ b/scripts/run_locally.js
@@ -29,7 +29,8 @@ var daemonsToRun = {
     HOST: HOST
   },
   proxy: { },
-  browserid: { }
+  browserid: { },
+  router: { }
 };
 
 // route outbound HTTP through our in-tree proxy to always test said codepath
@@ -53,8 +54,9 @@ 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";
+process.env['ROUTER_URL'] = 'http://' + HOST + ":10007";
 
-process.env['URL'] = process.env['BROWSERID_URL'];
+process.env['URL'] = process.env['ROUTER_URL'];
 
 // if the environment is a 'test_' environment, then we'll use an
 // ephemeral database
-- 
GitLab