Browse Source

Merge pull request #1649 from zane-techempower/improve-hapi

Improve hapi framework
Mike Smith 10 years ago
parent
commit
9c102775cf

+ 13 - 232
frameworks/JavaScript/hapi/app.js

@@ -1,236 +1,17 @@
-/**
- * Module dependencies.
- */
-
-var cluster = require('cluster'),
-	numCPUs = require('os').cpus().length,
-	Hapi = require('hapi'),
-	Sequelize = require('sequelize'),
-	mongoose = require('mongoose'),
-	conn = mongoose.connect('mongodb://localhost/hello_world'),
-	async = require('async');
-
-var WorldSchema = new mongoose.Schema({
-		id          : Number,
-		randomNumber: Number
-	}, {
-		collection: 'world'
-	}),
-	MWorld = conn.model('World', WorldSchema);
-
-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
-});
+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('worker ' + worker.pid + ' died');
-	});
+  // Fork workers.
+  for (var i = 0; i < numCPUs; i++) {
+    cluster.fork();
+  }
+
+  console.log('Master starting ' + new Date().toISOString(" "));
+  cluster.on('exit', function (worker, code, signal) {
+    process.exit(1);
+  });
 } else {
-	var server = module.exports = new Hapi.Server();
-	server.connection({port: 8080});
-	server.views({
-		engines: {
-			html: require('handlebars')
-		},
-		path: __dirname + '/views'
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/json',
-		handler: function(req, reply) {
-			reply({ message: 'Hello, World!' }).header('Server', 'hapi');
-		}
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/plaintext',
-		handler: function(req, reply) {
-			reply('Hello, World!')
-			 .header('Server', 'hapi')
-			 .header('Content-Type', 'text/plain');
-		}
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/mongoose/{queries?}',
-		handler: function(req, reply){
-			var queries = isNaN(req.params.queries) ? 1 : parseInt(req.params.queries, 10),
-				queryFunctions = [];
-
-			queries = Math.min(Math.max(queries, 1), 500);
-
-			for (var i = 1; i <= queries; i++) {
-				queryFunctions.push(function(callback){
-					MWorld.findOne({ id: (Math.floor(Math.random() * 10000) + 1) }).exec(callback);
-				});
-			}
-
-			async.parallel(queryFunctions, function(err, results){
-				if (!req.params.queries) {
-					results = results[0];
-				}
-				reply(results).header('Server', 'hapi');
-			});
-		}
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/mysql-orm/{queries?}',
-		handler: function(req, reply){
-			var queries = isNaN(req.params.queries) ? 1 : parseInt(req.params.queries, 10),
-				queryFunctions = [];
-
-			queries = Math.min(Math.max(queries, 1), 500);
-
-			for (var i = 1; i <= queries; i++) {
-				queryFunctions.push(function(callback){
-					World.findOne({
-						where: {
-							id: Math.floor(Math.random() * 10000) + 1}
-						}
-					).complete(callback);
-				});
-			}
-
-			async.parallel(queryFunctions, function(err, results){
-				if (!req.params.queries) {
-					results = results[0];
-				}
-				reply(results).header('Server', 'hapi');
-			});
-		}
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/fortune',
-		handler: function(req,reply){
-			Fortune.findAll().complete(function(err, fortunes){
-				fortunes.push({
-					id: 0,
-					message: 'Additional fortune added at request time.'
-				});
-				fortunes.sort(function(a, b){
-					return (a.message < b.message) ? -1 : 1;
-				});
-
-				reply.view('fortunes', {
-					fortunes: fortunes
-				}).header('Server', 'hapi');
-			});
-		}
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/mongoose-update/{queries?}',
-		handler: function(req, reply){
-			var queries = isNaN(req.params.queries) ? 1 : parseInt(req.params.queries, 10),
-				selectFunctions = [];
-
-			queries = Math.max(Math.min(queries, 500), 1);
-
-			for (var i = 1; i <= queries; i++) {
-				selectFunctions.push(function(callback){
-					MWorld.findOne({ id: Math.floor(Math.random() * 10000) + 1 }).exec(callback);
-				});
-			}
-
-			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) {
-					reply(worlds).header('Server', 'hapi');
-				});
-			});
-		}		
-	});
-
-	server.route({
-		method: 'GET',
-		path: '/mysql-orm-update/{queries?}',
-		handler: function(req,reply){
-			var queries = isNaN(req.params.queries) ? 1 : parseInt(req.params.queries, 10),
-				selectFunctions = [];
-
-			queries = Math.max(Math.min(queries, 500), 1);
-
-			for (var i = 1; i <= queries; i++) {
-				selectFunctions.push(function(callback){
-					World.findOne({
-						where: {
-							id: Math.floor(Math.random() * 10000) + 1}
-						}
-					).complete(callback);
-				});
-			}
-
-			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) {
-					reply(worlds).header('Server', 'hapi');
-				});
-			});
-		}
-	});
-
-	server.start();
+  // worker task
+  require('./create-server');
 }

