Parcourir la source

Merge pull request #1635 from zane-techempower/improve-JS

Improve JS frameworks
Mike Smith il y a 10 ans
Parent
commit
56478885d9

+ 17 - 42
frameworks/JavaScript/nodejs/README.md

@@ -2,55 +2,30 @@
 
 This is the NodeJS portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
 
-### JSON Encoding Test
+### There are individual handlers for each DB approach
+The logic for the test cases live in these.
 
-* [JSON test controller/view](hello.js)
-
-### Data-Store/Database Mapping Test
-
-* [DB test controller/model](hello.js)
+* [MySQL raw](handlers/mysql-raw.js)
+* [Sequelize (MySQL)](handlers/sequelize.js)
+* [MongoDB raw](handlers/mongodb-raw.js)
+* [Mongoose (MySQL)](handlers/mongoose.js)
+* [Hiredis (Redis)](handlers/redis)
 
 ## Infrastructure Software Versions
 The tests were run with:
 * [Node.js v0.12.2](http://nodejs.org/)
-* [Mongoose 4.0.1](http://mongoosejs.com/)
-* [Sequelize 2.0.6](https://github.com/sequelize/sequelize)
-* [Node MySQL 2.6.2](https://github.com/felixge/node-mysql/)
-* [Node MongoDB Driver 2.0.27](https://github.com/mongodb/node-mongodb-native)
-
-## Test URLs
-### JSON Encoding Test
-
-http://localhost:8080/json
-
-### Plaintext Test
-
-http://localhost:8080/plaintext
 
-### Data-Store/Database Mapping Test
+* [Node MySQL 2.7.0](https://github.com/felixge/node-mysql/)
+* [Sequelize 3.1.1](https://github.com/sequelize/sequelize)
+* [Node MongoDB Driver 2.0.33](https://github.com/mongodb/node-mongodb-native)
+* [Mongoose 4.0.4](http://mongoosejs.com/)
+* [Node Redis 0.12.1](https://github.com/mranney/node_redis)
+* [Hiredis 0.4.0 (C lib for Redis)](https://github.com/redis/hiredis)
 
-MongoDB:
-http://localhost:8080/mongoose
-
-MongoDB Raw:
-http://localhost:8080/mongodb
-
-MySQL:
-http://localhost:8080/mysql-orm
-
-MySQL Raw:
-http://localhost:8080/mysql
-
-### Variable Query Test
-
-MongoDB:
-http://localhost:8080/mongoose?queries=2
+## Test URLs
 
-MongoDB Raw:
-http://localhost:8080/mongodb?queries=2
+See the [Benchmark config](benchmark_config.json) file for a list of the tested routes.
 
-MySQL:
-http://localhost:8080/mysql-orm?queries=2
+`/json` and `/plaintext` are implemented
 
-MySQL Raw:
-http://localhost:8080/mysql?queries=2
+The four db-required tests, Single Query, Multiple Query, Fortunes, and Updates have been implemented for each of the 5 database approaches that this test covers

+ 19 - 0
frameworks/JavaScript/nodejs/app.js

@@ -0,0 +1,19 @@
+var cluster = require('cluster');
+var numCPUs = require('os').cpus().length;
+
+if (cluster.isMaster) {
+  // Fork workers.
+  for (var i = 0; i < numCPUs; i++) {
+    cluster.fork();
+  }
+
+  cluster.on('exit', function (worker, code, signal) {
+  	console.log([
+  	  'A process exit was triggered, most likely due to a failed database action',
+  	  'NodeJS test server shutting down now'].join('\n'));
+    process.exit(1);
+  });
+} else {
+  // Task for forked worker
+  require('./create-server');
+}

+ 37 - 12
frameworks/JavaScript/nodejs/benchmark_config.json

@@ -22,9 +22,10 @@
     },
     "mongodb": {
       "setup_file": "setup",
-      "db_url": "/mongoose",
-      "query_url": "/mongoose?queries=",
-      "update_url": "/mongoose-update?queries=",
+      "db_url": "/mongoose/db",
+      "query_url": "/mongoose/queries?queries=",
+      "update_url": "/mongoose/updates?queries=",
+      "fortune_url": "/mongoose/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
@@ -42,9 +43,10 @@
     },
     "mongodb-raw": {
       "setup_file": "setup",
-      "db_url": "/mongodb",
-      "query_url": "/mongodb?queries=",
-      "update_url": "/mongodb-update?queries=",
+      "db_url": "/mongodb/db",
+      "query_url": "/mongodb/queries?queries=",
+      "update_url": "/mongodb/updates?queries=",
+      "fortune_url": "/mongodb/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
@@ -62,9 +64,10 @@
     },
     "mysql": {
       "setup_file": "setup",
-      "db_url": "/mysql-orm",
-      "query_url": "/mysql-orm?queries=",
-      "update_url": "/mysql-orm-update?queries=",
+      "db_url": "/sequelize/db",
+      "query_url": "/sequelize/queries?queries=",
+      "update_url": "/sequelize/updates?queries=",
+      "fortune_url": "/sequelize/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
@@ -82,9 +85,10 @@
     },
     "mysql-raw": {
       "setup_file": "setup",
-      "db_url": "/mysql",
-      "query_url": "/mysql?queries=",
-      "update_url": "/mysql-update?queries=",
+      "db_url": "/mysql/db",
+      "query_url": "/mysql/queries?queries=",
+      "update_url": "/mysql/updates?queries=",
+      "fortune_url": "/mysql/fortunes",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Platform",
@@ -99,6 +103,27 @@
       "display_name": "nodejs",
       "notes": "",
       "versus": "nodejs"
+    },
+    "redis": {
+      "setup_file": "setup",
+      "db_url": "/hiredis/db",
+      "query_url": "/hiredis/queries?queries=",
+      "update_url": "/hiredis/updates?queries=",
+      "fortune_url": "/hiredis/fortunes",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Redis",
+      "framework": "nodejs",
+      "language": "JavaScript",
+      "orm": "full",
+      "platform": "nodejs",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "nodejs",
+      "notes": "",
+      "versus": "nodejs"
     }
   }]
 }

+ 35 - 0
frameworks/JavaScript/nodejs/create-server.js

@@ -0,0 +1,35 @@
+// Forked workers will run this code when found to not be
+// the master of the cluster.
+
+var http = require('http');
+var parseurl = require('parseurl'); // faster than native nodejs url package
+
+// Initialize routes & their handlers (once)
+var routing = require('./routing')
+var basicHandler = routing.BasicHandler;
+var queryHandler = routing.QueryHandler;
+var routeNotImplemented = require('./helper').responses.routeNotImplemented;
+
+module.exports = http.createServer(function (req, res) {
+  var url = parseurl(req);
+  var route = url.pathname;
+
+  // Routes that do no require a `queries` parameter
+  if (basicHandler.has(route)) {
+    return basicHandler.handle(route, req, res);
+  } else {
+    // naive: only works if there is one query param, as is the case in TFB
+    var queries = url.query && url.query.split('=')[1];
+    queries = ~~(queries) || 1;
+    queries = Math.min(Math.max(queries, 1), 500);
+
+    if (queryHandler.has(route)) {
+      return queryHandler.handle(route, queries, req, res);
+    } else {
+      return routeNotImplemented(req, res);
+    }
+  }
+
+}).listen(8080, function() {
+  console.log("NodeJS worker listening on port 8080");
+});

+ 91 - 0
frameworks/JavaScript/nodejs/handlers/mongodb-raw.js

@@ -0,0 +1,91 @@
+var h = require('../helper');
+var async = require('async');
+var MongoClient = require('mongodb').MongoClient;
+var collections = {
+  World: null,
+  Fortune: null
+};
+MongoClient.connect('mongodb://127.0.0.1/hello_world?maxPoolSize=5', function (err, db) {
+  // do nothing if there is err connecting to db
+
+  collections.World = db.collection('world');
+  collections.Fortune = db.collection('fortune');
+});
+
+
+function mongodbRandomWorld(callback) {
+  collections.World.findOne({
+    id: h.randomTfbNumber()
+  }, function (err, world) {
+    world._id = undefined; // remove _id from query response
+    callback(err, world);
+  });
+}
+
+function mongodbGetAllFortunes(callback) {
+  collections.Fortune.find().toArray(function (err, fortunes) {
+    callback(err, fortunes);
+  })
+}
+
+function mongodbDriverUpdateQuery(callback) {
+  collections.World.findAndModify({
+    id: h.randomTfbNumber()
+  }, [['_id','asc']], {
+    $set: {randomNumber: h.randomTfbNumber()}
+  }, {}, function (err, world) {
+    world.value._id = undefined; // remove _id from query response
+    callback(err, world.value);
+  });
+}
+
+
+module.exports = {
+
+  SingleQuery: function (req, res) {
+    mongodbRandomWorld(function (err, result) {
+      if (err) { return process.exit(1) }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(result));
+    });
+  },
+
+  MultipleQueries: function (queries, req, res) {
+    var queryFunctions = h.fillArray(mongodbRandomWorld, queries);
+
+    async.parallel(queryFunctions, function (err, results) {
+      if (err) { return process.exit(1) }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(results));
+    });
+  },
+
+  Fortunes: function (req, res) {
+    mongodbGetAllFortunes(function (err, fortunes) {
+      if (err) { return process.exit(1) }
+
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+      h.addTfbHeaders(res, 'html');
+      res.end(h.fortunesTemplate({
+        fortunes: fortunes
+      }));
+    });
+  },
+
+  Updates: function (queries, req, res) {
+    var queryFunctions = h.fillArray(mongodbDriverUpdateQuery, queries);
+
+    async.parallel(queryFunctions, function (err, results) {
+      if (err) { return process.exit(1) }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(results));
+    });
+  }
+
+};

+ 103 - 0
frameworks/JavaScript/nodejs/handlers/mongoose.js

@@ -0,0 +1,103 @@
+var h = require('../helper');
+var async = require('async');
+var Mongoose = require('mongoose');
+var connection = Mongoose.connect('mongodb://127.0.0.1/hello_world')
+
+// Mongoose Setup
+var WorldSchema = new Mongoose.Schema({
+    id          : Number,
+    randomNumber: Number
+  }, {
+    collection: 'world'
+  });
+var FortuneSchema = new Mongoose.Schema({
+    id:      Number,
+    message: String
+  }, {
+    collection: 'fortune'
+  });
+
+var Worlds = connection.model('World', WorldSchema);
+var Fortunes = connection.model('Fortune', FortuneSchema);
+
+function mongooseRandomWorld(callback) {
+  Worlds.findOne({
+    id: h.randomTfbNumber()
+  }).exec(callback);
+}
+
+function mongooseGetAllFortunes(callback) {
+  Fortunes.find({})
+    .exec(callback);
+}
+
+module.exports = {
+
+  SingleQuery: function (req, res) {
+    mongooseRandomWorld(function (err, result) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(result));
+    })
+  },
+
+  MultipleQueries: function (queries, req, res) {
+    var queryFunctions = h.fillArray(mongooseRandomWorld, queries)
+
+    async.parallel(queryFunctions, function (err, results) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(results));
+    });
+  },
+
+  Fortunes: function (req, res) {
+    mongooseGetAllFortunes(function (err, fortunes) {
+      if (err) { return process.exit(1); }
+
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+      h.addTfbHeaders(res, 'html');
+      res.end(h.fortunesTemplate({
+        fortunes: fortunes
+      }))
+    });
+  },
+
+  Updates: function (queries, req, res) {
+    var selectFunctions = h.fillArray(mongooseRandomWorld, queries);
+
+    async.parallel(selectFunctions, function (err, worlds) {
+      if (err) { return process.exit(1); }
+
+      var updateFunctions = [];
+
+      for (var i = 0; i < queries; i++) {
+        (function (i) {
+          updateFunctions.push(function (callback) {
+            worlds[i].randomNumber = h.randomTfbNumber();
+            Worlds.update({
+              id: worlds[i].id
+            }, {
+              randomNumber: worlds[i].randomNumber
+            }, callback);
+          });
+        })(i);
+      }
+
+      async.parallel(updateFunctions, function (err, results) {
+        if (err) { return process.exit(1); }
+
+        h.addTfbHeaders(res, 'json');
+        // results does not have updated document information
+        // if no err: all updates were successful
+        res.end(JSON.stringify(worlds));
+      });
+    });
+  }
+
+};

