Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ on:
workflow_dispatch:
inputs:
engines:
description: 'Comma-separated engines to benchmark. Supported by this workflow: "http-native,bun,fiber"'
description: 'Comma-separated engines to benchmark. Supported by this workflow default run: "http-native,bun"'
required: false
default: "http-native,bun,fiber"
default: "http-native,bun"
scenarios:
description: 'Comma-separated scenarios: "static,dynamic,opt"'
required: false
Expand All @@ -29,7 +29,6 @@ on:
description: "Bombardier timeout"
required: false
default: "2s"

jobs:
benchmark:
runs-on: ubuntu-latest
Expand All @@ -46,6 +45,11 @@ jobs:
with:
bun-version: latest

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

Expand All @@ -59,21 +63,36 @@ jobs:
go install github.com/codesenberg/bombardier@latest
echo "$(go env GOPATH)/bin" >> $GITHUB_PATH

- name: Run benchmarks
- name: Run benchmarks (http-native on Bun + selected engines)
run: |
bun bench/ci.js \
--engines="${{ github.event.inputs.engines || 'http-native,bun' }}" \
--scenarios="${{ github.event.inputs.scenarios || 'static,dynamic,opt' }}" \
--connections="${{ github.event.inputs.connections || '200' }}" \
--duration="${{ github.event.inputs.duration || '10s' }}" \
--timeout="${{ github.event.inputs.timeout || '2s' }}" \
--http-native-runtime="bun" \
--output-dir="bench/results/bun"

- name: Run benchmarks (http-native on Node)
run: |
bun bench/ci.js \
--engines="${{ github.event.inputs.engines || 'http-native,bun,fiber' }}" \
--engines="http-native" \
--scenarios="${{ github.event.inputs.scenarios || 'static,dynamic,opt' }}" \
--connections="${{ github.event.inputs.connections || '200' }}" \
--duration="${{ github.event.inputs.duration || '10s' }}" \
--timeout="${{ github.event.inputs.timeout || '2s' }}"
--timeout="${{ github.event.inputs.timeout || '2s' }}" \
--http-native-runtime="node" \
--output-dir="bench/results/node"

- name: Upload benchmark artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: |
bench/results/results.json
bench/results/summary.md
bench/results/bun/results.json
bench/results/bun/summary.md
bench/results/node/results.json
bench/results/node/summary.md
if-no-files-found: ignore
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Http-native is opensource and freely available for everyone.

When commiting code please mention what type of commit it is via the prefix. Example,

opt: This flag is used to show optimizations <br/>
chore: Fix / chore <br/>
rm: removal <br/>
other: Everything else. <br/>

A commit would like this,

opt: Fix benchmarks to be more optimized.
chore: removed ugly comments
rm: Removed all deprecated code
other: added a contributing.md

You are allowed to mix commit types via & as seen in 'opt&chore:'.

Please don't push code that's below the current benchmarks. Don't push testing code.

Contact Us
You can find us in a couple places online. First and foremost, we're active right here on GitHub. If you encounter a bug or other problems, open an issue on here for us to take a look at it. We also accept feature requests here as well.

- Http-native (Nadhi)
27 changes: 22 additions & 5 deletions bench/ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { once } from "node:events";
import { resolve } from "node:path";
import { homedir } from "node:os";

const DEFAULT_ENGINES = ["http-native", "bun", "fiber"];
const DEFAULT_ENGINES = ["http-native", "bun"];
const DEFAULT_SCENARIOS = ["static", "dynamic", "opt"];
const DEFAULT_CONNECTIONS = 200;
const DEFAULT_DURATION = "10s";
const DEFAULT_TIMEOUT = "2s";
const DEFAULT_OUTPUT_DIR = "bench/results";
const DEFAULT_HTTP_NATIVE_RUNTIME = "bun";
const DEFAULT_BOMBARDIER_BIN = resolveBombardierBin();
const SUPPORTED_HTTP_NATIVE_RUNTIMES = new Set(["bun", "node"]);

