فهرست منبع

[JavaScript] Add uWebSockets.js (#8202)

* Add uWebSockets.js with MySQL

* Update uWebSockets.js with Postgres

* Update readme and fix import

* Add fortunes and updates

* Update node.js version

* Remove mysql for now
Aayush Kapoor 2 سال پیش
والد
کامیت
6fd0b93124

+ 52 - 0
frameworks/JavaScript/uwebsockets.js/README.md

@@ -0,0 +1,52 @@
+# uWebSockets.js Benchmarking Test
+
+uWebSockets is a web server written in C/C++ (https://github.com/uNetworking/uWebSockets)
+
+µWebSockets.js is a web server bypass for Node.js (https://github.com/uNetworking/uWebSockets.js)
+
+## Important Libraries
+
+The tests were run with:
+
+- [uWebSockets.js](https://github.com/uNetworking/uWebSockets.js/)
+- [pg](https://github.com/brianc/node-postgres)
+
+## Database
+
+There are individual handlers for each DB approach. The logic for each of them are found here:
+
+- [PostgreSQL](database/postgres.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
+```
+
+## 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/updates?queries=
+$ curl localhost:8080/updates?queries=2
+$ curl localhost:8080/updates?queries=1000
+$ curl localhost:8080/updates?queries=foo
+$ curl localhost:8080/updates?queries=0
+
+$ curl localhost:8080/queries?queries=
+$ curl localhost:8080/queries?queries=2
+$ curl localhost:8080/queries?queries=1000
+$ curl localhost:8080/queries?queries=foo
+$ curl localhost:8080/queries?queries=0
+```

+ 47 - 0
frameworks/JavaScript/uwebsockets.js/benchmark_config.json

@@ -0,0 +1,47 @@
+{
+  "framework": "uwebsockets.js",
+  "tests": [
+    {
+      "default": {
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "None",
+        "database_os": "Linux",
+        "display_name": "uWebSockets.js",
+        "flavor": "NodeJS",
+        "framework": "uWebSockets.js",
+        "json_url": "/json",
+        "language": "JavaScript",
+        "notes": "",
+        "orm": "Raw",
+        "os": "Linux",
+        "plaintext_url": "/plaintext",
+        "platform": "nodejs",
+        "port": 8080,
+        "versus": "nodejs",
+        "webserver": "None"
+      },
+      "postgres": {
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "Postgres",
+        "database_os": "Linux",
+        "db_url": "/db",
+        "display_name": "uWebSockets.js",
+        "flavor": "NodeJS",
+        "fortune_url": "/fortunes",
+        "framework": "uWebSockets.js",
+        "language": "JavaScript",
+        "notes": "",
+        "orm": "Raw",
+        "os": "Linux",
+        "platform": "None",
+        "port": 8080,
+        "query_url": "/queries?queries=",
+        "update_url": "/updates?queries=",
+        "versus": "nodejs",
+        "webserver": "None"
+      }
+    }
+  ]
+}

+ 291 - 0
frameworks/JavaScript/uwebsockets.js/package-lock.json

@@ -0,0 +1,291 @@
+{
+  "name": "uwebsockets.js",
+  "version": "0.0.1",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "uwebsockets.js",
+      "version": "0.0.1",
+      "license": "MIT",
+      "dependencies": {
+        "html-escaper": "^3.0.3",
+        "pg": "^8.11.0",
+        "pg-pool": "^3.6.0",
+        "uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
+      }
+    },
+    "node_modules/buffer-writer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+      "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/html-escaper": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+      "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
+    },
+    "node_modules/packet-reader": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+      "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+    },
+    "node_modules/pg": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.0.tgz",
+      "integrity": "sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==",
+      "dependencies": {
+        "buffer-writer": "2.0.0",
+        "packet-reader": "1.0.0",
+        "pg-connection-string": "^2.6.0",
+        "pg-pool": "^3.6.0",
+        "pg-protocol": "^1.6.0",
+        "pg-types": "^2.1.0",
+        "pgpass": "1.x"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      },
+      "optionalDependencies": {
+        "pg-cloudflare": "^1.1.0"
+      },
+      "peerDependencies": {
+        "pg-native": ">=3.0.1"
+      },
+      "peerDependenciesMeta": {
+        "pg-native": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/pg-cloudflare": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz",
+      "integrity": "sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==",
+      "optional": true
+    },
+    "node_modules/pg-connection-string": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.0.tgz",
+      "integrity": "sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg=="
+    },
+    "node_modules/pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/pg-pool": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
+      "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
+      "peerDependencies": {
+        "pg": ">=8.0"
+      }
+    },
+    "node_modules/pg-protocol": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+      "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
+    },
+    "node_modules/pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "dependencies": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "dependencies": {
+        "split2": "^4.1.0"
+      }
+    },
+    "node_modules/postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/postgres-bytea": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+      "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "dependencies": {
+        "xtend": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+      "engines": {
+        "node": ">= 10.x"
+      }
+    },
+    "node_modules/uWebSockets.js": {
+      "version": "20.30.0",
+      "resolved": "git+ssh://[email protected]/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142"
+    },
+    "node_modules/xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+      "engines": {
+        "node": ">=0.4"
+      }
+    }
+  },
+  "dependencies": {
+    "buffer-writer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+      "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
+    },
+    "html-escaper": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz",
+      "integrity": "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="
+    },
+    "packet-reader": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+      "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+    },
+    "pg": {
+      "version": "8.11.0",
+      "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.0.tgz",
+      "integrity": "sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==",
+      "requires": {
+        "buffer-writer": "2.0.0",
+        "packet-reader": "1.0.0",
+        "pg-cloudflare": "^1.1.0",
+        "pg-connection-string": "^2.6.0",
+        "pg-pool": "^3.6.0",
+        "pg-protocol": "^1.6.0",
+        "pg-types": "^2.1.0",
+        "pgpass": "1.x"
+      }
+    },
+    "pg-cloudflare": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.0.tgz",
+      "integrity": "sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==",
+      "optional": true
+    },
+    "pg-connection-string": {
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.0.tgz",
+      "integrity": "sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg=="
+    },
+    "pg-int8": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+      "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
+    },
+    "pg-pool": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
+      "integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
+      "requires": {}
+    },
+    "pg-protocol": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
+      "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
+    },
+    "pg-types": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+      "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+      "requires": {
+        "pg-int8": "1.0.1",
+        "postgres-array": "~2.0.0",
+        "postgres-bytea": "~1.0.0",
+        "postgres-date": "~1.0.4",
+        "postgres-interval": "^1.1.0"
+      }
+    },
+    "pgpass": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+      "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+      "requires": {
+        "split2": "^4.1.0"
+      }
+    },
+    "postgres-array": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+      "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
+    },
+    "postgres-bytea": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+      "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w=="
+    },
+    "postgres-date": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+      "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
+    },
+    "postgres-interval": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+      "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+      "requires": {
+        "xtend": "^4.0.0"
+      }
+    },
+    "split2": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+      "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="
+    },
+    "uWebSockets.js": {
+      "version": "git+ssh://[email protected]/uNetworking/uWebSockets.js.git#d39d4181daf5b670d44cbc1b18f8c28c85fd4142",
+      "from": "uWebSockets.js@uNetworking/uWebSockets.js#v20.30.0"
+    },
+    "xtend": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+    }
+  }
+}

