|
@@ -0,0 +1,246 @@
|
|
|
+/// <reference types="@vertx/core/runtime" />
|
|
|
+// @ts-check
|
|
|
+
|
|
|
+import {Router} from '@vertx/web';
|
|
|
+
|
|
|
+import {PgClient, Tuple} from '@reactiverse/reactive-pg-client';
|
|
|
+import {PgPoolOptions} from '@reactiverse/reactive-pg-client/options';
|
|
|
+
|
|
|
+const util = require('./util');
|
|
|
+const HandlebarsTemplateEngine = Java.type('io.vertx.ext.web.templ.HandlebarsTemplateEngine');
|
|
|
+
|
|
|
+const SERVER = 'vertx.js';
|
|
|
+
|
|
|
+const app = Router.router(vertx);
|
|
|
+const template = HandlebarsTemplateEngine.create();
|
|
|
+let date = new Date().toString();
|
|
|
+
|
|
|
+vertx.setPeriodic(1000, t => date = new Date().toUTCString());
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test exercises the framework fundamentals including keep-alive support, request routing, request header
|
|
|
+ * parsing, object instantiation, JSON serialization, response header generation, and request count throughput.
|
|
|
+ */
|
|
|
+app.get("/json").handler(ctx => {
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "application/json")
|
|
|
+ .end(JSON.stringify({message: 'Hello, World!'}));
|
|
|
+});
|
|
|
+
|
|
|
+const UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
|
|
|
+const SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1";
|
|
|
+const SELECT_FORTUNE = "SELECT id, message from FORTUNE";
|
|
|
+
|
|
|
+let client = PgClient.pool(
|
|
|
+ vertx,
|
|
|
+ new PgPoolOptions()
|
|
|
+ .setCachePreparedStatements(true)
|
|
|
+ .setMaxSize(1)
|
|
|
+ .setHost('tfb-database')
|
|
|
+ .setUser('benchmarkdbuser')
|
|
|
+ .setPassword('benchmarkdbpass')
|
|
|
+ .setDatabase('hello_world'));
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test exercises the framework's object-relational mapper (ORM), random number generator, database driver,
|
|
|
+ * and database connection pool.
|
|
|
+ */
|
|
|
+app.get("/db").handler(ctx => {
|
|
|
+ client.preparedQuery(SELECT_WORLD, Tuple.of(util.randomWorld()), res => {
|
|
|
+ if (res.succeeded()) {
|
|
|
+ let resultSet = res.result().iterator();
|
|
|
+
|
|
|
+ if (!resultSet.hasNext()) {
|
|
|
+ ctx.fail(404);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let row = resultSet.next();
|
|
|
+
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "application/json")
|
|
|
+ .end(JSON.stringify({id: row.getInteger(0), randomNumber: row.getInteger(1)}));
|
|
|
+ } else {
|
|
|
+ ctx.fail(res.cause());
|
|
|
+ }
|
|
|
+ })
|
|
|
+});
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test is a variation of Test #2 and also uses the World table. Multiple rows are fetched to more dramatically
|
|
|
+ * punish the database driver and connection pool. At the highest queries-per-request tested (20), this test
|
|
|
+ * demonstrates all frameworks' convergence toward zero requests-per-second as database activity increases.
|
|
|
+ */
|
|
|
+app.get("/queries").handler(ctx => {
|
|
|
+ let failed = false;
|
|
|
+ let worlds = [];
|
|
|
+
|
|
|
+ const queries = util.getQueries(ctx.request());
|
|
|
+
|
|
|
+ for (let i = 0; i < queries; i++) {
|
|
|
+ client.preparedQuery(SELECT_WORLD, Tuple.of(util.randomWorld()), ar => {
|
|
|
+ if (!failed) {
|
|
|
+ if (ar.failed()) {
|
|
|
+ failed = true;
|
|
|
+ ctx.fail(ar.cause());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // we need a final reference
|
|
|
+ const row = ar.result().iterator().next();
|
|
|
+ worlds.push({id: row.getInteger(0), randomNumber: row.getInteger(1)});
|
|
|
+
|
|
|
+ // stop condition
|
|
|
+ if (worlds.length === queries) {
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "application/json")
|
|
|
+ .end(JSON.stringify(worlds));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test exercises the ORM, database connectivity, dynamic-size collections, sorting, server-side templates,
|
|
|
+ * XSS countermeasures, and character encoding.
|
|
|
+ */
|
|
|
+app.get("/fortunes").handler(ctx => {
|
|
|
+ client.preparedQuery(SELECT_FORTUNE, ar => {
|
|
|
+
|
|
|
+ if (ar.failed()) {
|
|
|
+ ctx.fail(ar.cause());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let fortunes = [];
|
|
|
+ let resultSet = ar.result().iterator();
|
|
|
+
|
|
|
+ if (!resultSet.hasNext()) {
|
|
|
+ ctx.fail(404);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ while (resultSet.hasNext()) {
|
|
|
+ let row = resultSet.next();
|
|
|
+ fortunes.push({id: row.getInteger(0), message: row.getString(1)});
|
|
|
+ }
|
|
|
+
|
|
|
+ fortunes.push({id: 0, message: "Additional fortune added at request time."});
|
|
|
+
|
|
|
+ fortunes.sort((a, b) => {
|
|
|
+ let messageA = a.message;
|
|
|
+ let messageB = b.message;
|
|
|
+ if (messageA < messageB) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ if (messageA > messageB) {
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
+
|
|
|
+ ctx.put("fortunes", fortunes);
|
|
|
+
|
|
|
+ // and now delegate to the engine to render it.
|
|
|
+ template.render(ctx, "templates", "/fortunes.hbs", res => {
|
|
|
+ if (res.succeeded()) {
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "text/html; charset=UTF-8")
|
|
|
+ .end(res.result());
|
|
|
+ } else {
|
|
|
+ ctx.fail(res.cause());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test is a variation of Test #3 that exercises the ORM's persistence of objects and the database driver's
|
|
|
+ * performance at running UPDATE statements or similar. The spirit of this test is to exercise a variable number of
|
|
|
+ * read-then-write style database operations.
|
|
|
+ */
|
|
|
+app.route("/updates").handler(ctx => {
|
|
|
+ let failed = false;
|
|
|
+ let queryCount = 0;
|
|
|
+ let worlds = [];
|
|
|
+
|
|
|
+ const queries = util.getQueries(ctx.request());
|
|
|
+
|
|
|
+ for (let i = 0; i < queries; i++) {
|
|
|
+ const id = util.randomWorld();
|
|
|
+ const index = i;
|
|
|
+
|
|
|
+ client.preparedQuery(SELECT_WORLD, Tuple.of(id), ar => {
|
|
|
+ if (!failed) {
|
|
|
+ if (ar.failed()) {
|
|
|
+ failed = true;
|
|
|
+ ctx.fail(ar.cause());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const row = ar.result().iterator().next();
|
|
|
+
|
|
|
+ worlds[index] = {id: row.getInteger(0), randomNumber: util.randomWorld()};
|
|
|
+ if (++queryCount === queries) {
|
|
|
+ worlds.sort((a, b) => {
|
|
|
+ return a.id - b.id;
|
|
|
+ });
|
|
|
+
|
|
|
+ let batch = [];
|
|
|
+
|
|
|
+ worlds.forEach(world => {
|
|
|
+ batch.push(Tuple.of(world.randomNumber, world.id));
|
|
|
+ });
|
|
|
+
|
|
|
+ client.preparedBatch(UPDATE_WORLD, batch, ar => {
|
|
|
+ if (ar.failed()) {
|
|
|
+ ctx.fail(ar.cause());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ let json = [];
|
|
|
+ worlds.forEach(world => {
|
|
|
+ json.push({id: world.id, randomNumber: world.randomNumber});
|
|
|
+ });
|
|
|
+
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "application/json")
|
|
|
+ .end(JSON.stringify(json));
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+/*
|
|
|
+ * This test is an exercise of the request-routing fundamentals only, designed to demonstrate the capacity of
|
|
|
+ * high-performance platforms in particular. Requests will be sent using HTTP pipelining. The response payload is
|
|
|
+ * still small, meaning good performance is still necessary in order to saturate the gigabit Ethernet of the test
|
|
|
+ * environment.
|
|
|
+ */
|
|
|
+app.get("/plaintext").handler(ctx => {
|
|
|
+ ctx.response()
|
|
|
+ .putHeader("Server", SERVER)
|
|
|
+ .putHeader("Date", date)
|
|
|
+ .putHeader("Content-Type", "text/plain")
|
|
|
+ .end('Hello, World!');
|
|
|
+});
|
|
|
+
|
|
|
+vertx
|
|
|
+ .createHttpServer()
|
|
|
+ .requestHandler(req => app.accept(req))
|
|
|
+ .listen(8080);
|
|
|
+
|
|
|
+console.log('Server listening at: http://localhost:8080/');
|