const SERVER_PORTS = Object.freeze({
bun: { static: 3000, dynamic: 3010, opt: 3020 },
Expand Down Expand Up @@ -59,6 +61,7 @@ async function main() {
connections: options.connections,
duration: options.duration,
timeout: options.timeout,
httpNativeRuntime: options.httpNativeRuntime,
},
results,
};
Expand Down Expand Up @@ -130,6 +133,15 @@ function parseArgs(argv) {
const duration = values.get("duration") ?? DEFAULT_DURATION;
const timeout = values.get("timeout") ?? DEFAULT_TIMEOUT;
const outputDir = values.get("output-dir") ?? DEFAULT_OUTPUT_DIR;
const httpNativeRuntime = (values.get("http-native-runtime") ?? DEFAULT_HTTP_NATIVE_RUNTIME).trim();

if (!SUPPORTED_HTTP_NATIVE_RUNTIMES.has(httpNativeRuntime)) {
throw new Error(
`Unsupported --http-native-runtime \"${httpNativeRuntime}\". Supported runtimes: ${[
...SUPPORTED_HTTP_NATIVE_RUNTIMES,
].join(", ")}`,
);
}

return {
engines,
Expand All @@ -138,18 +150,22 @@ function parseArgs(argv) {
duration,
timeout,
outputDir,
httpNativeRuntime,
};
}

function printUsage() {
console.log("Usage: bun bench/ci.js [options]");
console.log("");
console.log("Options:");
console.log(` --engines=http-native,bun,fiber Comma-separated list. Default: ${DEFAULT_ENGINES.join(",")}`);
console.log(` --engines=http-native,bun Comma-separated list. Default: ${DEFAULT_ENGINES.join(",")}`);
console.log(` --scenarios=static,dynamic,opt Comma-separated list. Default: ${DEFAULT_SCENARIOS.join(",")}`);
console.log(` --connections=${DEFAULT_CONNECTIONS} Bombardier concurrency`);
console.log(` --duration=${DEFAULT_DURATION} Bombardier duration`);
console.log(` --timeout=${DEFAULT_TIMEOUT} Bombardier timeout`);
console.log(
` --http-native-runtime=${DEFAULT_HTTP_NATIVE_RUNTIME} Runtime for http-native/old: bun | node`,
);
console.log(` --output-dir=${DEFAULT_OUTPUT_DIR} Where to write results.json and summary.md`);
}

Expand Down Expand Up @@ -212,7 +228,7 @@ function portFor(engine, scenario) {
async function runBenchmarkCase(testCase, options) {
console.log(`[http-native][bench] starting ${testCase.engine}/${testCase.scenario} on :${testCase.port}`);

const server = spawnServer(testCase);
const server = spawnServer(testCase, options);
const serverLogs = [];
let readyResolve;
const ready = new Promise((resolve) => {
Expand Down Expand Up @@ -308,9 +324,10 @@ async function runBenchmarkCase(testCase, options) {
}
}

function spawnServer(testCase) {
function spawnServer(testCase, options) {
if (testCase.engine === "bun" || testCase.engine === "http-native" || testCase.engine === "old") {
return spawn("bun", ["bench/target.js", testCase.engine, testCase.scenario, String(testCase.port)], {
const runtime = testCase.engine === "bun" ? "bun" : options.httpNativeRuntime;
return spawn(runtime, ["bench/target.js", testCase.engine, testCase.scenario, String(testCase.port)], {
cwd: process.cwd(),
detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "pipe"],
Expand Down
18 changes: 14 additions & 4 deletions bench/run.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import { spawn } from "node:child_process";
import { once } from "node:events";

const [, , engineArg, scenarioArg, portArg] = process.argv;
const [, , engineArg, scenarioArg, portArg, runtimeArg] = process.argv;

const engine = engineArg ?? "http-native";
const scenario = scenarioArg ?? "static";
const port = Number(portArg ?? 3001);
const httpNativeRuntime = runtimeArg ?? "bun";

function printUsage() {
console.log("Usage: bun bench/run.js <engine> <scenario> <port>");
console.log("Usage: bun bench/run.js <engine> <scenario> <port> [httpNativeRuntime]");
console.log("Engines: http-native | bun | fiber | xitca | monoio | zig");
console.log("Scenarios: static | dynamic | opt");
console.log("httpNativeRuntime: bun | node (only applies to http-native and old)");
console.log("");
console.log("Example:");
console.log(" bun bench/run.js http-native static 3001");
console.log(" bun bench/run.js http-native static 3001 bun");
console.log(" bun bench/run.js http-native static 3001 node");
console.log(" bombardier -c 200 -d 10s http://127.0.0.1:3001/");
}

Expand All @@ -40,6 +43,13 @@ async function main() {
process.exit(1);
}

if (!["bun", "node"].includes(httpNativeRuntime)) {
printUsage();
process.exit(1);
}

const targetRuntime = engine === "http-native" || engine === "old" ? httpNativeRuntime : "bun";

const child =
engine === "xitca" || engine === "monoio"
? spawn(
Expand Down Expand Up @@ -74,7 +84,7 @@ async function main() {
cwd: process.cwd(),
stdio: ["ignore", "pipe", "inherit"],
})
: spawn("bun", ["bench/target.js", engine, scenario, String(port)], {
: spawn(targetRuntime, ["bench/target.js", engine, scenario, String(port)], {
cwd: process.cwd(),
stdio: ["ignore", "pipe", "inherit"],
});
Expand Down
3 changes: 2 additions & 1 deletion bench/target.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { resolve } from "node:path";

process.env.HTTP_NATIVE_NODE_PATH ??= resolve(process.cwd(), "http-native.release.node");
process.env.HTTP_NATIVE_NATIVE_PATH ??= process.env.HTTP_NATIVE_NODE_PATH;

const { createApp: createHttpNativeApp } = await import("../src/index.js");

Expand Down Expand Up @@ -124,7 +125,7 @@ async function startFrameworkServer(createApp, label, activeScenario) {
port,
opt:
label === "http-native" && activeScenario === "opt"
? { notify: true }
? { notify: true, cache: true }
: undefined,
});

Expand Down
8 changes: 4 additions & 4 deletions examples/cors/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { cors } from "http-native/cors";

const app = createApp();

// ─── Example 1: Allow all origins ─────────────────────────────────────────────
// ─── Example 1: Allow all origins ───────
// app.use(cors());

// ─── Example 2: Allow specific origin ─────────────────────────────────────────
// ─── Example 2: Allow specific origin ───
// app.use(cors({ origin: "https://myapp.com" }));

// ─── Example 3: Allow multiple origins ────────────────────────────────────────
// ─── Example 3: Allow multiple origins ──
// app.use(cors({ origin: ["https://myapp.com", "https://admin.myapp.com"] }));

// ─── Example 4: Dynamic origin with credentials ──────────────────────────────
Expand All @@ -29,7 +29,7 @@ app.use(
}),
);

// ─── Routes ───────────────────────────────────────────────────────────────────
// ─── Routes ─────────────────────────────

app.get("/api/data", (req, res) => {
res.set("X-Request-Id", crypto.randomUUID()).json({
Expand Down
5 changes: 2 additions & 3 deletions examples/error-handling/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { createApp } from "http-native";

const app = createApp();

// ─── Custom Error Classes ─────────────────────────────────────────────────────

class AppError extends Error {
constructor(message, statusCode = 500, code = "INTERNAL_ERROR") {
Expand Down Expand Up @@ -30,7 +29,7 @@ class UnauthorizedError extends AppError {
}
}

// ─── Global Error Handler ─────────────────────────────────────────────────────
// Global error handler

app.onError((err, req, res) => {
// Known application errors
Expand Down Expand Up @@ -58,7 +57,7 @@ app.onError((err, req, res) => {
});
});

// ─── Routes ───────────────────────────────────────────────────────────────────
// Routes.

app.get("/", (req, res) => {
res.json({ status: "ok" });
Expand Down
10 changes: 5 additions & 5 deletions examples/middleware/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createApp } from "http-native";

const app = createApp();

// ─── Logging Middleware (global) ──────────────────────────────────────────────
// ─── Logging Middleware (global) ────────

app.use(async (req, res, next) => {
const start = performance.now();
Expand All @@ -14,7 +14,7 @@ app.use(async (req, res, next) => {
console.log(`← ${req.method} ${req.path} [${duration}ms]`);
});

// ─── Auth Middleware (scoped to /api) ─────────────────────────────────────────
// ─── Auth Middleware (scoped to /api) ───

app.use("/api", async (req, res, next) => {
const token = req.header("authorization");
Expand All @@ -39,7 +39,7 @@ app.use("/api", async (req, res, next) => {
await next();
});

// ─── Request ID Middleware (global) ────────────────────────────────────────────
// ─── Request ID Middleware (global) ──────

app.use(async (req, res, next) => {
const requestId = crypto.randomUUID();
Expand All @@ -48,7 +48,7 @@ app.use(async (req, res, next) => {
await next();
});

// ─── Routes ───────────────────────────────────────────────────────────────────
// ─── Routes ─────────────────────────────

// Public route (only logging + request ID middlewares run)
app.get("/", (req, res) => {
Expand All @@ -70,7 +70,7 @@ app.get("/api/secret", (req, res) => {
});
});

// ─── Error Handler ────────────────────────────────────────────────────────────
// ─── Error Handler ──────────────────────

app.onError((err, req, res) => {
console.error(`❌ Error on ${req.method} ${req.path}:`, err.message);
Expand Down
Loading
Loading