-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add ultimate express * feat: add ultimate express * refactor(express): simplify response handling by removing writeResponse helper - removed writeResponse function and headerTypes constant for direct header and response handling - refactored routes to set headers and send responses inline, making code more declarative - improved readability and reduced abstraction in response handling for each route * fix: unique display names * fix: names * fix: add db/query/update/fortune urls * fix: missing orm value * fixes * fixes * fixes * full refactor * fix etag and disable x-powered-by * generate json dynamically * fix space * use fjs --------- Co-authored-by: dimden <26517362+dimdenGD@users.noreply.github.com>
- Loading branch information
Showing
12 changed files
with
1,175 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
# UltimateExpress Benchmarking Test | ||
|
||
The Ultimate Express. Fastest http server with full Express compatibility, based on [µWebSockets](https://github.com/uNetworking/uWebSockets.js). | ||
|
||
## Important Libraries | ||
|
||
The tests were run with: | ||
|
||
- [ultimate-express](https://github.com/dimdenGD/ultimate-express) | ||
- [postgres](https://github.com/porsager/postgres) | ||
- [mariadb](https://github.com/mariadb-corporation/mariadb-connector-nodejs) | ||
- [lru-cache](https://github.com/isaacs/node-lru-cache) | ||
|
||
## Database | ||
|
||
There are individual handlers for each DB approach. The logic for each of them are found here: | ||
|
||
- [Postgres](database/postgres.js) | ||
- [MySQL](database/mysql.js) | ||
|
||
There are **no database endpoints** or drivers attached by default. | ||
|
||
To initialize the application with one of these, run any _one_ of the following commands: | ||
|
||
```sh | ||
$ DATABASE=postgres npm start | ||
$ DATABASE=mysql npm start | ||
``` | ||
|
||
## Test Endpoints | ||
|
||
> Visit the test requirements [here](https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview) | ||
```sh | ||
$ curl localhost:8080/json | ||
$ curl localhost:8080/plaintext | ||
|
||
# The following are only available with the DATABASE env var | ||
|
||
$ curl localhost:8080/db | ||
$ curl localhost:8080/fortunes | ||
|
||
$ curl localhost:8080/queries?queries=2 | ||
$ curl localhost:8080/queries?queries=0 | ||
$ curl localhost:8080/queries?queries=foo | ||
$ curl localhost:8080/queries?queries=501 | ||
$ curl localhost:8080/queries?queries= | ||
|
||
$ curl localhost:8080/updates?queries=2 | ||
$ curl localhost:8080/updates?queries=0 | ||
$ curl localhost:8080/updates?queries=foo | ||
$ curl localhost:8080/updates?queries=501 | ||
$ curl localhost:8080/updates?queries= | ||
|
||
$ curl localhost:8080/cached-worlds?count=2 | ||
$ curl localhost:8080/cached-worlds?count=0 | ||
$ curl localhost:8080/cached-worlds?count=foo | ||
$ curl localhost:8080/cached-worlds?count=501 | ||
$ curl localhost:8080/cached-worlds?count= | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import express from 'ultimate-express'; | ||
import { LRUCache } from 'lru-cache'; | ||
import cluster, { isWorker } from 'node:cluster'; | ||
import { maxQuery, maxRows } from './config.js'; | ||
import fjs from 'fast-json-stringify'; | ||
|
||
const { DATABASE } = process.env; | ||
const db = DATABASE ? await import(`./database/${DATABASE}.js`) : null; | ||
|
||
const jsonSerializer = fjs({ | ||
type: 'object', | ||
properties: { | ||
message: { | ||
type: 'string', | ||
format: 'unsafe', | ||
} | ||
} | ||
}); | ||
|
||
const generateRandomNumber = () => Math.floor(Math.random() * maxRows) + 1; | ||
|
||
const parseQueries = (i) => Math.min(parseInt(i) || 1, maxQuery); | ||
|
||
const escapeHTMLRules = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '/': '/' }; | ||
|
||
const unsafeHTMLMatcher = /[&<>"'\/]/g; | ||
|
||
const escapeHTMLCode = (text) => unsafeHTMLMatcher.test(text) ? text.replace(unsafeHTMLMatcher, function (m) { return escapeHTMLRules[m] || m; }) : text; | ||
|
||
const cache = new LRUCache({ | ||
max: maxRows | ||
}); | ||
|
||
const app = express(); | ||
app.set("etag", false); | ||
app.set("x-powered-by", false); | ||
|
||
app.get('/plaintext', (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
res.setHeader('Content-Type', 'text/plain'); | ||
res.end('Hello, World!'); | ||
}); | ||
|
||
app.get('/json', (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
res.setHeader('Content-Type', 'application/json'); | ||
res.end(jsonSerializer({ message: "Hello, World!" })); | ||
}); | ||
|
||
if (db) { | ||
app.get('/db', async (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
|
||
try { | ||
const world = await db.find(generateRandomNumber()); | ||
res.json(world); | ||
} catch (error) { | ||
throw error; | ||
} | ||
}); | ||
|
||
app.get('/queries', async (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
|
||
try { | ||
const queries = parseQueries(req.query.queries); | ||
const worldPromises = new Array(queries); | ||
|
||
for (let i = 0; i < queries; i++) { | ||
worldPromises[i] = db.find(generateRandomNumber()); | ||
} | ||
|
||
const worlds = await Promise.all(worldPromises); | ||
|
||
res.json(worlds); | ||
} catch (error) { | ||
throw error; | ||
} | ||
}) | ||
|
||
app.get('/updates', async (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
|
||
try { | ||
const queries = parseQueries(req.query.queries); | ||
const worldPromises = new Array(queries); | ||
|
||
for (let i = 0; i < queries; i++) { | ||
worldPromises[i] = db.find(generateRandomNumber()); | ||
} | ||
|
||
const worlds = await Promise.all(worldPromises); | ||
|
||
for (let i = 0; i < queries; i++) { | ||
worlds[i].randomNumber = generateRandomNumber(); | ||
} | ||
|
||
await db.bulkUpdate(worlds); | ||
|
||
res.json(worlds); | ||
} catch (error) { | ||
throw error; | ||
} | ||
}) | ||
|
||
app.get('/fortunes', async (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
|
||
try { | ||
const fortunes = await db.fortunes() | ||
|
||
fortunes.push({ id: 0, message: 'Additional fortune added at request time.' }); | ||
|
||
fortunes.sort((a, b) => (a.message < b.message) ? -1 : 1); | ||
|
||
const n = fortunes.length | ||
|
||
let i = 0, html = '' | ||
for (; i < n; i++) html += `<tr><td>${fortunes[i].id}</td><td>${escapeHTMLCode(fortunes[i].message)}</td></tr>` | ||
|
||
res | ||
.header('Content-Type', 'text/html; charset=utf-8') | ||
.end(`<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>${html}</table></body></html>`); | ||
} catch (error) { | ||
throw error; | ||
} | ||
}) | ||
|
||
let isCachePopulated = false | ||
app.get('/cached-worlds', async (req, res) => { | ||
res.setHeader('Server', 'UltimateExpress'); | ||
|
||
try { | ||
if (!isCachePopulated) { | ||
const worlds = await db.getAllWorlds(); | ||
for (let i = 0; i < worlds.length; i++) { | ||
cache.set(worlds[i].id, worlds[i]); | ||
} | ||
isCachePopulated = true; | ||
} | ||
const count = parseQueries(req.query.count); | ||
const worlds = new Array(count); | ||
|
||
for (let i = 0; i < count; i++) { | ||
worlds[i] = cache.get(generateRandomNumber()); | ||
} | ||
|
||
res.json(worlds); | ||
} catch (error) { | ||
throw error; | ||
} | ||
}); | ||
} | ||
|
||
app.listen(8080, () => { | ||
console.log(`${isWorker ? `${cluster.worker.id}: ` : ''}Successfully bound to http://0.0.0.0:8080`); | ||
}); |
70 changes: 70 additions & 0 deletions
70
frameworks/JavaScript/ultimate-express/benchmark_config.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
{ | ||
"framework": "ultimate-express", | ||
"tests": [ | ||
{ | ||
"default": { | ||
"json_url": "/json", | ||
"plaintext_url": "/plaintext", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "None", | ||
"framework": "ultimate-express", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "ultimate-express", | ||
"notes": "", | ||
"versus": "nodejs" | ||
}, | ||
"postgres": { | ||
"db_url": "/db", | ||
"fortune_url": "/fortunes", | ||
"query_url": "/queries?queries=", | ||
"update_url": "/updates?queries=", | ||
"cached_query_url": "/cached-worlds?count=", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "Postgres", | ||
"framework": "ultimate-express", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "ultimate-express-postgres", | ||
"notes": "", | ||
"versus": "nodejs" | ||
}, | ||
"mysql": { | ||
"db_url": "/db", | ||
"fortune_url": "/fortunes", | ||
"query_url": "/queries?queries=", | ||
"update_url": "/updates?queries=", | ||
"cached_query_url": "/cached-worlds?count=", | ||
"port": 8080, | ||
"approach": "Realistic", | ||
"classification": "Micro", | ||
"database": "MySQL", | ||
"framework": "ultimate-express", | ||
"language": "JavaScript", | ||
"flavor": "None", | ||
"orm": "Raw", | ||
"platform": "NodeJS", | ||
"webserver": "µws", | ||
"os": "Linux", | ||
"database_os": "Linux", | ||
"display_name": "ultimate-express-mysql", | ||
"notes": "", | ||
"versus": "nodejs" | ||
} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import cluster, { isPrimary, setupPrimary, fork } from 'node:cluster' | ||
import { cpus } from 'node:os' | ||
|
||
if (isPrimary) { | ||
setupPrimary({ | ||
exec: 'app.js', | ||
}) | ||
cluster.on('exit', (worker) => { | ||
console.log(`worker ${worker.process.pid} died`) | ||
process.exit(1) | ||
}) | ||
for (let i = 0; i < cpus().length; i++) { | ||
fork() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const maxQuery = 500 | ||
export const maxRows = 10000 | ||
export const clientOpts = { | ||
host: 'tfb-database', | ||
user: 'benchmarkdbuser', | ||
password: 'benchmarkdbpass', | ||
database: 'hello_world', | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { createPool } from 'mariadb' | ||
import { cpus } from 'node:os' | ||
import { clientOpts } from '../config.js' | ||
|
||
const pool = createPool({ ...clientOpts, connectionLimit: cpus().length }) | ||
|
||
const execute = (text, values) => pool.execute(text, values || undefined) | ||
|
||
export const fortunes = () => execute('SELECT id, message FROM fortune') | ||
|
||
export const find = (id) => execute('SELECT id, randomNumber FROM world WHERE id = ?', [id]).then(arr => arr[0]) | ||
|
||
export const getAllWorlds = () => execute('SELECT id, randomNumber FROM world') | ||
|
||
export const bulkUpdate = (worlds) => pool.batch('UPDATE world SET randomNumber = ? WHERE id = ?', worlds.map(world => [world.randomNumber, world.id]).sort((a, b) => (a[1] < b[1]) ? -1 : 1)) |
14 changes: 14 additions & 0 deletions
14
frameworks/JavaScript/ultimate-express/database/postgres.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import postgres from 'postgres' | ||
import { clientOpts } from '../config.js' | ||
|
||
const sql = postgres({ ...clientOpts, max: 1 }) | ||
|
||
export const fortunes = async () => sql`SELECT id, message FROM fortune` | ||
|
||
export const find = async (id) => sql`SELECT id, randomNumber FROM world WHERE id = ${id}`.then((arr) => arr[0]) | ||
|
||
export const getAllWorlds = async () => sql`SELECT id, randomNumber FROM world` | ||
|
||
export const bulkUpdate = async (worlds) => await sql`UPDATE world SET randomNumber = (update_data.randomNumber)::int | ||
FROM (VALUES ${sql(worlds.map(world => [world.id, world.randomNumber]).sort((a, b) => (a[0] < b[0]) ? -1 : 1))}) AS update_data (id, randomNumber) | ||
WHERE world.id = (update_data.id)::int`; |
Oops, something went wrong.