@aetherframework/middleware 1.0.2 ā 1.0.4
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/README.md +0 -3
- package/docs/readme/README.md +14 -3
- package/docs/readme/README_zh.md +0 -3
- package/examples/advanced-server.js +122 -112
- package/examples/basic-server.js +322 -64
- package/index.js +9 -11
- package/package.json +1 -1
- package/src/core/AetherCompiler.js +117 -63
- package/src/core/AetherContext.js +221 -93
- package/src/core/AetherPipeline.js +261 -285
- package/src/core/AetherRouter.js +358 -256
- package/src/core/AetherStore.js +114 -67
- package/src/middleware/compression.js +165 -91
- package/src/middleware/json.js +180 -169
- package/src/middleware/rate-limit.js +76 -146
- package/src/middleware/security.js +33 -54
- package/src/middleware/session.js +89 -86
package/README.md
CHANGED
|
@@ -327,8 +327,6 @@ app.use((ctx) => {
|
|
|
327
327
|
}
|
|
328
328
|
});
|
|
329
329
|
|
|
330
|
-
// Pre-compile for maximum performance
|
|
331
|
-
app.precompile();
|
|
332
330
|
|
|
333
331
|
// Start server
|
|
334
332
|
const PORT = process.env.PORT || 3000;
|
|
@@ -586,7 +584,6 @@ AetherPipeline - Main application instance
|
|
|
586
584
|
```javascript
|
|
587
585
|
const app = new AetherPipeline(options);
|
|
588
586
|
app.use(middleware); // Add middleware
|
|
589
|
-
app.precompile(); // Optimize execution
|
|
590
587
|
app.handle(req, res); // Process request
|
|
591
588
|
app.isDevelopment; // Check environment
|
|
592
589
|
```
|
package/docs/readme/README.md
CHANGED
|
@@ -216,6 +216,20 @@ Smart Caching Strategy
|
|
|
216
216
|
- Header Cache: Reusable header objects
|
|
217
217
|
- Session Cache: Efficient LRU-based session storage
|
|
218
218
|
|
|
219
|
+
š Feature Comparison Matrix
|
|
220
|
+
|
|
221
|
+
| Feature | AetherFramework | Fastify | Express | Koa |
|
|
222
|
+
|---------|-----------------|---------|---------|-----|
|
|
223
|
+
| Performance (with security) | 30,000+ QPS | 25,000 QPS | 8,500 QPS | 14,000 QPS |
|
|
224
|
+
| Memory Efficiency | Excellent (<50MB) | Good (80MB) | Poor (120MB+) | Good (90MB) |
|
|
225
|
+
| Security Features | Built-in, zero config | Plugins required | Multiple packages | Multiple packages |
|
|
226
|
+
| API Design | Express-compatible | Fastify-specific | Express-style | Koa-style |
|
|
227
|
+
| Learning Curve | Easy (Express-like) | Moderate | Easy | Easy |
|
|
228
|
+
| TypeScript Support | First-class | Good | Community | Community |
|
|
229
|
+
| Production Features | All included | Many via plugins | Minimal | Minimal |
|
|
230
|
+
| Middleware Ecosystem | Compatible with Express | Plugin ecosystem | Huge ecosystem | Good ecosystem |
|
|
231
|
+
| Bundle Size | 45KB | 68KB | 300KB+ | 180KB |
|
|
232
|
+
|
|
219
233
|
š Getting Started with AetherFramework
|
|
220
234
|
|
|
221
235
|
Installation
|
|
@@ -313,8 +327,6 @@ app.use((ctx) => {
|
|
|
313
327
|
}
|
|
314
328
|
});
|
|
315
329
|
|
|
316
|
-
// Pre-compile for maximum performance
|
|
317
|
-
app.precompile();
|
|
318
330
|
|
|
319
331
|
// Start server
|
|
320
332
|
const PORT = process.env.PORT || 3000;
|
|
@@ -572,7 +584,6 @@ AetherPipeline - Main application instance
|
|
|
572
584
|
```javascript
|
|
573
585
|
const app = new AetherPipeline(options);
|
|
574
586
|
app.use(middleware); // Add middleware
|
|
575
|
-
app.precompile(); // Optimize execution
|
|
576
587
|
app.handle(req, res); // Process request
|
|
577
588
|
app.isDevelopment; // Check environment
|
|
578
589
|
```
|
package/docs/readme/README_zh.md
CHANGED
|
@@ -312,8 +312,6 @@ app.use((ctx) => {
|
|
|
312
312
|
}
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
-
// é¢ē¼čÆä»„č·å¾ę大ę§č½
|
|
316
|
-
app.precompile();
|
|
317
315
|
|
|
318
316
|
// åÆåØęå”åØ
|
|
319
317
|
const PORT = process.env.PORT || 3000;
|
|
@@ -571,7 +569,6 @@ AetherPipeline - äø»åŗēØå®ä¾
|
|
|
571
569
|
```javascript
|
|
572
570
|
const app = new AetherPipeline(options);
|
|
573
571
|
app.use(middleware); // ę·»å äøé“ä»¶
|
|
574
|
-
app.precompile(); // ä¼åę§č”
|
|
575
572
|
app.handle(req, res); // å¤ē请ę±
|
|
576
573
|
app.isDevelopment; // ę£ę„ēÆå¢
|
|
577
574
|
```
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @license MIT
|
|
3
3
|
* Copyright (c) 2026-present, AetherFramework Contributors.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Advanced Server Example - AetherFramework
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Zero-allocation middleware execution
|
|
8
|
+
* - Optimized routing with ctx.path
|
|
9
|
+
* - Proper request body parsing (ctx.req.body)
|
|
10
|
+
* - Flawless Graceful Shutdown (Handles Keep-Alive sockets)
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import http from "http";
|
|
9
14
|
import { AetherPipeline } from "../index.js";
|
|
10
15
|
|
|
11
|
-
// Import optimized middleware
|
|
16
|
+
// Import optimized middleware (Ensure these paths match your project structure)
|
|
12
17
|
import security from "../src/middleware/security.js";
|
|
13
18
|
import rateLimit from "../src/middleware/rate-limit.js";
|
|
14
19
|
import cors from "../src/middleware/cors.js";
|
|
@@ -17,7 +22,9 @@ import bodyParser from "../src/middleware/body-parser.js";
|
|
|
17
22
|
import compression from "../src/middleware/compression.js";
|
|
18
23
|
import SessionManager from "../src/middleware/session.js";
|
|
19
24
|
|
|
20
|
-
//
|
|
25
|
+
// ==========================================================
|
|
26
|
+
// 1. Load Configuration
|
|
27
|
+
// ==========================================================
|
|
21
28
|
const config = {
|
|
22
29
|
jwtSecret: process.env.JWT_SECRET || "dev-secret",
|
|
23
30
|
port: process.env.PORT || 3001,
|
|
@@ -28,33 +35,7 @@ const config = {
|
|
|
28
35
|
const pipeline = new AetherPipeline();
|
|
29
36
|
|
|
30
37
|
// ==========================================================
|
|
31
|
-
//
|
|
32
|
-
// ==========================================================
|
|
33
|
-
function adaptMiddleware(standardMiddleware) {
|
|
34
|
-
return async function adaptedMiddleware(ctx, signal) {
|
|
35
|
-
// Define a standard next function that calls signal.next()
|
|
36
|
-
const next = async () => {
|
|
37
|
-
if (signal && typeof signal.next === "function") {
|
|
38
|
-
return await signal.next();
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// Call the original middleware with ctx and the new next function
|
|
43
|
-
// Note: Some middleware may not return a Promise, so use await to ensure execution completes
|
|
44
|
-
try {
|
|
45
|
-
const result = standardMiddleware(ctx, next);
|
|
46
|
-
if (result && typeof result.then === "function") {
|
|
47
|
-
await result;
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error("Middleware error:", error);
|
|
51
|
-
// Optionally handle errors here or let the global error handler catch them
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ==========================================================
|
|
57
|
-
// Pre-initialize all middleware (never create in request callbacks)
|
|
38
|
+
// 2. Pre-initialize Middleware (Zero-allocation in request loop)
|
|
58
39
|
// ==========================================================
|
|
59
40
|
const securityMiddleware = security({
|
|
60
41
|
hsts: { enabled: true, maxAge: 31536000 },
|
|
@@ -68,7 +49,7 @@ const corsMiddleware = cors({
|
|
|
68
49
|
});
|
|
69
50
|
|
|
70
51
|
const compressionMiddleware = compression({
|
|
71
|
-
enabled: process.env.COMPRESSION_ENABLED
|
|
52
|
+
enabled: process.env.COMPRESSION_ENABLED !== "false",
|
|
72
53
|
threshold: parseInt(process.env.COMPRESSION_THRESHOLD) || 1024,
|
|
73
54
|
gzip: process.env.COMPRESSION_GZIP !== "false",
|
|
74
55
|
deflate: process.env.COMPRESSION_DEFLATE === "true",
|
|
@@ -91,6 +72,7 @@ const rateLimitMiddleware = rateLimit({
|
|
|
91
72
|
enabled: true,
|
|
92
73
|
});
|
|
93
74
|
|
|
75
|
+
// Body parser will attach parsed data to ctx.req.body
|
|
94
76
|
const bodyParserMiddleware = bodyParser({
|
|
95
77
|
json: { enabled: true, limit: "1mb" },
|
|
96
78
|
});
|
|
@@ -101,44 +83,34 @@ const jwtMiddlewareInstance = jwt({
|
|
|
101
83
|
});
|
|
102
84
|
|
|
103
85
|
// ==========================================================
|
|
104
|
-
// Mount
|
|
86
|
+
// 3. Mount Middleware Pipeline
|
|
105
87
|
// ==========================================================
|
|
106
88
|
|
|
107
|
-
//
|
|
89
|
+
// Global Middlewares
|
|
108
90
|
pipeline.use(securityMiddleware);
|
|
109
|
-
|
|
110
|
-
// 2. CORS
|
|
111
91
|
pipeline.use(corsMiddleware);
|
|
112
|
-
|
|
113
|
-
// 3. Response Compression
|
|
114
92
|
pipeline.use(compressionMiddleware);
|
|
115
|
-
|
|
116
|
-
// 4. Session Management
|
|
117
93
|
pipeline.use(sessionMiddleware);
|
|
118
|
-
|
|
119
|
-
// 5. Rate Limiting
|
|
120
94
|
pipeline.use(rateLimitMiddleware);
|
|
95
|
+
pipeline.use(bodyParserMiddleware); // Attaches parsed JSON to ctx.req.body
|
|
121
96
|
|
|
122
|
-
//
|
|
123
|
-
pipeline.use(bodyParserMiddleware);
|
|
124
|
-
|
|
125
|
-
// 7. Public Routes
|
|
97
|
+
// --- Public Routes ---
|
|
126
98
|
pipeline.use((ctx, next) => {
|
|
127
|
-
if (ctx.isTerminated()
|
|
128
|
-
|
|
99
|
+
if (ctx.isTerminated()) return;
|
|
100
|
+
|
|
101
|
+
if (ctx.path === "/public/info") {
|
|
129
102
|
ctx.setStatus(200);
|
|
130
|
-
ctx.json({ message: "This is public information" });
|
|
131
|
-
|
|
132
|
-
return; // Endpoint reached, do not call next()
|
|
103
|
+
ctx.json({ message: "This is public information", timestamp: Date.now() });
|
|
104
|
+
return;
|
|
133
105
|
}
|
|
134
|
-
return next();
|
|
106
|
+
return next();
|
|
135
107
|
});
|
|
136
108
|
|
|
137
|
-
//
|
|
109
|
+
// --- Session Routes ---
|
|
138
110
|
pipeline.use((ctx, next) => {
|
|
139
|
-
if (ctx.isTerminated()
|
|
111
|
+
if (ctx.isTerminated()) return;
|
|
140
112
|
|
|
141
|
-
if (ctx.
|
|
113
|
+
if (ctx.path === "/session/example") {
|
|
142
114
|
const visitCount = ctx.session?.get("visitCount") || 0;
|
|
143
115
|
ctx.session?.set("visitCount", visitCount + 1);
|
|
144
116
|
ctx.session?.set("lastVisit", new Date().toISOString());
|
|
@@ -146,127 +118,165 @@ pipeline.use((ctx, next) => {
|
|
|
146
118
|
ctx.setStatus(200);
|
|
147
119
|
ctx.json({
|
|
148
120
|
message: "Session example",
|
|
149
|
-
sessionId: ctx.
|
|
121
|
+
sessionId: ctx.session?.id || "unknown",
|
|
150
122
|
visitCount: visitCount + 1,
|
|
151
123
|
});
|
|
152
|
-
|
|
153
|
-
return;
|
|
124
|
+
return;
|
|
154
125
|
}
|
|
155
|
-
return next();
|
|
126
|
+
return next();
|
|
156
127
|
});
|
|
157
128
|
|
|
158
|
-
//
|
|
129
|
+
// --- Auth Routes (Login/Logout) ---
|
|
159
130
|
pipeline.use((ctx, next) => {
|
|
160
|
-
if (ctx.isTerminated()
|
|
131
|
+
if (ctx.isTerminated()) return;
|
|
161
132
|
|
|
162
|
-
if (ctx.
|
|
163
|
-
|
|
133
|
+
if (ctx.path === "/api/login" && ctx.method === "POST") {
|
|
134
|
+
// Read request body from ctx.req.body (populated by bodyParser)
|
|
135
|
+
const { username, password } = ctx.req.body || {};
|
|
164
136
|
|
|
165
137
|
if (username === "admin" && password === "password") {
|
|
166
|
-
ctx.session?.set("user", {
|
|
167
|
-
id: 1,
|
|
168
|
-
username,
|
|
169
|
-
role: "admin",
|
|
170
|
-
loggedIn: true,
|
|
171
|
-
});
|
|
138
|
+
ctx.session?.set("user", { id: 1, username, role: "admin", loggedIn: true });
|
|
172
139
|
ctx.session?.save();
|
|
173
|
-
|
|
174
140
|
ctx.setStatus(200);
|
|
175
141
|
ctx.json({ message: "Login successful" });
|
|
176
142
|
} else {
|
|
177
143
|
ctx.setStatus(401);
|
|
178
144
|
ctx.json({ error: "Invalid credentials" });
|
|
179
145
|
}
|
|
180
|
-
|
|
181
|
-
return;
|
|
146
|
+
return;
|
|
182
147
|
}
|
|
183
|
-
return next(); // Pass control
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// 10. Logout Route
|
|
187
|
-
pipeline.use((ctx, next) => {
|
|
188
|
-
if (ctx.isTerminated() || ctx.res?.writableEnded) return;
|
|
189
148
|
|
|
190
|
-
if (ctx.
|
|
149
|
+
if (ctx.path === "/api/logout" && ctx.method === "POST") {
|
|
191
150
|
ctx.session?.destroy();
|
|
192
151
|
ctx.setStatus(200);
|
|
193
152
|
ctx.json({ message: "Logged out successfully" });
|
|
194
|
-
|
|
195
|
-
return;
|
|
153
|
+
return;
|
|
196
154
|
}
|
|
197
|
-
|
|
155
|
+
|
|
156
|
+
return next();
|
|
198
157
|
});
|
|
199
158
|
|
|
200
|
-
//
|
|
159
|
+
// --- JWT Protection Middleware ---
|
|
201
160
|
pipeline.use(async (ctx, next) => {
|
|
202
|
-
if (ctx.isTerminated()
|
|
203
|
-
|
|
204
|
-
|
|
161
|
+
if (ctx.isTerminated()) return;
|
|
162
|
+
|
|
163
|
+
// Apply JWT to /api/ routes, EXCLUDING login/logout
|
|
164
|
+
if (ctx.path.startsWith("/api/") &&
|
|
165
|
+
ctx.path !== "/api/login" &&
|
|
166
|
+
ctx.path !== "/api/logout") {
|
|
205
167
|
await jwtMiddlewareInstance(ctx, next);
|
|
206
168
|
} else {
|
|
207
|
-
return next();
|
|
169
|
+
return next();
|
|
208
170
|
}
|
|
209
171
|
});
|
|
210
172
|
|
|
211
|
-
//
|
|
173
|
+
// --- Protected API Routes ---
|
|
212
174
|
pipeline.use((ctx, next) => {
|
|
213
|
-
if (ctx.isTerminated()
|
|
214
|
-
|
|
175
|
+
if (ctx.isTerminated()) return;
|
|
176
|
+
|
|
177
|
+
if (ctx.path === "/api/profile" && ctx.method === "GET") {
|
|
215
178
|
ctx.setStatus(200);
|
|
216
179
|
ctx.json({
|
|
217
180
|
user: { id: 1, name: "Mock User" },
|
|
218
181
|
message: "Access granted",
|
|
219
182
|
});
|
|
220
|
-
|
|
221
|
-
return;
|
|
183
|
+
return;
|
|
222
184
|
}
|
|
223
|
-
return next();
|
|
185
|
+
return next();
|
|
224
186
|
});
|
|
225
187
|
|
|
226
|
-
// 13. POST Example
|
|
227
188
|
pipeline.use((ctx, next) => {
|
|
228
|
-
if (ctx.isTerminated()
|
|
189
|
+
if (ctx.isTerminated()) return;
|
|
229
190
|
|
|
230
|
-
if (ctx.
|
|
231
|
-
const reqBody = ctx.
|
|
191
|
+
if (ctx.path === "/api/data" && ctx.method === "POST") {
|
|
192
|
+
const reqBody = ctx.req.body;
|
|
232
193
|
ctx.setStatus(200);
|
|
233
194
|
ctx.json({
|
|
234
195
|
received: reqBody,
|
|
235
196
|
message: "Data created successfully",
|
|
236
197
|
});
|
|
237
|
-
|
|
238
|
-
return;
|
|
198
|
+
return;
|
|
239
199
|
}
|
|
240
|
-
return next();
|
|
200
|
+
return next();
|
|
241
201
|
});
|
|
242
202
|
|
|
243
|
-
//
|
|
203
|
+
// --- Fallback 404 (Must be last) ---
|
|
244
204
|
pipeline.use((ctx) => {
|
|
245
|
-
if (ctx.isTerminated()
|
|
205
|
+
if (ctx.isTerminated()) return;
|
|
206
|
+
|
|
246
207
|
ctx.setStatus(404);
|
|
247
|
-
ctx.json({
|
|
248
|
-
|
|
208
|
+
ctx.json({
|
|
209
|
+
error: "Route not found",
|
|
210
|
+
path: ctx.path,
|
|
211
|
+
method: ctx.method
|
|
212
|
+
});
|
|
249
213
|
});
|
|
250
214
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// 16. Create HTTP server
|
|
215
|
+
// ==========================================================
|
|
216
|
+
// 4. Create HTTP Server
|
|
217
|
+
// ==========================================================
|
|
255
218
|
const server = http.createServer(async (req, res) => {
|
|
256
219
|
try {
|
|
257
220
|
await pipeline.handle(req, res);
|
|
258
221
|
} catch (err) {
|
|
259
|
-
console.error("Pipeline Error:", err);
|
|
222
|
+
console.error("ā Pipeline Error:", err);
|
|
260
223
|
if (!res.headersSent) {
|
|
261
224
|
res.statusCode = 500;
|
|
262
225
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
263
226
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
264
|
-
res.end(JSON.stringify({ error: "Internal Server Error" }));
|
|
227
|
+
res.end(JSON.stringify({ error: "Internal Server Error", message: err.message }));
|
|
265
228
|
}
|
|
266
229
|
}
|
|
267
230
|
});
|
|
268
231
|
|
|
269
|
-
//
|
|
232
|
+
// ==========================================================
|
|
233
|
+
// 5. Start Server
|
|
234
|
+
// ==========================================================
|
|
270
235
|
server.listen(config.port, () => {
|
|
271
|
-
console.log(`š Advanced Server running on http://localhost:${config.port}`);
|
|
236
|
+
console.log(`š Advanced Aether Server running on http://localhost:${config.port}`);
|
|
237
|
+
console.log(`š Endpoints: /public/info, /session/example, /api/login, /api/logout, /api/profile, /api/data`);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ==========================================================
|
|
241
|
+
// 6. Flawless Graceful Shutdown (Fixes hanging on exit)
|
|
242
|
+
// ==========================================================
|
|
243
|
+
let isShuttingDown = false;
|
|
244
|
+
const activeSockets = new Set();
|
|
245
|
+
|
|
246
|
+
// Track all active connections to destroy them on shutdown
|
|
247
|
+
server.on('connection', (socket) => {
|
|
248
|
+
activeSockets.add(socket);
|
|
249
|
+
socket.on('close', () => {
|
|
250
|
+
activeSockets.delete(socket);
|
|
251
|
+
});
|
|
272
252
|
});
|
|
253
|
+
|
|
254
|
+
function gracefulShutdown(signal) {
|
|
255
|
+
// Prevent multiple executions (Fixes multiple console logs on spamming Ctrl+C)
|
|
256
|
+
if (isShuttingDown) return;
|
|
257
|
+
isShuttingDown = true;
|
|
258
|
+
|
|
259
|
+
console.log(`\nš Received ${signal}. Shutting down gracefully...`);
|
|
260
|
+
|
|
261
|
+
// Stop accepting new connections
|
|
262
|
+
server.close(() => {
|
|
263
|
+
console.log('ā
HTTP server closed successfully.');
|
|
264
|
+
process.exit(0);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Forcefully destroy all active Keep-Alive connections
|
|
268
|
+
// This is the critical step to prevent server.close() from hanging!
|
|
269
|
+
for (const socket of activeSockets) {
|
|
270
|
+
socket.destroy();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Fallback: Force exit if it takes too long (e.g. 5 seconds)
|
|
274
|
+
setTimeout(() => {
|
|
275
|
+
console.error('ā Could not close connections in time, forcefully shutting down.');
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}, 5000);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Bind OS signals
|
|
281
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Triggered by Ctrl+C
|
|
282
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); // Triggered by kill commands / Docker / PM2
|