+ 18 - 0
frameworks/JavaScript/uwebsockets.js/package.json

@@ -0,0 +1,18 @@
+{
+  "dependencies": {
+    "html-escaper": "^3.0.3",
+    "pg": "^8.11.0",
+    "pg-pool": "^3.6.0",
+    "uWebSockets.js": "uNetworking/uWebSockets.js#v20.30.0"
+  },
+  "license": "MIT",
+  "main": "src/server.js",
+  "name": "uwebsockets.js",
+  "private": true,
+  "scripts": {
+    "dev": "node src/server.js",
+    "start": "node src/clustered.js"
+  },
+  "type": "module",
+  "version": "0.0.1"
+}

+ 23 - 0
frameworks/JavaScript/uwebsockets.js/src/clustered.js

@@ -0,0 +1,23 @@
+import cluster from "node:cluster";
+import os from "node:os";
+import process from "node:process";
+
+if (cluster.isPrimary) {
+  // Master Node
+  console.log(`Primary ${process.pid} is running`);
+
+  // Fork workers
+  const numCPUs = os.cpus().length;
+  for (let i = 0; i < numCPUs; i++) {
+    cluster.fork();
+  }
+
+  cluster.on("exit", (worker) => {
+    console.log(`worker ${worker.process.pid} died`);
+    process.exit(1);
+  });
+} else {
+  // Cluster Node
+  await import("./server.js");
+  console.log(`Worker ${process.pid} started`);
+}