+ 97 - 0
frameworks/JavaScript/nodejs/handlers/mysql-raw.js

@@ -0,0 +1,97 @@
+var h = require('../helper');
+var async = require('async');
+var mysql = require('mysql');
+var connection = mysql.createConnection({
+  host     : '127.0.0.1',
+  user     : 'benchmarkdbuser',
+  password : 'benchmarkdbpass',
+  database : 'hello_world'
+});
+
+connection.connect();
+
+var queries = {
+  RANDOM_WORLD: "SELECT * FROM world WHERE id = " + h.randomTfbNumber(),
+  ALL_FORTUNES: "SELECT * FROM fortune",
+  UPDATE_WORLD: function (rows) {
+    return [
+      "UPDATE world SET randomNumber = ", rows[0].randomNumber,
+      " WHERE id = ", rows[0]['id']
+    ].join('');
+  }
+}
+
+function mysqlRandomWorld(callback) {
+  connection.query(queries.RANDOM_WORLD, function (err, rows, fields) {
+    callback(err, rows[0]);
+  });
+}
+
+function mysqlGetAllFortunes(callback) {
+  connection.query(queries.ALL_FORTUNES, function (err, rows, fields) {
+    callback(err, rows);
+  })
+}
+
+function mysqlUpdateQuery(callback) {
+  connection.query(queries.RANDOM_WORLD, function (err, rows, fields) {
+    if (err) { return process.exit(1); }
+
+    rows[0].randomNumber = h.randomTfbNumber();
+    var updateQuery = queries.UPDATE_WORLD(rows);
+
+    connection.query(updateQuery, function (err, result) {
+      callback(err, rows[0]);
+    });
+  });
+}
+
+module.exports = {
+
+  SingleQuery: function (req, res) {
+    mysqlRandomWorld(function (err, result) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(result));
+    });
+  },
+
+  MultipleQueries: function (queries, req, res) {
+    var queryFunctions = h.fillArray(mysqlRandomWorld, queries);
+
+    async.parallel(queryFunctions, function (err, results) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(results));
+    });
+  },
+
+  Fortunes: function (req, res) {
+    mysqlGetAllFortunes(function (err, fortunes) {
+      if (err) { return process.exit(1); }
+
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      })
+      h.addTfbHeaders(res, 'html');
+      res.end(h.fortunesTemplate({
+        fortunes: fortunes
+      }));
+    });
+  },
+
+  Updates: function (queries, req, res) {
+    var queryFunctions = h.fillArray(mysqlUpdateQuery, queries);
+
+    async.parallel(queryFunctions, function (err, results) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(results));
+    });
+  } 
+
+}