+ 52 - 9
frameworks/JavaScript/hapi/benchmark_config.json

@@ -22,16 +22,17 @@
     },
     "mongodb": {
       "setup_file": "setup",
-      "db_url": "/mongoose/",
-      "query_url": "/mongoose/",
-      "update_url": "/mongoose-update/",
+      "db_url": "/mongoose/db",
+      "query_url": "/mongoose/queries?queries=",
+      "fortune_url": "/mongoose/fortunes",
+      "update_url": "/mongoose/updates?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
       "database": "MongoDB",
       "framework": "hapi",
       "language": "JavaScript",
-      "orm": "Raw",
+      "orm": "Full",
       "platform": "nodejs",
       "webserver": "None",
       "os": "Linux",
@@ -42,17 +43,59 @@
     },
     "mysql": {
       "setup_file": "setup",
-      "db_url": "/mysql-orm/",
-      "query_url": "/mysql-orm/",
-      "fortune_url": "/fortune",
-      "update_url": "/mysql-orm-update/",
+      "db_url": "/sequelize/db",
+      "query_url": "/sequelize/queries?queries=",
+      "fortune_url": "/sequelize/fortunes",
+      "update_url": "/sequelize/updates?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
       "database": "MySQL",
       "framework": "hapi",
       "language": "JavaScript",
-      "orm": "Raw",
+      "orm": "Full",
+      "platform": "nodejs",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "hapi",
+      "notes": "",
+      "versus": "node"
+    },
+    "postgres": {
+      "setup_file": "setup",
+      "db_url": "/sequelize-pg/db",
+      "query_url": "/sequelize-pg/queries?queries=",
+      "fortune_url": "/sequelize-pg/fortunes",
+      "update_url": "/sequelize-pg/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "hapi",
+      "language": "JavaScript",
+      "orm": "Full",
+      "platform": "nodejs",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "hapi",
+      "notes": "",
+      "versus": "node"
+    },
+    "redis": {
+      "setup_file": "setup",
+      "db_url": "/hiredis/db",
+      "query_url": "/hiredis/queries?queries=",
+      "fortune_url": "/hiredis/fortunes",
+      "update_url": "/hiredis/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Redis",
+      "framework": "hapi",
+      "language": "JavaScript",
+      "orm": "Full",
       "platform": "nodejs",
       "webserver": "None",
       "os": "Linux",

+ 83 - 0
frameworks/JavaScript/hapi/create-server.js

@@ -0,0 +1,83 @@
+var Hapi = require('hapi');
+var server = new Hapi.Server();
+server.connection({port: 8080});
+server.views({
+  engines: {
+    hbs: require('handlebars')
+  },
+  path: __dirname + '/views',
+  compileOptions: {
+    pretty: false
+  }
+});
+
+var Promise = require('bluebird');
+var MongooseHandler;
+var SequelizeHandler;
+var SequelizePgHandler;
+var RedisHandler;
+
+// Slight start-up improvement loading handlers in parallel
+Promise.join(
+  require('./handlers/mongoose'),
+  require('./handlers/sequelize'),
+  require('./handlers/sequelize-postgres'),
+  require('./handlers/redis'),
+  function (mongo, mysql, pg, redis) {
+    MongooseHandler = mongo;
+    SequelizeHandler = mysql;
+    SequelizePgHandler = pg;
+    RedisHandler = redis;
+  })
+  .catch(function (err) {
+    console.log('There was a problem setting up the handlers');
+    process.exit(1);
+  });
+
+
+Route('/json', JsonSerialization);
+Route('/plaintext', Plaintext);
+
+Route('/mongoose/db', MongooseHandler.SingleQuery);
+Route('/mongoose/queries', MongooseHandler.MultipleQueries);
+Route('/mongoose/fortunes', MongooseHandler.Fortunes);
+Route('/mongoose/updates', MongooseHandler.Updates);
+
+Route('/sequelize/db', SequelizeHandler.SingleQuery);
+Route('/sequelize/queries', SequelizeHandler.MultipleQueries);
+Route('/sequelize/fortunes', SequelizeHandler.Fortunes);
+Route('/sequelize/updates', SequelizeHandler.Updates);
+
+Route('/sequelize-pg/db', SequelizePgHandler.SingleQuery);
+Route('/sequelize-pg/queries', SequelizePgHandler.MultipleQueries);
+Route('/sequelize-pg/fortunes', SequelizePgHandler.Fortunes);
+Route('/sequelize-pg/updates', SequelizePgHandler.Updates);
+
+Route('/hiredis/db', RedisHandler.SingleQuery);
+Route('/hiredis/queries', RedisHandler.MultipleQueries);
+Route('/hiredis/fortunes', RedisHandler.Fortunes);
+Route('/hiredis/updates', RedisHandler.Updates);
+
+
+function JsonSerialization(req, reply) {
+  reply({ message: 'Hello, World!' })
+    .header('Server', 'hapi');
+}
+
+function Plaintext(req, reply) {
+  reply('Hello, World!')
+    .header('Server', 'hapi')
+    .header('Content-Type', 'text/plain');
+}
+
+// Makes routing simpler as tfb routes are all GET's
+// We also don't use the nifty route features that Hapi has
+// to offer such as attaching a validator
+function Route(path, handler) {
+  server.route({ method: 'GET', path: path, handler: handler})
+}
+
+server.start(function (err) {
+  console.log('Hapi worker started and listening on ' + server.info.uri + " "
+    + new Date().toISOString(" "));
+});

+ 128 - 0
frameworks/JavaScript/hapi/handlers/mongoose.js

@@ -0,0 +1,128 @@
+// Connects to MongoDB using the mongoose driver
+// Handles related routes
+
+var h = require('../helper');
+var Promise = require('bluebird');
+// Can treat mongoose library as one that supports Promises
+// these methods will then have "-Async" appended to them.
+var Mongoose = Promise.promisifyAll(require('mongoose'));
+var connection = Mongoose.connect('mongodb://127.0.0.1/hello_world');
+
+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 randomWorldPromise() {
+  var id = h.randomTfbNumber();
+  var promise = Worlds
+    .findOneAsync({
+      id: id
+    })
+    .then(function (world) {
+      return world;
+    })
+    .catch(function (err) {
+      process.exit(1);
+    });
+  return promise;
+}
+
+function promiseAllFortunes() {
+  var promise = Fortunes
+    .findAsync({})
+    .then(function (fortunes) {
+      return fortunes;
+    })
+    .catch(function (err) {
+      process.exit(1);
+    });
+  return promise;
+}
+
+function updateWorld(world) {
+  var promise = Worlds
+    .updateAsync({
+      id: world.randomNumber
+    }, {
+      randomNumber: world.randomNumber
+    })
+    .then(function (result) {
+      return world;
+    })
+    .catch(function (err) {
+      process.exit(1);
+    });
+  return promise;
+}
+
+module.exports = {
+
+  SingleQuery: function(req, reply) {
+    randomWorldPromise()
+      .then(function (world) {
+        reply(world)
+          .header('Server', 'hapi');
+      });
+  },
+
+  MultipleQueries: function(req, reply) {
+    var queries = h.getQueries(req);
+    var worldPromises = h.fillArray(randomWorldPromise(), queries);
+
+    Promise
+      .all(worldPromises)
+      .then(function (worlds) {
+        reply(worlds)
+          .header('Server', 'hapi');
+      });
+  },
+
+  Fortunes: function(req, reply) {
+    promiseAllFortunes()
+      .then(function (fortunes) {
+        fortunes.push(h.ADDITIONAL_FORTUNE);
+        fortunes.sort(function (a, b) {
+          return a.message.localeCompare(b.message);
+        });
+      
+        reply.view('fortunes', {
+          fortunes: fortunes
+        })
+          .header('Content-Type', 'text/html')
+          .header('Server', 'hapi');
+      });
+  },
+
+  Updates: function(req, reply) {
+    var queries = h.getQueries(req);
+    var worldPromises = [];
+
+    for (var i = 0; i < queries; i++) {
+      worldPromises.push(randomWorldPromise());
+    }
+
+    Promise
+      .all(worldPromises)
+      .map(function (world) {
+        world.randomNumber = h.randomTfbNumber();
+        return updateWorld(world);
+      })
+      .then(function (worlds) {
+        reply(worlds)
+          .header('Server', 'hapi');
+      });
+  }
+
+};

+ 133 - 0
frameworks/JavaScript/hapi/handlers/redis.js

@@ -0,0 +1,133 @@
+// Connects to Redis using the node_redis and hiredis drivers
+// Handles related routes
+
+// "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 h = require('../helper');
+var Promise = require('bluebird');
+// Can treat redis library as one that supports Promises
+// these methods will then have "-Async" appended to them.
+var redis = Promise.promisifyAll(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 randomWorldPromise() {
+  var id = h.randomTfbNumber();
+  var redisId = redisWorldId(id);
+
+  var promise = client.getAsync(redisId)
+    .then(function (worldValue) {
+      return {
+        id: id,
+        randomNumber: worldValue
+      }
+    })
+    .catch(function (err) {
+      process.exit(1);
+    });
+  return promise;
+}
+
+function redisSetWorld(world) {
+  var redisId = redisWorldId(world.id);
+  var promise = client
+    .setAsync(redisId, world.randomNumber)
+    .then(function (result) {
+      return world;
+    })
+    .catch(function (err) {
+      process.exit(1);
+    });
+  return promise;
+}
+
+function redisGetAllFortunes() {
+  var promise = client
+    .lrangeAsync('fortunes', 0, -1)
+    .then(function (fortuneMessages) {
+      var fortunes = fortuneMessages.map(function (e, i) {
+        return { id: i + 1, message: e }
+      });
+      return fortunes;
+    })
+    .catch(function (err) {
+      if (err) { return process.exit(1); }
+    });
+  return promise;
+}
+
+
+module.exports = {
+  
+  SingleQuery: function(req, reply) {
+    randomWorldPromise()
+      .then(function (world) {
+        reply(world)
+          .header('Server', 'hapi');
+      })
+      .catch(function (err) {
+        if (err) { return process.exit(1); }
+      })
+  },
+
+  MultipleQueries: function(req, reply) {
+    var queries = h.getQueries(req);
+    var worldPromises = h.fillArray(randomWorldPromise(), queries);
+
+    Promise
+      .all(worldPromises)
+      .then(function (worlds) {
+         reply(worlds)
+          .header('Server', 'hapi');
+      });
+  },
+
+  Fortunes: function(req, reply) {
+    redisGetAllFortunes()
+      .then(function (fortunes) {
+        fortunes.push(h.ADDITIONAL_FORTUNE);
+        fortunes.sort(function (a, b) {
+          return a.message.localeCompare(b.message);
+        });
+
+        reply.view('fortunes', {
+          fortunes: fortunes
+        })
+          .header('Content-Type', 'text/html')
+          .header('Server', 'hapi');
+      })
+      .catch(function (err) {
+        process.exit(1);
+      })
+  },
+
+  Updates: function(req, reply) {
+    var queries = h.getQueries(req)
+    var worldPromises = h.fillArray(randomWorldPromise(), queries);
+
+    Promise
+      .all(worldPromises)
+      .map(function (world) {
+        world.randomNumber = h.randomTfbNumber();
+        return redisSetWorld(world);
+      })
+      .then(function (updated) {
+        reply(updated)
+          .header('Server', 'hapi');
+      })
+      .catch(function (err) {
+        process.exit(1);
+      });
+  }
+
+};

+ 117 - 0
frameworks/JavaScript/hapi/handlers/sequelize-postgres.js

@@ -0,0 +1,117 @@
+// Connects to Postgres using the sequelize driver
+// Handles related routes
+
+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: 'postgres',
+  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, reply) {
+    randomWorldPromise().then(function (world) {
+      reply(world)
+        .header('Server', 'hapi');
+    })
+  },
+
+  MultipleQueries: function (req, reply) {
+    var queries = h.getQueries(req);
+    var worldPromises = [];
+
+    for (var i = 0; i < queries; i++) {
+      worldPromises.push(randomWorldPromise());
+    }
+
+    Promise.all(worldPromises).then(function (worlds) {
+      reply(worlds)
+        .header('Server', 'hapi');
+    });
+  },
+
+  Fortunes: function (req, reply) {
+    Fortunes.findAll().then(function (fortunes) {
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+
+      reply.view('fortunes', {
+        fortunes: fortunes
+      })
+        .header('Content-Type', 'text/html')
+        .header('Server', 'hapi');
+    }).catch(function (err) {
+      process.exit(1);
+    }); 
+  },
+
+  Updates: function (req, reply) {
+    var queries = h.getQueries(req);
+    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)
+      .map(function (world) {
+        return worldUpdate(world);
+      })
+      .then(function (updated) {
+        reply(updated)
+          .header('Server', 'hapi')
+      })
+      .catch(function (err) {
+        process.exit(1);
+      });
+  }
+
+};