+ 24 - 0
frameworks/JavaScript/uwebsockets.js/src/database/postgres.js

@@ -0,0 +1,24 @@
+import Pool from "pg-pool";
+
+const pool = new Pool({
+  host: "tfb-database",
+  user: "benchmarkdbuser",
+  password: "benchmarkdbpass",
+  database: "hello_world",
+});
+
+await pool.connect();
+
+const query = (text, values) =>
+  pool.query(text, values || []).then((r) => r.rows);
+
+export const fortunes = () => query("SELECT * FROM fortune");
+
+export const find = (id) =>
+  query("SELECT * FROM world WHERE id = $1", [id]).then((arr) => arr[0]);
+
+export const update = (obj) =>
+  query("UPDATE world SET randomNumber = $1 WHERE id = $2", [
+    obj.randomNumber,
+    obj.id,
+  ]);

+ 205 - 0
frameworks/JavaScript/uwebsockets.js/src/server.js

@@ -0,0 +1,205 @@
+import { escape } from "html-escaper";
+import uWebSockets from "uWebSockets.js";
+import {
+  addBenchmarkHeaders,
+  generateRandomNumber,
+  getQueriesCount,
+  handleError,
+} from "./utils.js";
+
+let db;
+const { DATABASE } = process.env;
+if (DATABASE) db = await import(`./database/${DATABASE}.js`);
+
+const webserver = uWebSockets.App();
+
+webserver.get("/plaintext", (response) => {
+  addBenchmarkHeaders(response);
+  response.writeHeader("Content-Type", "text/plain");
+  response.end("Hello, World!");
+});
+
+webserver.get("/json", (response) => {
+  addBenchmarkHeaders(response);
+  response.writeHeader("Content-Type", "application/json");
+  response.end(JSON.stringify({ message: "Hello, World!" }));
+});
+
+if (db) {
+  webserver.get("/db", async (response) => {
+    response.onAborted(() => {
+      response.aborted = true;
+    });
+
+    try {
+      const rows = await db.find(generateRandomNumber());
+
+      if (response.aborted) {
+        return;
+      }
+
+      if (rows.length < 1) {
+        return handleError(new Error("Row not found"), response);
+      }
+
+      response.cork(() => {
+        addBenchmarkHeaders(response);
+        response.writeHeader("Content-Type", "application/json");
+        response.end(JSON.stringify(rows));
+      });
+    } catch (error) {
+      if (response.aborted) {
+        return;
+      }
+
+      handleError(error, response);
+    }
+  });
+
+  webserver.get("/queries", async (response, request) => {
+    response.onAborted(() => {
+      response.aborted = true;
+    });
+
+    try {
+      const queriesCount = getQueriesCount(request);
+
+      const databaseJobs = [];
+
+      for (let i = 0; i < queriesCount; i++) {
+        databaseJobs.push(db.find(generateRandomNumber()));
+      }
+
+      const worldObjects = await Promise.all(databaseJobs);
+
+      if (response.aborted) {
+        return;
+      }
+
+      response.cork(() => {
+        addBenchmarkHeaders(response);
+        response.writeHeader("Content-Type", "application/json");
+        response.end(JSON.stringify(worldObjects));
+      });
+    } catch (error) {
+      if (response.aborted) {
+        return;
+      }
+
+      handleError(error, response);
+    }
+  });
+
+  webserver.get("/fortunes", async (response) => {
+    response.onAborted(() => {
+      response.aborted = true;
+    });
+
+    try {
+      const rows = await db.fortunes();
+
+      if (response.aborted) {
+        return;
+      }
+
+      if (rows.length < 1) {
+        return handleError(new Error("Row not found"), response);
+      }
+
+      rows.push({
+        id: 0,
+        message: "Additional fortune added at request time.",
+      });
+
+      rows.sort((a, b) => a.message.localeCompare(b.message));
+
+      let html =
+        "<!DOCTYPE html><html><head><title>Fortunes</title></head><body><table><tr><th>id</th><th>message</th></tr>";
+
+      for (let i = 0; i < rows.length; i++) {
+        html += `<tr><td>${rows[i].id}</td><td>${escape(
+          rows[i].message
+        )}</td></tr>`;
+      }
+
+      html += "</table></body></html>";
+
+      response.cork(() => {
+        addBenchmarkHeaders(response);
+        response.writeHeader("Content-Type", "text/html; charset=utf-8");
+        response.end(html);
+      });
+    } catch (error) {
+      if (response.aborted) {
+        return;
+      }
+
+      handleError(error, response);
+    }
+  });
+
+  webserver.get("/updates", async (response, request) => {
+    response.onAborted(() => {
+      response.aborted = true;
+    });
+
+    try {
+      const queriesCount = getQueriesCount(request);
+
+      const databaseReadJobs = [];
+
+      for (let i = 0; i < queriesCount; i++) {
+        databaseReadJobs.push(db.find(generateRandomNumber()));
+      }
+
+      const worldObjects = await Promise.all(databaseReadJobs);
+
+      if (response.aborted) {
+        return;
+      }
+
+      const databaseWriteJobs = [];
+
+      for (let i = 0; i < worldObjects.length; i++) {
+        worldObjects[i].randomNumber = generateRandomNumber();
+        databaseWriteJobs.push(db.update(worldObjects[i]));
+      }
+
+      if (response.aborted) {
+        return;
+      }
+
+      await Promise.all(databaseWriteJobs);
+
+      response.cork(() => {
+        addBenchmarkHeaders(response);
+        response.writeHeader("Content-Type", "application/json");
+        response.end(JSON.stringify(worldObjects));
+      });
+    } catch (error) {
+      if (response.aborted) {
+        return;
+      }
+
+      handleError(error, response);
+    }
+  });
+}
+
+webserver.any("/*", (response) => {
+  response.writeStatus("404 Not Found");
+  addBenchmarkHeaders(response);
+  response.writeHeader("Content-Type", "text/plain");
+  response.end("Not Found");
+});
+
+const host = process.env.HOST || "0.0.0.0";
+const port = parseInt(process.env.PORT || "8080");
+webserver.listen(host, port, (socket) => {
+  if (!socket) {
+    console.error(`Couldn't bind to http://${host}:${port}!`);
+    process.exit(1);
+  }
+
+  console.log(`Successfully bound to http://${host}:${port}.`);
+});