+ 114 - 0
frameworks/JavaScript/nodejs/handlers/redis.js

@@ -0,0 +1,114 @@
+var h = require('../helper');
+var async = require('async');
+// "If hiredis [pure C library] is installed, node_redis will use it by default.
+// Otherwise, a pure JavaScript parser will be used."
+// >> hiredis is installed for these tests
+var redis = require('redis');
+var client = redis.createClient();
+
+client.on('error', function (err) {
+  console.log('Redis Error: ' + err);
+  // Do nothing further if Redis errors/is unavailable
+});
+
+function redisWorldId(id) {
+  return 'world:' + id;
+}
+
+function redisRandomWorld(callback) {
+  var id = h.randomTfbNumber();
+  var redisId = redisWorldId(id);
+  client.get(redisId, function (err, worldValue) {
+    var world = {
+      id: id,
+      randomNumber: worldValue
+    }
+    callback(err, world);
+  });
+}
+
+function redisSetWorld(world, callback) {
+  var redisId = redisWorldId(world.id);
+  client.set(redisId, world.randomNumber, function (err, result) {
+    callback(err, world);
+  });
+}
+
+function redisGetAllFortunes(callback) {
+  client.lrange('fortunes', 0, -1, function (err, fortuneMessages) {
+    if (err) { return process.exit(1); }
+
+    var fortunes = fortuneMessages.map(function (e, i) {
+      return { id: i + 1, message: e }
+    });
+
+    callback(err, fortunes)
+  });
+}
+
+
+module.exports = {
+  
+  SingleQuery: function(req, res) {
+    redisRandomWorld(function (err, world) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(world));
+    })
+  },
+
+  MultipleQueries: function(queries, req, res) {
+    var queryFunctions = h.fillArray(redisRandomWorld, queries);
+
+    async.parallel(queryFunctions, function (err, worlds) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(worlds));
+    })
+  },
+
+  Fortunes: function(req, res) {
+    redisGetAllFortunes(function (err, fortunes) {
+      if (err) { return process.exit(1); }
+
+      h.addTfbHeaders(res, 'html');
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+      res.end(h.fortunesTemplate({
+        fortunes: fortunes
+      }));
+    });
+  },
+
+  Updates: function(queries, req, res) {
+    var getFunctions = h.fillArray(redisRandomWorld, queries);
+
+    async.parallel(getFunctions, function (err, worlds) {
+      if (err) { return process.exit(1); }
+
+      var updateFunctions = [];
+
+      worlds.forEach(function (w) {
+        w.id = h.randomTfbNumber();
+        updateFunctions.push(function (callback) {
+          if (err) { return process.exit(1); }
+
+          return redisSetWorld(w, callback);
+        });
+      });
+
+      async.parallel(updateFunctions, function (err, updated) {
+        if (err) { return process.exit(1); }
+
+        h.addTfbHeaders(res, 'json');
+        res.end(JSON.stringify(updated));
+      });
+    });
+
+  }
+
+};