+ 116 - 0
frameworks/JavaScript/hapi/handlers/sequelize.js

@@ -0,0 +1,116 @@
+// Connects to MySQL using the sequelize driver
+// Handles related routes
+
+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, reply) {
+    randomWorldPromise().then(function (world) {
+      reply(world)
+        .header('Server', 'hapi');
+    })
+  },
+
+  MultipleQueries: function (req, reply) {
+    var queries = h.getQueries(req);
+    var worldPromises = [];
+
+    for (var i = 0; i < queries; i++) {
+      worldPromises.push(randomWorldPromise());
+    }
+
+    Promise.all(worldPromises).then(function (worlds) {
+      reply(worlds)
+        .header('Server', 'hapi');
+    });
+  },
+
+  Fortunes: function (req, reply) {
+    Fortunes.findAll().then(function (fortunes) {
+      fortunes.push(h.ADDITIONAL_FORTUNE);
+      fortunes.sort(function (a, b) {
+        return a.message.localeCompare(b.message);
+      });
+
+      reply.view('fortunes', {
+        fortunes: fortunes
+      })
+        .header('Content-Type', 'text/html')
+        .header('Server', 'hapi');
+    }).catch(function (err) {
+      process.exit(1);
+    }); 
+  },
+
+  Updates: function (req, reply) {
+    var queries = h.getQueries(req);
+    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)
+      .map(function (world) {
+        return worldUpdate(world);
+      })
+      .then(function (updated) {
+        reply(updated)
+          .header('Server', 'hapi')
+      })
+      .catch(function (e) {
+        process.exit(1);
+      });
+  }
+}

