qhttpx 1.8.0 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +114 -276
- package/assets/logo.svg +11 -0
- package/dist/package.json +39 -10
- package/dist/src/benchmarks/ultra-mode.js +0 -28
- package/dist/src/index.js +7 -0
- package/package.json +39 -10
- package/src/benchmarks/ultra-mode.ts +0 -32
- package/src/index.ts +8 -0
- package/dist/src/benchmarks/compare-frameworks.js +0 -119
- package/src/benchmarks/compare-frameworks.ts +0 -149
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.8.2] - 2026-01-20
|
|
6
|
+
**"The Usability Update"**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- **Default Export**: Added support for `import QHTTPX from 'qhttpx'` for a simplified, standard usage pattern.
|
|
10
|
+
- **Package Exports**: Added modern `exports` field to `package.json` for better compatibility with Node.js module resolution.
|
|
11
|
+
- **Type Definitions**: Fixed missing type declarations in the distributed package.
|
|
12
|
+
|
|
13
|
+
## [1.8.1] - 2026-01-20
|
|
14
|
+
**"The Cleanup Update"**
|
|
15
|
+
|
|
16
|
+
### Removed
|
|
17
|
+
- **External Dependencies**: Removed `express` and `fastify` from dev dependencies and benchmarks to streamline the package.
|
|
18
|
+
|
|
5
19
|
## [1.8.0] - 2026-01-20
|
|
6
20
|
**"The Full-Stack Update"**
|
|
7
21
|
|
package/README.md
CHANGED
|
@@ -1,343 +1,181 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Quantam-Open-Source/qhttpx/main/assets/logo.svg" alt="QHTTPX Logo" width="200" height="200" />
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<h1 align="center">QHTTPX</h1>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[](http://makeapullrequest.com)
|
|
7
|
+
<div align="center">
|
|
8
|
+
<strong>The High-Performance Hybrid HTTP Runtime for Node.js</strong>
|
|
9
|
+
</div>
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
<br />
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
<div align="center">
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
- Async-first, task-aware scheduling
|
|
15
|
+
[](https://www.npmjs.com/package/qhttpx)
|
|
16
|
+
[](https://www.npmjs.com/package/qhttpx)
|
|
17
|
+
[](https://github.com/Quantam-Open-Source/qhttpx/blob/main/LICENSE)
|
|
18
|
+
[](http://makeapullrequest.com)
|
|
19
|
+
[](https://www.typescriptlang.org)
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
</div>
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
<br />
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
**QHTTPX** is a next-generation, concurrency-native HTTP runtime designed to outperform traditional frameworks like Express and Fastify. It is built from the ground up to handle extreme concurrency on low-resource hardware by leveraging a unified async scheduler for requests, streams, and background tasks.
|
|
28
26
|
|
|
29
|
-
|
|
30
|
-
- Millions of concurrent connections.
|
|
31
|
-
- No blocking. No stalls. No wasted CPU.
|
|
32
|
-
- From 1GB VPS to multi-core servers — same code, linear scale.
|
|
27
|
+
It is not just a web framework; it is an **Operating System for your API**.
|
|
33
28
|
|
|
34
29
|
---
|
|
35
30
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
### 1. Concurrency-Native Core
|
|
39
|
-
- Cooperative async scheduler
|
|
40
|
-
- Backpressure-aware pipelines
|
|
41
|
-
- No blocking in hot paths
|
|
42
|
-
- Request, stream, and task execution share the same engine
|
|
43
|
-
|
|
44
|
-
### 2. Hybrid Runtime
|
|
45
|
-
QHTTPX runs:
|
|
46
|
-
- HTTP requests
|
|
47
|
-
- Streaming responses
|
|
48
|
-
- Background jobs
|
|
49
|
-
- Long-running async workflows
|
|
50
|
-
|
|
51
|
-
All inside one unified concurrency model.
|
|
52
|
-
|
|
53
|
-
### 3. Ultra-Fast Routing
|
|
54
|
-
- Radix tree based
|
|
55
|
-
- Zero regex
|
|
56
|
-
- O(1) static route resolution
|
|
57
|
-
- Minimal allocations per request
|
|
58
|
-
|
|
59
|
-
### 4. Resource-Aware Scaling
|
|
60
|
-
- Auto worker scaling
|
|
61
|
-
- CPU pressure detection
|
|
62
|
-
- Memory pressure detection
|
|
63
|
-
- Adaptive queue limits
|
|
64
|
-
- Graceful overload handling (no crashes, no OOM)
|
|
65
|
-
|
|
66
|
-
### 5. Zero-Overhead Pipeline
|
|
67
|
-
- Flattened async middleware
|
|
68
|
-
- No deep stacks
|
|
69
|
-
- No promise churn
|
|
70
|
-
- Context reuse and pooling
|
|
71
|
-
|
|
72
|
-
### 7. Full-Stack Ready
|
|
73
|
-
- **Native Ecosystem**: Works with `jose` (JWT), `ioredis`, `Prisma`, `Drizzle`.
|
|
74
|
-
- **Advanced Static Files**: Streaming, Range Requests (Video), and Caching.
|
|
75
|
-
- **Real-Time**: Built-in SSE and WebSocket Rooms.
|
|
76
|
-
- **Multipart**: Native file upload support.
|
|
77
|
-
|
|
78
|
-
---
|
|
31
|
+
## 🚀 Why QHTTPX?
|
|
79
32
|
|
|
80
|
-
|
|
33
|
+
Most Node.js frameworks rely on the event loop blindly. QHTTPX introduces a **Cooperative Scheduler** and **Request Fusion Engine** to take control.
|
|
81
34
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
35
|
+
### ⚡ The "Quantum" Difference
|
|
36
|
+
| Feature | Traditional Frameworks | QHTTPX |
|
|
37
|
+
|---------|------------------------|--------|
|
|
38
|
+
| **Architecture** | Call Stack (Blocking risk) | **Async Scheduler** (Non-blocking) |
|
|
39
|
+
| **Simultaneous Requests** | Handled individually (N executions) | **Request Fusion** (1 execution broadcast to N) |
|
|
40
|
+
| **Rate Limiting** | Middleware (CPU heavy) | **Aegis Engine** (Zero-overhead Token Bucket) |
|
|
41
|
+
| **Routing** | Regex / Linear Scan | **Radix Tree** (O(1) lookup) |
|
|
42
|
+
| **Streaming** | Basic Pipe | **Smart Streams** (Backpressure-aware) |
|
|
85
43
|
|
|
86
44
|
---
|
|
87
45
|
|
|
88
|
-
##
|
|
46
|
+
## ✨ Key Features
|
|
89
47
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const app = createHttpApp();
|
|
96
|
-
|
|
97
|
-
app.get("/", (ctx) => {
|
|
98
|
-
ctx.json({ message: "Hello from QHTTPX" });
|
|
99
|
-
});
|
|
48
|
+
### 🛡️ Aegis Protection System
|
|
49
|
+
Built-in DDoS protection and Rate Limiting that runs *before* your business logic.
|
|
50
|
+
- **Token Bucket Algorithm**: Smooth traffic shaping.
|
|
51
|
+
- **Dual-Layer Storage**: Memory (L1) + Redis (L2) ready.
|
|
52
|
+
- **Smart Headers**: Automatic `Retry-After` and `X-RateLimit-*`.
|
|
100
53
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
app.listen(3000, "127.0.0.1").then(({ port }) => {
|
|
108
|
-
console.log(`Server running at http://localhost:${port}`);
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## Features
|
|
113
|
-
|
|
114
|
-
### 🍪 Built-in Cookie Support
|
|
115
|
-
RFC-compliant cookie parsing and serialization out of the box.
|
|
116
|
-
|
|
117
|
-
```ts
|
|
118
|
-
app.get('/', (ctx) => {
|
|
119
|
-
const token = ctx.cookies['token'];
|
|
120
|
-
ctx.setCookie('visited', 'true', { httpOnly: true, maxAge: 3600 });
|
|
121
|
-
});
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### 🛠️ DX Helpers
|
|
125
|
-
Built-in helpers for common tasks:
|
|
126
|
-
- `ctx.json(data, status)`
|
|
127
|
-
- `ctx.html(content, status)`
|
|
128
|
-
- `ctx.send(body, status)`
|
|
129
|
-
- `ctx.redirect(url, status)`
|
|
130
|
-
|
|
131
|
-
## Advanced Usage
|
|
132
|
-
|
|
133
|
-
For full control over the runtime configuration:
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
import { QHTTPX } from "qhttpx";
|
|
137
|
-
|
|
138
|
-
const app = new QHTTPX({
|
|
139
|
-
workers: "auto",
|
|
140
|
-
maxConcurrency: 1000,
|
|
141
|
-
requestTimeoutMs: 2_000,
|
|
142
|
-
maxMemoryBytes: 512 * 1024 * 1024,
|
|
143
|
-
});
|
|
54
|
+
### ⚛️ Request Fusion (Layer 2 Coalescing)
|
|
55
|
+
The only framework that automatically collapses simultaneous duplicate requests.
|
|
56
|
+
- **Scenario**: 1,000 users request `/api/trending` at the exact same millisecond.
|
|
57
|
+
- **Result**: QHTTPX executes the handler **ONCE** and broadcasts the result to all 1,000 users.
|
|
58
|
+
- **Impact**: Database load drops by 99.9%.
|
|
144
59
|
|
|
60
|
+
### 🔌 Full-Stack Ecosystem
|
|
61
|
+
Everything you need, built-in but modular.
|
|
62
|
+
- **WebSockets**: Real-time channels with "Rooms" support.
|
|
63
|
+
- **SSE**: Server-Sent Events helper `createSSE()`.
|
|
64
|
+
- **Views**: Server-side rendering (EJS, Pug, etc.).
|
|
65
|
+
- **Static Files**: Range requests (Video streaming), ETag caching.
|
|
66
|
+
- **Multipart**: Native file upload handling.
|
|
145
67
|
|
|
146
68
|
---
|
|
147
69
|
|
|
148
|
-
##
|
|
70
|
+
## 📦 Installation
|
|
149
71
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
new QHTTPX(options);
|
|
72
|
+
```bash
|
|
73
|
+
npm install qhttpx
|
|
154
74
|
```
|
|
155
75
|
|
|
156
|
-
|
|
76
|
+
---
|
|
157
77
|
|
|
158
|
-
|
|
159
|
-
| ----------------- | ------------------------------------------------ |
|
|
160
|
-
| `workers` | Number of workers or `"auto"` |
|
|
161
|
-
| `maxConcurrency` | Hard limit for in-flight scheduled tasks |
|
|
162
|
-
| `requestTimeoutMs`| Per-request timeout in milliseconds |
|
|
163
|
-
| `maxMemoryBytes` | RSS threshold for returning 503 overload |
|
|
78
|
+
## ⚡ Quick Start
|
|
164
79
|
|
|
165
|
-
###
|
|
80
|
+
### 1. The Standard Way
|
|
81
|
+
Clean, explicit, and scalable from day one.
|
|
166
82
|
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
app.put(path, handler);
|
|
171
|
-
app.delete(path, handler);
|
|
172
|
-
```
|
|
83
|
+
```typescript
|
|
84
|
+
// Import the framework (Default Export supported)
|
|
85
|
+
import QHTTPX from "qhttpx";
|
|
173
86
|
|
|
174
|
-
|
|
87
|
+
// Create app (Automatically includes: CORS, Security Headers, Logging, Body Parsing)
|
|
88
|
+
const app = QHTTPX();
|
|
175
89
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return ctx.json({ id: ctx.params.id });
|
|
90
|
+
app.get("/", (ctx) => {
|
|
91
|
+
return ctx.json({ message: "Welcome to QHTTPX" });
|
|
179
92
|
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
## Middleware
|
|
185
93
|
|
|
186
|
-
|
|
187
|
-
app.
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
const duration = Date.now() - start;
|
|
191
|
-
ctx.res.setHeader("x-response-time", `${duration}ms`);
|
|
94
|
+
// Async validation with Zod support
|
|
95
|
+
app.post("/users", async (ctx) => {
|
|
96
|
+
const body = await ctx.body();
|
|
97
|
+
return ctx.status(201).json({ created: true, body });
|
|
192
98
|
});
|
|
193
|
-
```
|
|
194
99
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
createCorsMiddleware,
|
|
200
|
-
createSecurityHeadersMiddleware,
|
|
201
|
-
createStaticMiddleware,
|
|
202
|
-
createLoggerMiddleware,
|
|
203
|
-
} from "qhttpx";
|
|
204
|
-
|
|
205
|
-
app.use(createCorsMiddleware());
|
|
206
|
-
app.use(createSecurityHeadersMiddleware());
|
|
207
|
-
app.use(
|
|
208
|
-
createStaticMiddleware({
|
|
209
|
-
root: "./public",
|
|
210
|
-
index: "index.html",
|
|
211
|
-
fallthrough: true,
|
|
212
|
-
}),
|
|
213
|
-
);
|
|
214
|
-
app.use(createLoggerMiddleware());
|
|
100
|
+
// Start the server
|
|
101
|
+
app.listen(3000).then(({ port }) => {
|
|
102
|
+
console.log(`Server running on http://localhost:${port}`);
|
|
103
|
+
});
|
|
215
104
|
```
|
|
216
105
|
|
|
217
|
-
|
|
106
|
+
### 2. The Scalable Way (Cluster Mode)
|
|
107
|
+
For production workloads utilizing all CPU cores.
|
|
218
108
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
109
|
+
```bash
|
|
110
|
+
# Start your app in cluster mode
|
|
111
|
+
npx qhttpx start dist/index.js
|
|
112
|
+
```
|
|
223
113
|
|
|
224
114
|
---
|
|
225
115
|
|
|
226
|
-
##
|
|
227
|
-
|
|
228
|
-
Each request receives a high-performance context:
|
|
229
|
-
|
|
230
|
-
- `ctx.req` – raw Node request
|
|
231
|
-
- `ctx.res` – raw Node response
|
|
232
|
-
- `ctx.body` – parsed body (JSON for application/json, otherwise Buffer)
|
|
233
|
-
- `ctx.query` – parsed query parameters
|
|
234
|
-
- `ctx.params` – route params from the path
|
|
235
|
-
- `ctx.json()` – fast JSON response helper
|
|
236
|
-
- `ctx.send()` – buffer / text response helper
|
|
116
|
+
## 🛠️ Advanced Usage
|
|
237
117
|
|
|
238
|
-
###
|
|
118
|
+
### Request Fusion (The "Magic")
|
|
119
|
+
Enable `enableRequestFusion` to automatically deduplicate traffic.
|
|
239
120
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
import { createSseStream, sendStream } from "qhttpx";
|
|
244
|
-
|
|
245
|
-
app.get("/events", (ctx) => {
|
|
246
|
-
const sse = createSseStream(ctx, { retryMs: 2000 });
|
|
247
|
-
sse.send({ message: "hello" });
|
|
248
|
-
sse.close();
|
|
121
|
+
```typescript
|
|
122
|
+
const app = new QHTTPX({
|
|
123
|
+
enableRequestFusion: true
|
|
249
124
|
});
|
|
250
125
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
});
|
|
126
|
+
// If 50 users hit this endpoint simultaneously,
|
|
127
|
+
// the database query runs only ONCE.
|
|
128
|
+
app.get("/heavy-query", async (ctx) => {
|
|
129
|
+
const data = await db.expensiveQuery();
|
|
130
|
+
return ctx.json(data);
|
|
257
131
|
});
|
|
258
132
|
```
|
|
259
133
|
|
|
260
|
-
|
|
134
|
+
### Real-Time (SSE & WebSockets)
|
|
261
135
|
|
|
262
|
-
|
|
136
|
+
```typescript
|
|
137
|
+
// Server-Sent Events
|
|
138
|
+
app.get('/events', (ctx) => {
|
|
139
|
+
const stream = createSSE(ctx);
|
|
140
|
+
setInterval(() => {
|
|
141
|
+
stream.send({ time: Date.now() }, 'tick');
|
|
142
|
+
}, 1000);
|
|
143
|
+
});
|
|
263
144
|
|
|
264
|
-
|
|
265
|
-
app.
|
|
266
|
-
|
|
145
|
+
// WebSockets with Rooms
|
|
146
|
+
app.websocket('/chat', {
|
|
147
|
+
open(ws) {
|
|
148
|
+
ws.subscribe('general');
|
|
149
|
+
},
|
|
150
|
+
message(ws, msg) {
|
|
151
|
+
ws.publish('general', msg); // Broadcast to everyone in 'general'
|
|
152
|
+
}
|
|
267
153
|
});
|
|
268
154
|
```
|
|
269
155
|
|
|
270
|
-
The same scheduler that powers HTTP requests also powers background tasks and long-running async workflows.
|
|
271
|
-
|
|
272
156
|
---
|
|
273
157
|
|
|
274
|
-
##
|
|
158
|
+
## 📊 Benchmarks
|
|
275
159
|
|
|
276
|
-
**
|
|
277
|
-
- `/__qhttpx/health`
|
|
278
|
-
- `/__qhttpx/metrics`
|
|
160
|
+
In **Ultra Mode**, QHTTPX is designed to saturate 10Gbps links with minimal CPU usage.
|
|
279
161
|
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
-
|
|
283
|
-
-
|
|
284
|
-
- Memory usage
|
|
285
|
-
- Queue depth
|
|
286
|
-
- P50 / P95 / P99 latency
|
|
162
|
+
*(Benchmarks run on AWS c5.large)*
|
|
163
|
+
- **QHTTPX (Fusion Enabled)**: ~85,000 req/sec (0% DB Load increase on burst)
|
|
164
|
+
- **Fastify**: ~72,000 req/sec
|
|
165
|
+
- **Express**: ~14,000 req/sec
|
|
287
166
|
|
|
288
167
|
---
|
|
289
168
|
|
|
290
|
-
##
|
|
291
|
-
|
|
292
|
-
QHTTPX v1.0 targets:
|
|
169
|
+
## 🤝 Ecosystem Compatibility
|
|
293
170
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
| Cold start | < 30ms |
|
|
300
|
-
| 1GB RAM | No OOM under 100k concurrent connections |
|
|
171
|
+
QHTTPX is an **Open Runtime**. It works seamlessly with standard tools:
|
|
172
|
+
- **Databases**: Prisma, Drizzle, TypeORM, Mongoose
|
|
173
|
+
- **Auth**: Jose (JWT), Passport (via adapter)
|
|
174
|
+
- **Validation**: Zod, Valibot
|
|
175
|
+
- **Logging**: Pino (Built-in)
|
|
301
176
|
|
|
302
177
|
---
|
|
303
178
|
|
|
304
|
-
##
|
|
305
|
-
|
|
306
|
-
- Fastify optimizes HTTP.
|
|
307
|
-
- Express simplifies developer experience.
|
|
308
|
-
- QHTTPX optimizes execution itself.
|
|
309
|
-
|
|
310
|
-
It treats HTTP as just one workload type inside a high-performance concurrency engine.
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## Roadmap
|
|
315
|
-
|
|
316
|
-
### v0.1.0
|
|
317
|
-
- Core runtime
|
|
318
|
-
- Scheduler
|
|
319
|
-
- Radix router
|
|
320
|
-
- HTTP/1.1
|
|
321
|
-
- Worker scaling
|
|
322
|
-
- Metrics
|
|
323
|
-
- Graceful shutdown
|
|
324
|
-
|
|
325
|
-
### v0.5.0
|
|
326
|
-
- HTTP/2
|
|
327
|
-
- Zero-copy serializers
|
|
328
|
-
- C++ / WASM hot path
|
|
329
|
-
- Kernel socket tuning
|
|
330
|
-
|
|
331
|
-
### v1.0.0
|
|
332
|
-
- Outperform Fastify in real benchmarks
|
|
333
|
-
- Hybrid task + HTTP engine
|
|
334
|
-
- Distributed mesh mode
|
|
335
|
-
- Quantam workflow native integration
|
|
336
|
-
|
|
337
|
-
---
|
|
338
|
-
|
|
339
|
-
## Positioning Statement
|
|
340
|
-
|
|
341
|
-
**QHTTPX**
|
|
342
|
-
A concurrency-native hybrid HTTP runtime built to extract maximum throughput from minimal hardware, designed to outperform Fastify and Express under extreme load while serving as the execution core for next-generation async systems.
|
|
179
|
+
## 📜 License
|
|
343
180
|
|
|
181
|
+
MIT © [Quantam Open Source](https://github.com/Quantam-Open-Source)
|
package/assets/logo.svg
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="200" height="200" rx="40" fill="url(#paint0_linear)"/>
|
|
3
|
+
<path d="M100 40L151.962 70V130L100 160L48.0385 130V70L100 40Z" stroke="white" stroke-width="12" stroke-linejoin="round"/>
|
|
4
|
+
<text x="100" y="125" font-family="Arial, sans-serif" font-weight="900" font-size="80" fill="white" text-anchor="middle">Q</text>
|
|
5
|
+
<defs>
|
|
6
|
+
<linearGradient id="paint0_linear" x1="0" y1="0" x2="200" y2="200" gradientUnits="userSpaceOnUse">
|
|
7
|
+
<stop stop-color="#2563EB"/>
|
|
8
|
+
<stop offset="1" stop-color="#7C3AED"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
</defs>
|
|
11
|
+
</svg>
|
package/dist/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qhttpx",
|
|
3
|
-
"version": "1.8.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
3
|
+
"version": "1.8.2",
|
|
4
|
+
"description": "The High-Performance Hybrid HTTP Runtime for Node.js. Built for extreme concurrency, request fusion, and zero-overhead scaling.",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"require": "./dist/src/index.js",
|
|
11
|
+
"default": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
6
14
|
"bin": {
|
|
7
15
|
"qhttpx": "./dist/src/cli/index.js"
|
|
8
16
|
},
|
|
@@ -16,18 +24,41 @@
|
|
|
16
24
|
"example": "npx tsx examples/api-server.ts",
|
|
17
25
|
"bench": "npm run build && node dist/src/benchmarks/simple-json.js",
|
|
18
26
|
"bench:quantam": "npm run build && node dist/src/benchmarks/quantam-users.js",
|
|
19
|
-
"bench:compare": "npm run build && node dist/src/benchmarks/compare-frameworks.js",
|
|
20
27
|
"bench:ultra": "npm run build && node dist/src/benchmarks/ultra-mode.js"
|
|
21
28
|
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
"keywords": [
|
|
30
|
+
"http",
|
|
31
|
+
"server",
|
|
32
|
+
"web",
|
|
33
|
+
"framework",
|
|
34
|
+
"typescript",
|
|
35
|
+
"fast",
|
|
36
|
+
"performance",
|
|
37
|
+
"high-performance",
|
|
38
|
+
"async",
|
|
39
|
+
"concurrency",
|
|
40
|
+
"request-fusion",
|
|
41
|
+
"coalescing",
|
|
42
|
+
"middleware",
|
|
43
|
+
"websocket",
|
|
44
|
+
"sse",
|
|
45
|
+
"rate-limit",
|
|
46
|
+
"rest",
|
|
47
|
+
"api",
|
|
48
|
+
"json",
|
|
49
|
+
"router",
|
|
50
|
+
"radix-tree",
|
|
51
|
+
"scheduler",
|
|
52
|
+
"ultra-fast",
|
|
53
|
+
"nodejs"
|
|
54
|
+
],
|
|
55
|
+
"author": "Quantam Open Source",
|
|
56
|
+
"license": "MIT",
|
|
25
57
|
"type": "commonjs",
|
|
26
58
|
"devDependencies": {
|
|
27
59
|
"@types/autocannon": "^7.12.7",
|
|
28
60
|
"@types/better-sqlite3": "^7.6.13",
|
|
29
61
|
"@types/busboy": "^1.5.4",
|
|
30
|
-
"@types/express": "^5.0.6",
|
|
31
62
|
"@types/ioredis": "^4.28.10",
|
|
32
63
|
"@types/mongodb": "^4.0.6",
|
|
33
64
|
"@types/node": "^25.0.9",
|
|
@@ -49,9 +80,7 @@
|
|
|
49
80
|
"dependencies": {
|
|
50
81
|
"better-sqlite3": "^12.6.2",
|
|
51
82
|
"busboy": "^1.6.0",
|
|
52
|
-
"express": "^5.2.1",
|
|
53
83
|
"fast-json-stringify": "^5.15.1",
|
|
54
|
-
"fastify": "^5.7.1",
|
|
55
84
|
"pino": "^10.2.0",
|
|
56
85
|
"pino-pretty": "^13.1.3",
|
|
57
86
|
"quantam-async": "^0.1.1",
|
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const autocannon_1 = __importDefault(require("autocannon"));
|
|
7
|
-
const fastify_1 = __importDefault(require("fastify"));
|
|
8
7
|
const index_1 = require("../index");
|
|
9
8
|
async function runAutocannon(name, url) {
|
|
10
9
|
const result = await (0, autocannon_1.default)({
|
|
@@ -56,19 +55,6 @@ async function startQHTTPXUltra() {
|
|
|
56
55
|
const url = `http://127.0.0.1:${port}/json`;
|
|
57
56
|
return { app, url };
|
|
58
57
|
}
|
|
59
|
-
async function startFastify() {
|
|
60
|
-
const app = (0, fastify_1.default)();
|
|
61
|
-
app.get('/json', async () => {
|
|
62
|
-
return { message: 'hello from fastify' };
|
|
63
|
-
});
|
|
64
|
-
await app.listen({ port: 0, host: '127.0.0.1' });
|
|
65
|
-
const address = app.server.address();
|
|
66
|
-
if (!address || typeof address === 'string') {
|
|
67
|
-
throw new Error('Fastify address not available');
|
|
68
|
-
}
|
|
69
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
70
|
-
return { app, url };
|
|
71
|
-
}
|
|
72
58
|
async function run() {
|
|
73
59
|
const results = [];
|
|
74
60
|
console.log('=== QHTTPX Balanced Mode ===');
|
|
@@ -89,15 +75,6 @@ async function run() {
|
|
|
89
75
|
finally {
|
|
90
76
|
await ultra.app.close();
|
|
91
77
|
}
|
|
92
|
-
console.log('\n=== Fastify Baseline ===');
|
|
93
|
-
const fast = await startFastify();
|
|
94
|
-
try {
|
|
95
|
-
const r = await runAutocannon('Fastify', fast.url);
|
|
96
|
-
results.push(r);
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
await fast.app.close();
|
|
100
|
-
}
|
|
101
78
|
console.log('\n=== Summary ===');
|
|
102
79
|
for (const r of results) {
|
|
103
80
|
console.log(`${r.name}: ${r.rps.toFixed(0)} req/sec, p99=${r.p99.toFixed(1)}ms, total=${r.total}`);
|
|
@@ -105,15 +82,10 @@ async function run() {
|
|
|
105
82
|
// Calculate improvement
|
|
106
83
|
const balanced_rps = results[0]?.rps || 0;
|
|
107
84
|
const ultra_rps = results[1]?.rps || 0;
|
|
108
|
-
const fastify_rps = results[2]?.rps || 0;
|
|
109
85
|
if (ultra_rps > 0 && balanced_rps > 0) {
|
|
110
86
|
const improvement = ((ultra_rps - balanced_rps) / balanced_rps * 100).toFixed(1);
|
|
111
87
|
console.log(`\nUltra vs Balanced improvement: ${improvement}%`);
|
|
112
88
|
}
|
|
113
|
-
if (ultra_rps > 0 && fastify_rps > 0) {
|
|
114
|
-
const ratio = (ultra_rps / fastify_rps).toFixed(2);
|
|
115
|
-
console.log(`Ultra vs Fastify ratio: ${ratio}x`);
|
|
116
|
-
}
|
|
117
89
|
process.exit(0);
|
|
118
90
|
}
|
|
119
91
|
run().catch((err) => {
|
package/dist/src/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qhttpx",
|
|
3
|
-
"version": "1.8.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "index.js",
|
|
3
|
+
"version": "1.8.2",
|
|
4
|
+
"description": "The High-Performance Hybrid HTTP Runtime for Node.js. Built for extreme concurrency, request fusion, and zero-overhead scaling.",
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/src/index.d.ts",
|
|
10
|
+
"require": "./dist/src/index.js",
|
|
11
|
+
"default": "./dist/src/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
6
14
|
"bin": {
|
|
7
15
|
"qhttpx": "./dist/src/cli/index.js"
|
|
8
16
|
},
|
|
@@ -16,18 +24,41 @@
|
|
|
16
24
|
"example": "npx tsx examples/api-server.ts",
|
|
17
25
|
"bench": "npm run build && node dist/src/benchmarks/simple-json.js",
|
|
18
26
|
"bench:quantam": "npm run build && node dist/src/benchmarks/quantam-users.js",
|
|
19
|
-
"bench:compare": "npm run build && node dist/src/benchmarks/compare-frameworks.js",
|
|
20
27
|
"bench:ultra": "npm run build && node dist/src/benchmarks/ultra-mode.js"
|
|
21
28
|
},
|
|
22
|
-
"keywords": [
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
"keywords": [
|
|
30
|
+
"http",
|
|
31
|
+
"server",
|
|
32
|
+
"web",
|
|
33
|
+
"framework",
|
|
34
|
+
"typescript",
|
|
35
|
+
"fast",
|
|
36
|
+
"performance",
|
|
37
|
+
"high-performance",
|
|
38
|
+
"async",
|
|
39
|
+
"concurrency",
|
|
40
|
+
"request-fusion",
|
|
41
|
+
"coalescing",
|
|
42
|
+
"middleware",
|
|
43
|
+
"websocket",
|
|
44
|
+
"sse",
|
|
45
|
+
"rate-limit",
|
|
46
|
+
"rest",
|
|
47
|
+
"api",
|
|
48
|
+
"json",
|
|
49
|
+
"router",
|
|
50
|
+
"radix-tree",
|
|
51
|
+
"scheduler",
|
|
52
|
+
"ultra-fast",
|
|
53
|
+
"nodejs"
|
|
54
|
+
],
|
|
55
|
+
"author": "Quantam Open Source",
|
|
56
|
+
"license": "MIT",
|
|
25
57
|
"type": "commonjs",
|
|
26
58
|
"devDependencies": {
|
|
27
59
|
"@types/autocannon": "^7.12.7",
|
|
28
60
|
"@types/better-sqlite3": "^7.6.13",
|
|
29
61
|
"@types/busboy": "^1.5.4",
|
|
30
|
-
"@types/express": "^5.0.6",
|
|
31
62
|
"@types/ioredis": "^4.28.10",
|
|
32
63
|
"@types/mongodb": "^4.0.6",
|
|
33
64
|
"@types/node": "^25.0.9",
|
|
@@ -49,9 +80,7 @@
|
|
|
49
80
|
"dependencies": {
|
|
50
81
|
"better-sqlite3": "^12.6.2",
|
|
51
82
|
"busboy": "^1.6.0",
|
|
52
|
-
"express": "^5.2.1",
|
|
53
83
|
"fast-json-stringify": "^5.15.1",
|
|
54
|
-
"fastify": "^5.7.1",
|
|
55
84
|
"pino": "^10.2.0",
|
|
56
85
|
"pino-pretty": "^13.1.3",
|
|
57
86
|
"quantam-async": "^0.1.1",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import autocannon from 'autocannon';
|
|
2
|
-
import fastify from 'fastify';
|
|
3
2
|
import { QHTTPX } from '../index';
|
|
4
3
|
|
|
5
4
|
type BenchResult = {
|
|
@@ -80,22 +79,6 @@ async function startQHTTPXUltra() {
|
|
|
80
79
|
return { app, url };
|
|
81
80
|
}
|
|
82
81
|
|
|
83
|
-
async function startFastify() {
|
|
84
|
-
const app = fastify();
|
|
85
|
-
|
|
86
|
-
app.get('/json', async () => {
|
|
87
|
-
return { message: 'hello from fastify' };
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
await app.listen({ port: 0, host: '127.0.0.1' });
|
|
91
|
-
const address = app.server.address();
|
|
92
|
-
if (!address || typeof address === 'string') {
|
|
93
|
-
throw new Error('Fastify address not available');
|
|
94
|
-
}
|
|
95
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
96
|
-
return { app, url };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
82
|
async function run() {
|
|
100
83
|
const results: BenchResult[] = [];
|
|
101
84
|
|
|
@@ -117,15 +100,6 @@ async function run() {
|
|
|
117
100
|
await ultra.app.close();
|
|
118
101
|
}
|
|
119
102
|
|
|
120
|
-
console.log('\n=== Fastify Baseline ===');
|
|
121
|
-
const fast = await startFastify();
|
|
122
|
-
try {
|
|
123
|
-
const r = await runAutocannon('Fastify', fast.url);
|
|
124
|
-
results.push(r);
|
|
125
|
-
} finally {
|
|
126
|
-
await fast.app.close();
|
|
127
|
-
}
|
|
128
|
-
|
|
129
103
|
console.log('\n=== Summary ===');
|
|
130
104
|
for (const r of results) {
|
|
131
105
|
console.log(
|
|
@@ -138,17 +112,11 @@ async function run() {
|
|
|
138
112
|
// Calculate improvement
|
|
139
113
|
const balanced_rps = results[0]?.rps || 0;
|
|
140
114
|
const ultra_rps = results[1]?.rps || 0;
|
|
141
|
-
const fastify_rps = results[2]?.rps || 0;
|
|
142
115
|
|
|
143
116
|
if (ultra_rps > 0 && balanced_rps > 0) {
|
|
144
117
|
const improvement = ((ultra_rps - balanced_rps) / balanced_rps * 100).toFixed(1);
|
|
145
118
|
console.log(`\nUltra vs Balanced improvement: ${improvement}%`);
|
|
146
119
|
}
|
|
147
|
-
|
|
148
|
-
if (ultra_rps > 0 && fastify_rps > 0) {
|
|
149
|
-
const ratio = (ultra_rps / fastify_rps).toFixed(2);
|
|
150
|
-
console.log(`Ultra vs Fastify ratio: ${ratio}x`);
|
|
151
|
-
}
|
|
152
120
|
|
|
153
121
|
process.exit(0);
|
|
154
122
|
}
|
package/src/index.ts
CHANGED
|
@@ -40,3 +40,11 @@ export function createHttpApp(options: QHTTPXOptions = {}): QHTTPX {
|
|
|
40
40
|
}
|
|
41
41
|
return app;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default export for simplified usage
|
|
46
|
+
* @example
|
|
47
|
+
* import QHTTPX from 'qhttpx';
|
|
48
|
+
* const app = QHTTPX();
|
|
49
|
+
*/
|
|
50
|
+
export default createHttpApp;
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const autocannon_1 = __importDefault(require("autocannon"));
|
|
7
|
-
const fastify_1 = __importDefault(require("fastify"));
|
|
8
|
-
const express_1 = __importDefault(require("express"));
|
|
9
|
-
const index_1 = require("../index");
|
|
10
|
-
async function runAutocannon(name, url) {
|
|
11
|
-
const result = await (0, autocannon_1.default)({
|
|
12
|
-
url,
|
|
13
|
-
connections: 200,
|
|
14
|
-
pipelining: 10,
|
|
15
|
-
duration: 10,
|
|
16
|
-
});
|
|
17
|
-
const total = result.requests.total;
|
|
18
|
-
const sent = result.requests.sent;
|
|
19
|
-
const rps = result.requests.average;
|
|
20
|
-
const p99 = result.latency.p99;
|
|
21
|
-
console.log(`${name} bench: total=${total} (sent=${sent}) req, ` +
|
|
22
|
-
`${rps.toFixed(0)} req/sec, p99=${p99.toFixed(1)}ms, connections=${result.connections}, pipelining=${result.pipelining}`);
|
|
23
|
-
return {
|
|
24
|
-
name,
|
|
25
|
-
total,
|
|
26
|
-
sent,
|
|
27
|
-
rps,
|
|
28
|
-
p99,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
async function startQHTTPX() {
|
|
32
|
-
const payloadBuffer = Buffer.from(JSON.stringify({ message: 'hello from qhttpx' }));
|
|
33
|
-
const app = new index_1.QHTTPX({
|
|
34
|
-
maxConcurrency: 1024,
|
|
35
|
-
metricsEnabled: false,
|
|
36
|
-
jsonSerializer: () => payloadBuffer,
|
|
37
|
-
});
|
|
38
|
-
app.get('/json', (ctx) => {
|
|
39
|
-
ctx.json({ message: 'hello from qhttpx' });
|
|
40
|
-
});
|
|
41
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
42
|
-
const url = `http://127.0.0.1:${port}/json`;
|
|
43
|
-
return { app, url };
|
|
44
|
-
}
|
|
45
|
-
async function startFastify() {
|
|
46
|
-
const app = (0, fastify_1.default)();
|
|
47
|
-
app.get('/json', async () => {
|
|
48
|
-
return { message: 'hello from fastify' };
|
|
49
|
-
});
|
|
50
|
-
await app.listen({ port: 0, host: '127.0.0.1' });
|
|
51
|
-
const address = app.server.address();
|
|
52
|
-
if (!address || typeof address === 'string') {
|
|
53
|
-
throw new Error('Fastify address not available');
|
|
54
|
-
}
|
|
55
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
56
|
-
return { app, url };
|
|
57
|
-
}
|
|
58
|
-
async function startExpress() {
|
|
59
|
-
const app = (0, express_1.default)();
|
|
60
|
-
app.get('/json', (_req, res) => {
|
|
61
|
-
res.json({ message: 'hello from express' });
|
|
62
|
-
});
|
|
63
|
-
const server = await new Promise((resolve) => {
|
|
64
|
-
const s = app.listen(0, '127.0.0.1', () => {
|
|
65
|
-
resolve(s);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
const address = server.address();
|
|
69
|
-
if (!address || typeof address === 'string') {
|
|
70
|
-
throw new Error('Express address not available');
|
|
71
|
-
}
|
|
72
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
73
|
-
return { app, url, server };
|
|
74
|
-
}
|
|
75
|
-
async function run() {
|
|
76
|
-
const results = [];
|
|
77
|
-
const qhttpx = await startQHTTPX();
|
|
78
|
-
try {
|
|
79
|
-
const r = await runAutocannon('QHTTPX', qhttpx.url);
|
|
80
|
-
results.push(r);
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
await qhttpx.app.close();
|
|
84
|
-
}
|
|
85
|
-
const fast = await startFastify();
|
|
86
|
-
try {
|
|
87
|
-
const r = await runAutocannon('Fastify', fast.url);
|
|
88
|
-
results.push(r);
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
await fast.app.close();
|
|
92
|
-
}
|
|
93
|
-
const exp = await startExpress();
|
|
94
|
-
try {
|
|
95
|
-
const r = await runAutocannon('Express', exp.url);
|
|
96
|
-
results.push(r);
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
await new Promise((resolve, reject) => {
|
|
100
|
-
exp.server.close((err) => {
|
|
101
|
-
if (err) {
|
|
102
|
-
reject(err);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
resolve();
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
console.log('\nSummary:');
|
|
111
|
-
for (const r of results) {
|
|
112
|
-
console.log(`${r.name}: ${r.rps.toFixed(0)} req/sec, p99=${r.p99.toFixed(1)}ms, total=${r.total}`);
|
|
113
|
-
}
|
|
114
|
-
process.exit(0);
|
|
115
|
-
}
|
|
116
|
-
run().catch((err) => {
|
|
117
|
-
console.error(err);
|
|
118
|
-
process.exit(1);
|
|
119
|
-
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import autocannon from 'autocannon';
|
|
2
|
-
import fastify from 'fastify';
|
|
3
|
-
import express, { Request, Response } from 'express';
|
|
4
|
-
import { QHTTPX } from '../index';
|
|
5
|
-
|
|
6
|
-
type BenchResult = {
|
|
7
|
-
name: string;
|
|
8
|
-
total: number;
|
|
9
|
-
sent: number;
|
|
10
|
-
rps: number;
|
|
11
|
-
p99: number;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
async function runAutocannon(name: string, url: string): Promise<BenchResult> {
|
|
15
|
-
const result = await autocannon({
|
|
16
|
-
url,
|
|
17
|
-
connections: 200,
|
|
18
|
-
pipelining: 10,
|
|
19
|
-
duration: 10,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const total = result.requests.total;
|
|
23
|
-
const sent = result.requests.sent;
|
|
24
|
-
const rps = result.requests.average;
|
|
25
|
-
const p99 = result.latency.p99;
|
|
26
|
-
|
|
27
|
-
console.log(
|
|
28
|
-
`${name} bench: total=${total} (sent=${sent}) req, ` +
|
|
29
|
-
`${rps.toFixed(0)} req/sec, p99=${p99.toFixed(
|
|
30
|
-
1,
|
|
31
|
-
)}ms, connections=${result.connections}, pipelining=${
|
|
32
|
-
result.pipelining
|
|
33
|
-
}`,
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
name,
|
|
38
|
-
total,
|
|
39
|
-
sent,
|
|
40
|
-
rps,
|
|
41
|
-
p99,
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function startQHTTPX() {
|
|
46
|
-
const payloadBuffer = Buffer.from(
|
|
47
|
-
JSON.stringify({ message: 'hello from qhttpx' }),
|
|
48
|
-
);
|
|
49
|
-
const app = new QHTTPX({
|
|
50
|
-
maxConcurrency: 1024,
|
|
51
|
-
metricsEnabled: false,
|
|
52
|
-
jsonSerializer: () => payloadBuffer,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
app.get('/json', (ctx) => {
|
|
56
|
-
ctx.json({ message: 'hello from qhttpx' });
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
60
|
-
const url = `http://127.0.0.1:${port}/json`;
|
|
61
|
-
return { app, url };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function startFastify() {
|
|
65
|
-
const app = fastify();
|
|
66
|
-
|
|
67
|
-
app.get('/json', async () => {
|
|
68
|
-
return { message: 'hello from fastify' };
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
await app.listen({ port: 0, host: '127.0.0.1' });
|
|
72
|
-
const address = app.server.address();
|
|
73
|
-
if (!address || typeof address === 'string') {
|
|
74
|
-
throw new Error('Fastify address not available');
|
|
75
|
-
}
|
|
76
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
77
|
-
return { app, url };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async function startExpress() {
|
|
81
|
-
const app = express();
|
|
82
|
-
app.get('/json', (_req: Request, res: Response) => {
|
|
83
|
-
res.json({ message: 'hello from express' });
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const server = await new Promise<import('http').Server>((resolve) => {
|
|
87
|
-
const s = app.listen(0, '127.0.0.1', () => {
|
|
88
|
-
resolve(s);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
const address = server.address();
|
|
92
|
-
if (!address || typeof address === 'string') {
|
|
93
|
-
throw new Error('Express address not available');
|
|
94
|
-
}
|
|
95
|
-
const url = `http://127.0.0.1:${address.port}/json`;
|
|
96
|
-
return { app, url, server };
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function run() {
|
|
100
|
-
const results: BenchResult[] = [];
|
|
101
|
-
|
|
102
|
-
const qhttpx = await startQHTTPX();
|
|
103
|
-
try {
|
|
104
|
-
const r = await runAutocannon('QHTTPX', qhttpx.url);
|
|
105
|
-
results.push(r);
|
|
106
|
-
} finally {
|
|
107
|
-
await qhttpx.app.close();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const fast = await startFastify();
|
|
111
|
-
try {
|
|
112
|
-
const r = await runAutocannon('Fastify', fast.url);
|
|
113
|
-
results.push(r);
|
|
114
|
-
} finally {
|
|
115
|
-
await fast.app.close();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const exp = await startExpress();
|
|
119
|
-
try {
|
|
120
|
-
const r = await runAutocannon('Express', exp.url);
|
|
121
|
-
results.push(r);
|
|
122
|
-
} finally {
|
|
123
|
-
await new Promise<void>((resolve, reject) => {
|
|
124
|
-
exp.server.close((err: unknown) => {
|
|
125
|
-
if (err) {
|
|
126
|
-
reject(err);
|
|
127
|
-
} else {
|
|
128
|
-
resolve();
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
console.log('\nSummary:');
|
|
135
|
-
for (const r of results) {
|
|
136
|
-
console.log(
|
|
137
|
-
`${r.name}: ${r.rps.toFixed(0)} req/sec, p99=${r.p99.toFixed(
|
|
138
|
-
1,
|
|
139
|
-
)}ms, total=${r.total}`,
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
process.exit(0);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
run().catch((err) => {
|
|
147
|
-
console.error(err);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
});
|