+ 110 - 0
frameworks/JavaScript/nodejs/handlers/sequelize.js

@@ -0,0 +1,110 @@
+var h = require('../helper');
+var Promise = require('bluebird');
+
+var Sequelize = require('sequelize');
+var sequelize = new Sequelize('hello_world', 'benchmarkdbuser', 'benchmarkdbpass', {
+  host: '127.0.0.1',
+  dialect: 'mysql',
+  logging: false
+});
+
+var Worlds = sequelize.define('World', {
+  id:           { type: 'Sequelize.INTEGER' },
+  randomNumber: { type: 'Sequelize.INTEGER' }
+}, {
+  timestamps: false,
+  freezeTableName: true
+});
+
+var Fortunes = sequelize.define('Fortune', {
+  id:      { type: 'Sequelize.INTEGER' },
+  message: { type: 'Sequelize.STRING' }
+}, {
+  timestamps: false,
+  freezeTableName: true
+});
+
+var randomWorldPromise = function() {
+  return Worlds.findOne({
+    where: { id: h.randomTfbNumber() }
+  }).then(function (results) {
+    return results;
+  }).catch(function (err) {
+    process.exit(1);
+  });
+}
+
+module.exports = {
+
+  SingleQuery: function (req, res) {
+    randomWorldPromise().then(function (world) {
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(world));
+    });
+  },
+
+  MultipleQueries: function (queries, req, res) {
+    var worldPromises = [];
+
+    for (var i = 0; i < queries; i++) {
+      worldPromises.push(randomWorldPromise());
+    } 
+
+    Promise.all(worldPromises).then(function (worlds) {
+      h.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(worlds));
+    });
+  },
+
+  Fortunes: function (req, res) {
+    Fortunes.findAll().then(function (fortunes) {
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+
+      h.addTfbHeaders(res, 'html');
+      res.end(h.fortunesTemplate({
+        fortunes: fortunes
+      }));
+    }).catch(function (err) {
+      console.log(err.stack);
+      process.exit(1);
+    });
+  },
+
+  Updates: function (queries, req, res) {
+    var worldPromises = [];
+
+    for (var i = 0; i < queries; i++) {
+      worldPromises.push(randomWorldPromise());
+    }
+
+    var worldUpdate = function(world) {
+      world.randomNumber = h.randomTfbNumber();
+
+      return Worlds.update({
+        randomNumber: world.randomNumber
+      },
+      {
+        where: { id: world.id }
+      }).then(function (results) {
+        return world;
+      }).catch(function (err) {
+        process.exit(1);
+      });
+    }
+
+    Promise.all(worldPromises).then(function (worlds) {
+      var updates = worlds.map(function (e) {
+        return worldUpdate(e);
+      });
+
+      Promise.all(updates).then(function (updated) {
+        h.addTfbHeaders(res, 'json');
+        res.end(JSON.stringify(updated));
+      });
+    });
+  }
+
+}