+ 57 - 0
frameworks/JavaScript/uwebsockets.js/src/utils.js

@@ -0,0 +1,57 @@
+/**
+ * Add Benchmark HTTP response headers.
+ *
+ * Add HTTP response headers `Server` and `Date` which are required by the test suite.
+ * https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Framework-Tests-Overview
+ *
+ * @param {import('uWebSockets.js').HttpResponse} response
+ */
+export function addBenchmarkHeaders(response) {
+  response.writeHeader("Server", "uWebSockets.js");
+  response.writeHeader("Date", new Date().toUTCString());
+}
+
+/**
+ * Handle error for response
+ *
+ * @param {Error} error
+ * @param {import('uWebSockets.js').HttpResponse} response
+ */
+export function handleError(error, response) {
+  console.error(error);
+  response.cork(() => {
+    addBenchmarkHeaders(response);
+    response.writeHeader("Content-Type", "text/plain");
+    response.end("Internal Server Error");
+  });
+}
+
+/**
+ * Get queries count
+ *
+ * @param {import('uWebSockets.js').HttpRequest} request
+ */
+export function getQueriesCount(request) {
+  let queriesCount = 1;
+
+  if (request.getQuery("queries")) {
+    try {
+      const queries = parseInt(request.getQuery("queries"));
+      if (queries <= 500 && queries >= 1) {
+        queriesCount = queries;
+      } else if (queries > 500) {
+        queriesCount = 500;
+      }
+    } catch {}
+  }
+
+  return queriesCount;
+}
+
+/**
+ * Generate random number
+ *
+ */
+export function generateRandomNumber() {
+  return Math.floor(Math.random() * 9999) + 1;
+}

+ 12 - 0
frameworks/JavaScript/uwebsockets.js/uwebsockets.js-postgres.dockerfile

@@ -0,0 +1,12 @@
+FROM node:18-slim
+
+COPY ./ ./
+
+RUN npm install
+
+ENV NODE_ENV production
+ENV DATABASE postgres
+
+EXPOSE 8080
+
+CMD ["npm", "start"]

+ 11 - 0
frameworks/JavaScript/uwebsockets.js/uwebsockets.js.dockerfile

@@ -0,0 +1,11 @@
+FROM node:18-slim
+
+COPY ./ ./
+
+RUN npm install
+
+ENV NODE_ENV production
+
+EXPOSE 8080
+
+CMD ["npm", "start"]