+ 31 - 0
frameworks/JavaScript/hapi/helper.js

@@ -0,0 +1,31 @@
+var Handlebars = require('handlebars');
+
+var GREETING = "Hello, World";
+var HELLO_OBJ = { message: GREETING }
+
+module.exports = {
+  randomTfbNumber: function() {
+    return Math.floor(Math.random() * 10000) + 1;
+  },
+
+  fillArray: function(value, len) {
+    var filled = [];
+
+    for (var i = 0; i < len; i++) {
+      filled.push(value);
+    }
+    return filled;
+  },
+
+  getQueries: function(req) {
+    var queries = ~~(req.query.queries) || 1;
+    queries = Math.min(Math.max(queries, 1), 500);
+    return queries;
+  },
+
+  ADDITIONAL_FORTUNE: {
+    id: 0,
+    message: 'Additional fortune added at request time.'
+  }
+
+}

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

@@ -1,13 +1,18 @@
 {
-	"name": "application-name",
-	"version": "0.0.1",
-	"private": true,
-	"dependencies": {
-		"hapi": "8.4.0",
-		"mongoose": "4.0.1",
-		"async": "0.9.0",
-		"handlebars": "3.0.1",
-		"sequelize": "2.0.6",
-		"mysql": "2.6.2"
-	}
+  "name": "application-name",
+  "version": "0.0.1",
+  "private": true,
+  "dependencies": {
+    "async": "1.2.0",
+    "bluebird": "^2.9.27",
+    "handlebars": "3.0.3",
+    "hapi": "8.6.0",
+    "hiredis": "^0.4.0",
+    "mongoose": "4.0.4",
+    "mysql": "2.7.0",
+    "pg": "^4.3.0",
+    "pg-hstore": "^2.3.2",
+    "redis": "^0.12.1",
+    "sequelize": "3.1.1"
+  }
 }

+ 3 - 1
frameworks/JavaScript/hapi/setup.sh

@@ -14,4 +14,6 @@ npm install -g npm
 
 # run app
 npm install
-node app &
+node app &
+echo "sleeping additional 30 seconds for hapi..."
+sleep 30

+ 1 - 0
frameworks/JavaScript/hapi/views/fortunes.hbs

@@ -0,0 +1 @@
+<!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>

+ 0 - 21
frameworks/JavaScript/hapi/views/fortunes.html

@@ -1,21 +0,0 @@
-<!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>