+ 0 - 351
frameworks/JavaScript/nodejs/hello.js

@@ -1,351 +0,0 @@
-/**
- * Module dependencies.
- */
-
-var cluster = require('cluster')
-  , numCPUs = require('os').cpus().length
-  , http = require('http')
-  , url = require('url')
-  , Sequelize = require('sequelize')
-  , mysql = require('mysql')
-  , async = require('async')
-  , mongoose = require('mongoose')
-  , conn = mongoose.connect('mongodb://localhost/hello_world')
-  , MongoClient = require('mongodb').MongoClient;
-
-// MongoDB Raw Setup
-var collection = null;
-MongoClient.connect('mongodb://localhost/hello_world?maxPoolSize=5', function(err, db) {
-  collection = db.collection('world');
-});
-
-// MySQL Raw Setup
-var connection = mysql.createConnection({
-  host     : 'localhost',
-  user     : 'benchmarkdbuser',
-  password : 'benchmarkdbpass',
-  database : 'hello_world'
-});
-connection.connect();
-
-// Mongoose Setup
-var WorldSchema = new mongoose.Schema({
-    id          : Number,
-    randomNumber: Number
-  }, {
-    collection: 'world'
-  }),
-  MWorld = conn.model('World', WorldSchema);
-
-// Sequelize Setup
-var sequelize = new Sequelize('hello_world', 'benchmarkdbuser', 'benchmarkdbpass', {
-  host: 'localhost',
-  dialect: 'mysql',
-  logging: false
-});
-
-var World = sequelize.define('World', {
-  id: {
-    type: 'Sequelize.INTEGER'
-  },
-  randomNumber: {
-    type: 'Sequelize.INTEGER'
-  }
-}, {
-  timestamps: false,
-  freezeTableName: true
-});
-var Fortune = sequelize.define('Fortune', {
-  id: {
-    type: 'Sequelize.INTEGER'
-  },
-  message: {
-    type: 'Sequelize.STRING'
-  }
-}, {
-  timestamps: false,
-  freezeTableName: true
-});
-
-// Helper functions
-function getRandomNumber() {
-  return Math.floor(Math.random() * 10000) + 1;
-}
-
-// Mongoose Query Functions
-function mongooseQuery(callback) {
-  MWorld.findOne({
-    id: getRandomNumber()
-  }).exec(function (err, world) {
-    callback(err, world);
-  });
-}
-
-// MongoDB-Raw Query Functions
-function mongodbDriverQuery(callback) {
-  collection.findOne({
-    id: getRandomNumber()
-  }, function(err, world) {
-    world._id = undefined; // remove _id from query response
-    callback(err, world);
-  });
-}
-
-function mongodbDriverUpdateQuery(callback) {
-  collection.findAndModify({
-    id: getRandomNumber()
-  }, [['_id','asc']], {
-    $set: {randomNumber: getRandomNumber()}
-  }, {}, function(err, world) {
-    world.value._id = undefined; // remove _id from query response
-    callback(err, world.value);
-  });
-}
-
-// Sequelize Query Functions
-function sequelizeQuery(callback) {
-  World.findOne({
-    where: {
-      id: Math.floor(Math.random() * 10000) + 1}
-    }
-  ).complete(callback);
-}
-
-
-// MySQL-Raw Query Functions
-function mysqlQuery(callback) {
-  connection.query("SELECT * FROM world WHERE id = " + getRandomNumber(), function (err, rows, fields) {
-    if (err) {
-      throw err;
-    }
-    callback(null, rows[0]);
-  });
-}
-
-function mysqlUpdateQuery(callback) {
-  connection.query("SELECT * FROM world WHERE id = " + getRandomNumber(), function (err, rows, fields) {
-    if (err) {
-      throw err;
-    }
-    rows[0].randomNumber = getRandomNumber();
-    var updateQuery = "UPDATE world SET randomNumber = " + rows[0].randomNumber + " WHERE id = " + rows[0]['id'];
-    connection.query(updateQuery, function (err, result) {
-      if (err) {
-        throw err;
-      }
-      callback(null, rows[0]);
-    });
-  });
-} 
-
-if(cluster.isMaster) {
-  // Fork workers.
-  for (var i = 0; i < numCPUs; i++) {
-    cluster.fork();
-  }
-
-  cluster.on('exit', function(worker, code, signal) {
-    console.log('worker ' + worker.pid + ' died');
-  });
-
-  return;
-} else {
-  http.createServer(function (req, res) {
-    // JSON response object
-    var hello = {message: "Hello, World!"};
-    var helloStr = "Hello, World!";
-    var path = url.parse(req.url).pathname;
-
-    switch (req.url) {
-      case '/json':
-        res.writeHead(200, {
-          'Content-Type': 'application/json',
-          'Server': 'Node'
-        });
-        res.end(JSON.stringify(hello));
-        break;
-
-      case '/plaintext':
-        res.writeHead(200, {
-          'Content-Type': 'text/plain; charset=UTF-8',
-          'Server': 'Node'
-        });
-        res.end(helloStr);
-        break;
-    }
-    var values = url.parse(req.url, true);
-    var queries = isNaN(values.query.queries) ? 1 : parseInt(values.query.queries, 10);
-    queries = Math.min(Math.max(queries, 1), 500);
-    switch (values.pathname) {
-      // Raw MongoDB Routes
-      case '/mongodb':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(mongodbDriverQuery);
-        }
-
-        async.parallel(queryFunctions, function(err, results) {
-          if (!values.query.queries) {
-            results = results[0];
-          }
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      case '/mongodb-update':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(mongodbDriverUpdateQuery);
-        }
-
-        async.parallel(queryFunctions, function(err, results) {
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      // Mongoose ORM Routes
-      case '/mongoose':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(mongooseQuery);
-        }
-
-        async.parallel(queryFunctions, function(err, results) {
-          if (!values.query.queries) {
-            results = results[0];
-          }
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      case '/mongoose-update':
-        var selectFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          selectFunctions.push(mongooseQuery);
-        }
-
-        async.parallel(selectFunctions, function(err, worlds) {
-          var updateFunctions = [];
-
-          for (var i = 0; i < queries; i++) {
-            (function(i){
-              updateFunctions.push(function(callback){
-                worlds[i].randomNumber = Math.ceil(Math.random() * 10000);
-                MWorld.update({
-                  id: worlds[i]
-                }, {
-                  randomNumber: worlds[i].randomNumber
-                }, callback);
-              });
-            })(i);
-          }
-
-          async.parallel(updateFunctions, function(err, updates) {
-            res.writeHead(200, {
-              'Content-Type': 'application/json',
-              'Server': 'Node'
-            });
-            res.end(JSON.stringify(worlds));
-          });
-        });
-        break;
-
-      // Sequelize (MySQL ORM) Routes
-      case '/mysql-orm':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(sequelizeQuery);
-        }
-
-        async.parallel(queryFunctions, function(err, results) {
-          if (!values.query.queries) {
-            results = results[0];
-          }
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      case '/mysql-orm-update':
-        var selectFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          selectFunctions.push(sequelizeQuery);
-        }
-
-        async.parallel(selectFunctions, function(err, worlds) {
-          var updateFunctions = [];
-
-          for (var i = 0; i < queries; i++) {
-            (function(i){
-              updateFunctions.push(function(callback){
-                worlds[i].randomNumber = Math.ceil(Math.random() * 10000);
-                worlds[i].save().complete(callback);
-              });
-            })(i);
-          }
-
-          async.parallel(updateFunctions, function(err, updates) {
-            res.writeHead(200, {
-              'Content-Type': 'application/json',
-              'Server': 'Node'
-            });
-            res.end(JSON.stringify(worlds));
-          });
-        });
-        break;
-
-      // Raw MongoDB Routes
-      case '/mysql':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(mysqlQuery);
-        }
-
-        async.parallel(queryFunctions, function(err, results) {
-          if (!values.query.queries) {
-            results = results[0];
-          }
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      case '/mysql-update':
-        var queryFunctions = [];
-        for (var i = 0; i < queries; i += 1) {
-          queryFunctions.push(mysqlUpdateQuery);
-        }
-        async.parallel(queryFunctions, function(err, results) {
-          res.writeHead(200, {
-            'Content-Type': 'application/json',
-            'Server': 'Node'
-          });
-          res.end(JSON.stringify(results));
-        });
-        break;
-
-      default:
-        // File not found handler
-        res.writeHead(501, {'Content-Type': 'text/plain; charset=UTF-8'});
-        res.end("NOT IMPLEMENTED");
-      }
-    }).listen(8080);
-}

+ 82 - 0
frameworks/JavaScript/nodejs/helper.js

@@ -0,0 +1,82 @@
+var Handlebars = require('handlebars');
+
+var GREETING = "Hello, World!";
+var HELLO_OBJ = { message: GREETING };
+
+var self = module.exports = {
+
+  ADDITIONAL_FORTUNE: {
+    id: 0,
+    message: 'Additional fortune added at request time.'
+  },
+
+  fortunesTemplate: Handlebars.compile([
+    "<!DOCTYPE html>",
+    "<html>",
+    "<head><title>Fortunes</title></head>",
+    "<body>",
+    "<table>",
+      "<tr>",
+        "<th>id</th>",
+        "<th>message</th>",
+      "</tr>",
+      "{{#fortunes}}",
+      "<tr>",
+        "<td>{{id}}</td>",
+        "<td>{{message}}</td>",
+      "</tr>",
+      "{{/fortunes}}",
+    "</table>",
+    "</body>",
+    "</html>"
+  ].join('')),
+
+  randomTfbNumber: function () {
+    return Math.floor(Math.random() * 10000) + 1;
+  },
+
+  fillArray: function(value, len) {
+    var arr = [];
+    for (var i = 0; i < len; i++) {
+      arr.push(value);
+    }
+    return arr;
+  },
+
+  addTfbHeaders: function (res, headerType) {
+    var headers = {
+      'Server': 'Node'
+    }
+
+    if (headerType === 'plain') {
+      headers['Content-Type'] = 'text/plain; charset=UTF-8';
+    } else if (headerType === 'json') {
+      headers['Content-Type'] = 'application/json';
+    } else if (headerType === 'html') {
+      headers['Content-Type'] = 'text/html; cherset=UTF-8';
+    }
+
+    res.writeHead(200, headers);
+  },
+
+  responses: {
+
+    jsonSerialization: function (req, res) {
+      self.addTfbHeaders(res, 'json');
+      res.end(JSON.stringify(HELLO_OBJ));
+    },
+
+    plaintext: function (req, res) {
+      self.addTfbHeaders(res, 'plain');
+      res.end(GREETING);
+    },
+
+    routeNotImplemented: function (req, res) {
+      res.writeHead(501, {'Content-Type': 'text/plain; charset=UTF-8'});
+      var reason = { reason: "`" + req.url + "` is not an implemented route" };
+      res.end(JSON.stringify(reason));
+    }
+
+  }
+
+};

+ 16 - 11
frameworks/JavaScript/nodejs/package.json

@@ -1,13 +1,18 @@
 {
-    "name": "application-name"
-  , "version": "0.0.1"
-  , "private": true
-  , "dependencies": {
-      "mongoose": "4.0.1" 
-    , "async": "0.9.0"
-    , "mongodb": "2.0.27"
-    , "sequelize": "2.0.6"
-    , "mysql": "2.6.2"
-  }
-  , "main": "hello.js"
+  "name": "application-name",
+  "version": "0.0.1",
+  "private": true,
+  "dependencies": {
+    "async": "1.2.0",
+    "bluebird": "^2.9.27",
+    "handlebars": "^3.0.3",
+    "hiredis": "^0.4.0",
+    "mongodb": "2.0.33",
+    "mongoose": "4.0.4",
+    "mysql": "2.7.0",
+    "parseurl": "^1.3.0",
+    "redis": "^0.12.1",
+    "sequelize": "3.1.1"
+  },
+  "main": "hello.js"
 }

+ 77 - 0
frameworks/JavaScript/nodejs/routing.js

@@ -0,0 +1,77 @@
+// Intialized database connections, one for each db config
+// * Mongoose is a popular Node/MongoDB driver
+// * Sequelize is a popular Node/SQL driver
+// * Node's redis package uses the C bindings of the hiredis library
+var MongodbRawHandler = require('./handlers/mongodb-raw');
+var MySQLRawHandler = require('./handlers/mysql-raw');
+var MongooseHandler = require('./handlers/mongoose');
+var SequelizeHandler = require('./handlers/sequelize');
+var HiredisHandler = require('./handlers/redis');
+
+var h = require('./helper');
+
+module.exports.BasicHandler = (function() {
+  var self = {}
+
+  self.routes = {
+    '/json':               h.responses.jsonSerialization,
+    '/plaintext':          h.responses.plaintext,
+
+    '/mongoose/db':        MongooseHandler.SingleQuery,
+    '/mongoose/fortunes':  MongooseHandler.Fortunes,
+
+    '/mongodb/db':         MongodbRawHandler.SingleQuery,
+    '/mongodb/fortunes':   MongodbRawHandler.Fortunes,
+
+    '/sequelize/db':       SequelizeHandler.SingleQuery,
+    '/sequelize/fortunes': SequelizeHandler.Fortunes,
+
+    '/mysql/db':           MySQLRawHandler.SingleQuery,
+    '/mysql/fortunes':     MySQLRawHandler.Fortunes,
+
+    '/hiredis/db':         HiredisHandler.SingleQuery,
+    '/hiredis/fortunes':   HiredisHandler.Fortunes
+  }
+
+  self.has = function(path) {
+    return self.routes[path];
+  }
+
+  self.handle = function(path, req, res) {
+    return self.routes[path](req, res);
+  }
+
+  return self;
+}());
+
+module.exports.QueryHandler = (function () {
+  var self = {}
+
+  self.routes = {
+    '/mongoose/queries':  MongooseHandler.MultipleQueries,
+    '/mongoose/updates':  MongooseHandler.Updates,
+
+    '/mongodb/queries':   MongodbRawHandler.MultipleQueries,
+    '/mongodb/updates':   MongodbRawHandler.Updates,
+
+    '/sequelize/queries': SequelizeHandler.MultipleQueries,
+    '/sequelize/updates': SequelizeHandler.Updates,
+
+    '/mysql/queries':     MySQLRawHandler.MultipleQueries,
+    '/mysql/updates':     MySQLRawHandler.Updates,
+
+    '/hiredis/queries':   HiredisHandler.MultipleQueries,
+    '/hiredis/updates':   HiredisHandler.Updates
+  }
+
+  self.has = function(path) {
+    return self.routes[path];
+  }
+
+  self.handle = function(path, queries, req, res) {
+    return self.routes[path](queries, req, res);
+  }
+
+  return self;
+}());
+

+ 3 - 3
frameworks/JavaScript/nodejs/setup.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
-sed -i 's|localhost|'"${DBHOST}"'|g' hello.js
-sed -i 's|mongodb://.*/hello_world|mongodb://'"${DBHOST}"'/hello_world|g' hello.js
+sed -i 's|localhost|'"${DBHOST}"'|g' app.js
+sed -i 's|mongodb://.*/hello_world|mongodb://'"${DBHOST}"'/hello_world|g' app.js
 
 export NODE_ENV=production
 export NVM_HOME=${IROOT}/nvm
@@ -14,4 +14,4 @@ nvm use 0.12.2
 npm install -g npm
 
 npm install
-node hello.js &
+node app.js &