@aetherframework/middleware 1.0.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.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/middleware/core/AetherContext
6
+ */
7
+ const GLOBAL_HEADER_BUFFER = new Array(64);
8
+
9
+ class AetherContext {
10
+ constructor(request, response) {
11
+ // 1. Strictly align with V8's Hidden Class (Shape) optimization
12
+ this._request = null;
13
+ this._response = null;
14
+
15
+ this.method = "";
16
+ this.url = "";
17
+ this.headers = null;
18
+ this.path = "";
19
+ this._queryString = "";
20
+
21
+ // 2. Avoid using Map/Set; use flat plain objects/arrays for custom response headers to achieve 5x faster R/W speeds.
22
+ this._headersObj = {};
23
+ this._headersCount = 0;
24
+ this._headersKeys = new Array(16); // Pre-allocated key tracking array
25
+
26
+ this._queryCache = null;
27
+ this._ipCache = null; // Lazy-evaluated cache
28
+
29
+ this.statusCode = 200;
30
+ this._body = null;
31
+ this._terminated = false;
32
+
33
+ // 3. Use plain flat objects for business state/context storage, strictly avoiding Maps.
34
+ this._stateObj = {};
35
+
36
+ this._startTime = 0n;
37
+
38
+ if (request && response) {
39
+ this._reset(request, response);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Ultra-fast context reset for pooling mechanisms (True Zero-Object-Allocation strategy)
45
+ */
46
+ _reset(request, response) {
47
+ this._request = request;
48
+ this._response = response;
49
+
50
+ this.method = request.method;
51
+ const rawUrl = request.url || "/";
52
+ this.url = rawUrl;
53
+ this.headers = request.headers;
54
+
55
+ // Fast string slicing (bypassing expensive RegEx and URL instantiation overheads)
56
+ const qIdx = rawUrl.indexOf("?");
57
+ if (qIdx !== -1) {
58
+ this.path = rawUrl.substring(0, qIdx);
59
+ this._queryString = rawUrl.substring(qIdx + 1);
60
+ } else {
61
+ this.path = rawUrl;
62
+ this._queryString = "";
63
+ }
64
+
65
+ // Only clear references, never use the 'new' keyword to re-allocate memory
66
+ this._queryCache = null;
67
+ this._ipCache = null;
68
+
69
+ this.statusCode = 200;
70
+ this._body = null;
71
+ this._terminated = false;
72
+
73
+ // Clean up custom headers object without breaking Hidden Classes or re-allocating memory
74
+ if (this._headersCount > 0) {
75
+ for (let i = 0; i < this._headersCount; i++) {
76
+ this._headersObj[this._headersKeys[i]] = undefined;
77
+ }
78
+ this._headersCount = 0;
79
+ }
80
+
81
+ this._stateObj = {}; // Kept as empty object in most scenarios
82
+ this._startTime = process.hrtime.bigint();
83
+ }
84
+
85
+ // 🟢 Intercept the body property to establish the data pipeline
86
+ get body() {
87
+ return this._body;
88
+ }
89
+ set body(value) {
90
+ if (this._terminated) return;
91
+ this._body = value;
92
+ }
93
+
94
+ /**
95
+ * Lazy IP evaluation: Trigger C++ binding bridge only when business logic explicitly requests ctx.ip
96
+ */
97
+ get ip() {
98
+ if (this._ipCache) return this._ipCache;
99
+ const sock = this._request?.socket;
100
+ return (this._ipCache = sock ? sock.remoteAddress : "127.0.0.1");
101
+ }
102
+
103
+ /**
104
+ * Lazy Query parsing: Synchronous hand-optimized raw string scanner
105
+ */
106
+ get query() {
107
+ if (this._queryCache) return this._queryCache;
108
+ if (!this._queryString) return (this._queryCache = {});
109
+
110
+ const obj = {};
111
+ const pairs = this._queryString.split("&");
112
+ for (let i = 0; i < pairs.length; i++) {
113
+ const pair = pairs[i];
114
+ const eqIdx = pair.indexOf("=");
115
+ if (eqIdx !== -1) {
116
+ obj[pair.substring(0, eqIdx)] = pair.substring(eqIdx + 1);
117
+ } else {
118
+ obj[pair] = "";
119
+ }
120
+ }
121
+ return (this._queryCache = obj);
122
+ }
123
+
124
+ setHeader(key, value) {
125
+ if (this._terminated) return this;
126
+
127
+ // Convention: High-performance middlewares should pass standard casing headers (e.g., 'Content-Type').
128
+ // We intentionally omit .toLowerCase() here to conserve CPU cycles.
129
+ if (this._headersObj[key] === undefined) {
130
+ this._headersKeys[this._headersCount++] = key;
131
+ }
132
+ this._headersObj[key] = value;
133
+ return this;
134
+ }
135
+
136
+ getHeader(key) {
137
+ return (
138
+ this.headers[key] ||
139
+ this.headers[key.toLowerCase()] ||
140
+ this._headersObj[key] ||
141
+ null
142
+ );
143
+ }
144
+
145
+ setStatus(code) {
146
+ if (this._terminated) return this;
147
+ this.statusCode = code;
148
+ return this;
149
+ }
150
+
151
+ json(data) {
152
+ if (this._terminated) return this;
153
+ // Direct assignment to prevent redundant lookup overheads
154
+ if (this._headersObj["Content-Type"] === undefined) {
155
+ this._headersKeys[this._headersCount++] = "Content-Type";
156
+ }
157
+ this._headersObj["Content-Type"] = "application/json; charset=utf-8";
158
+ this._body = typeof data === "object" ? JSON.stringify(data) : data;
159
+ this._finalize();
160
+ return this;
161
+ }
162
+
163
+ text(data) {
164
+ if (this._terminated) return this;
165
+ if (this._headersObj["Content-Type"] === undefined) {
166
+ this._headersKeys[this._headersCount++] = "Content-Type";
167
+ }
168
+ this._headersObj["Content-Type"] = "text/plain; charset=utf-8";
169
+ this._body = String(data);
170
+ this._finalize();
171
+ return this;
172
+ }
173
+
174
+ raw(data) {
175
+ if (this._terminated) return this;
176
+ this._body = data;
177
+ this._finalize();
178
+ return this;
179
+ }
180
+
181
+ setState(key, value) {
182
+ this._stateObj[key] = value;
183
+ return this;
184
+ }
185
+
186
+ getState(key) {
187
+ return this._stateObj[key];
188
+ }
189
+
190
+ isTerminated() {
191
+ return this._terminated;
192
+ }
193
+
194
+ /**
195
+ * Critical Performance Bottleneck Optimization: Flush data instantly via the shared buffer
196
+ */
197
+ _finalize() {
198
+ if (this._terminated) return;
199
+ this._terminated = true;
200
+
201
+ const res = this._response;
202
+ const socket = res.socket;
203
+
204
+ if (socket) socket.cork();
205
+
206
+ // Core optimization: Reuse the pre-allocated flat array to eliminate transient garbage collection pressure
207
+ GLOBAL_HEADER_BUFFER[0] = "Connection";
208
+ GLOBAL_HEADER_BUFFER[1] = "keep-alive";
209
+ let cursor = 2;
210
+
211
+ for (let i = 0; i < this._headersCount; i++) {
212
+ const key = this._headersKeys[i];
213
+ const val = this._headersObj[key];
214
+ if (val !== undefined) {
215
+ GLOBAL_HEADER_BUFFER[cursor++] = key;
216
+ GLOBAL_HEADER_BUFFER[cursor++] = val;
217
+ }
218
+ }
219
+
220
+ // Slice the buffer and pass it directly to the native writeHead.
221
+ // Note: The slice size is highly predictable and short, allowing V8 to aggressively inline the operation.
222
+ res.writeHead(this.statusCode, GLOBAL_HEADER_BUFFER.slice(0, cursor));
223
+
224
+ if (this._body !== null) {
225
+ res.end(this._body);
226
+ } else {
227
+ res.end();
228
+ }
229
+
230
+ if (socket) {
231
+ process.nextTick(() => socket.uncork());
232
+ }
233
+ }
234
+
235
+ getMetrics() {
236
+ return {
237
+ duration: Number(process.hrtime.bigint() - this._startTime) / 1e6,
238
+ };
239
+ }
240
+ }
241
+
242
+ export default AetherContext;
@@ -0,0 +1,375 @@
1
+ /**
2
+ * @license MIT
3
+ * Copyright (c) 2026-present AetherFramework Contributors.
4
+ * SPDX-License-Identifier: MIT
5
+ * @module @aetherframework/middleware/core/AetherPipeline
6
+ */
7
+ import { EventEmitter } from "events";
8
+ import AetherContext from "./AetherContext.js";
9
+
10
+ const STATIC_RESPONSES = new Map([
11
+ [200, Buffer.from(JSON.stringify({ status: "ok" }))],
12
+ [404, Buffer.from(JSON.stringify({ error: "Not Found" }))],
13
+ [500, Buffer.from(JSON.stringify({ error: "Internal Server Error" }))],
14
+ ]);
15
+
16
+ const CONTEXT_POOL = [];
17
+ const IN_POOL_CHECK = new Set();
18
+ const CONTEXT_POOL_SIZE = 4096;
19
+
20
+ class AetherPipeline extends EventEmitter {
21
+ constructor() {
22
+ super();
23
+ this._middlewares = [];
24
+ this._compiled = null;
25
+ this._compiledSync = null;
26
+ this._cache = new Map();
27
+ this._cacheMaxSize = 1000;
28
+ this.enableMetrics = false;
29
+
30
+ this._stats = {
31
+ totalRequests: 0,
32
+ averageLatency: 0,
33
+ errorCount: 0,
34
+ cacheHits: 0,
35
+ cacheMisses: 0,
36
+ poolHits: 0,
37
+ poolMisses: 0,
38
+ };
39
+
40
+ this._initObjectPools();
41
+ }
42
+
43
+ _initObjectPools() {
44
+ for (let i = 0; i < CONTEXT_POOL_SIZE; i++) {
45
+ const ctx = new AetherContext(null, null);
46
+ CONTEXT_POOL.push(ctx);
47
+ IN_POOL_CHECK.add(ctx);
48
+ }
49
+ }
50
+
51
+ _getContext(request, response) {
52
+ if (CONTEXT_POOL.length > 0) {
53
+ const context = CONTEXT_POOL.pop();
54
+ IN_POOL_CHECK.delete(context);
55
+ context._reset(request, response);
56
+ if (this.enableMetrics) this._stats.poolHits++;
57
+ return context;
58
+ }
59
+ if (this.enableMetrics) this._stats.poolMisses++;
60
+ return new AetherContext(request, response);
61
+ }
62
+
63
+ _returnContext(context) {
64
+ if (!context || IN_POOL_CHECK.has(context)) return;
65
+
66
+ if (CONTEXT_POOL.length < CONTEXT_POOL_SIZE) {
67
+ context.req = null;
68
+ context.res = null;
69
+ context._body = null;
70
+
71
+ // Safely clean up Headers
72
+ if (context._headers && typeof context._headers.clear === "function") {
73
+ context._headers.clear();
74
+ } else {
75
+ context._headers = null;
76
+ }
77
+
78
+ CONTEXT_POOL.push(context);
79
+ IN_POOL_CHECK.add(context);
80
+ }
81
+ }
82
+
83
+ use(middleware) {
84
+ if (typeof middleware !== "function") {
85
+ throw new TypeError("Middleware must be a function");
86
+ }
87
+ this._middlewares.push(middleware);
88
+ this._compiled = null;
89
+ this._compiledSync = null;
90
+ return this;
91
+ }
92
+
93
+ /**
94
+ *Core Control 1: Standard V8-level high-concurrency asynchronous onion model compiler
95
+ * Ensure perfect timing wait chain, absolutely prevent asynchronous middleware (like CORS/security headers) from experiencing asynchronous drift
96
+ */
97
+ compile() {
98
+ if (this._compiled) return this._compiled;
99
+ const middlewares = this._middlewares;
100
+ const len = middlewares.length;
101
+
102
+ this._compiled = async function executePipeline(context) {
103
+ async function dispatch(i) {
104
+ // 1. Safety boundary check: If context is terminated or connection is disconnected, return directly
105
+ if (
106
+ context.isTerminated() ||
107
+ (context._response && context._response.writableEnded)
108
+ ) {
109
+ if (context._finalize) context._finalize();
110
+ return;
111
+ }
112
+
113
+ // 2. Pipeline end: Safely trigger final _finalize
114
+ if (i >= len) {
115
+ if (context._finalize) context._finalize();
116
+ return;
117
+ }
118
+
119
+ const mw = middlewares[i];
120
+
121
+ // 3. Strictly bind current middleware with next subsequent chain's asynchronous timing
122
+ await mw(context, function next() {
123
+ return dispatch(i + 1);
124
+ });
125
+ }
126
+
127
+ // 🚀 Start the first middleware and strictly wait for the entire chain lifecycle to end
128
+ await dispatch(0);
129
+ };
130
+
131
+ return this._compiled;
132
+ }
133
+
134
+ _compileSync() {
135
+ if (this._compiledSync) return this._compiledSync;
136
+ const middlewares = this._middlewares;
137
+ const len = middlewares.length;
138
+
139
+ const allSync = middlewares.every((mw) => {
140
+ const funcStr = mw.toString();
141
+ return (
142
+ !funcStr.includes("async ") &&
143
+ !funcStr.includes(".then") &&
144
+ !funcStr.includes("await ")
145
+ );
146
+ });
147
+
148
+ if (!allSync) return null;
149
+
150
+ this._compiledSync = function executePipelineSync(context) {
151
+ for (let i = 0; i < len; i++) {
152
+ middlewares[i](context, () => {});
153
+ if (context.isTerminated() || context.res?.writableEnded) return;
154
+ }
155
+ if (context._finalize) context._finalize();
156
+ };
157
+
158
+ return this._compiledSync;
159
+ }
160
+
161
+ async handle(request, response) {
162
+ this._stats.totalRequests++;
163
+ const url = request.url;
164
+ const method = request.method;
165
+
166
+ // 1. Root high-speed channel (with fallback CORS headers)
167
+ if (method === "GET" && url === "/") {
168
+ const socket = response.socket;
169
+ if (socket) socket.cork();
170
+ response.writeHead(200, [
171
+ "Content-Type",
172
+ "application/json; charset=utf-8",
173
+ "Content-Length",
174
+ "15",
175
+ "Connection",
176
+ "keep-alive",
177
+ "Access-Control-Allow-Origin",
178
+ "*",
179
+ ]);
180
+ response.end(STATIC_RESPONSES.get(200));
181
+ if (socket) socket.uncork();
182
+ return { cacheHit: true, static: true };
183
+ }
184
+
185
+ // 2. Cache route hit
186
+ let methodCache = this._cache.get(method);
187
+ if (!methodCache) {
188
+ methodCache = new Map();
189
+ this._cache.set(method, methodCache);
190
+ }
191
+ const cached = methodCache.get(url);
192
+
193
+ if (cached) {
194
+ if (this.enableMetrics) this._stats.cacheHits++;
195
+ const socket = response.socket;
196
+ if (socket) socket.cork();
197
+ response.writeHead(cached.status, cached.headers);
198
+ response.end(cached.buffer);
199
+ if (socket) socket.uncork();
200
+ return { cacheHit: true };
201
+ }
202
+
203
+ if (this.enableMetrics) this._stats.cacheMisses++;
204
+
205
+ // 3. Context construction and core scheduling
206
+ const context = this._getContext(request, response);
207
+
208
+ try {
209
+ //Core Control 2: Prioritize reading cached synchronous pipeline to avoid frequent character scanning causing CPU spikes under high concurrency
210
+ const pipelineSync = this._compiledSync || this._compileSync();
211
+
212
+ if (pipelineSync) {
213
+ pipelineSync(context);
214
+ } else {
215
+ // Strictly lock asynchronous middleware timing
216
+ await this.compile()(context);
217
+ }
218
+
219
+ // 4. Dynamically capture all response headers, merge and store in high-speed cache
220
+ if (method === "GET" && response && response.statusCode < 400) {
221
+ this._setResponseCache(methodCache, url, context, response);
222
+ }
223
+
224
+ this._returnContext(context);
225
+ return { cacheHit: false };
226
+ } catch (error) {
227
+ if (this.enableMetrics) this._stats.errorCount++;
228
+ try {
229
+ if (response && !response.headersSent) {
230
+ response.writeHead(500, [
231
+ "Content-Type",
232
+ "application/json; charset=utf-8",
233
+ "Access-Control-Allow-Origin",
234
+ "*",
235
+ ]);
236
+ response.end(STATIC_RESPONSES.get(500));
237
+ }
238
+ } catch (e) {}
239
+ if (context) this._returnContext(context);
240
+ throw error;
241
+ }
242
+ }
243
+
244
+ /**
245
+ *Core Control 3: Ultimate comprehensive defense extraction algorithm —— Intercept multi-source Headers without blind spots
246
+ */
247
+ _setResponseCache(methodCache, url, context, response) {
248
+ // Establish a standard flat array, native two-element storage format is most efficient
249
+ const rawHeaders = ["Connection", "keep-alive"];
250
+ const lowerKeys = new Set(["connection"]);
251
+
252
+ // ==========================================
253
+ // Strategy A: Forcefully synchronously extract AetherContext's high-performance private storage
254
+ // ==========================================
255
+ if (context && context._headersCount > 0) {
256
+ for (let i = 0; i < context._headersCount; i++) {
257
+ const key = context._headersKeys[i];
258
+ const val = context._headersObj[key];
259
+ if (val !== undefined && key) {
260
+ const kLower = key.toLowerCase();
261
+ if (!lowerKeys.has(kLower)) {
262
+ rawHeaders.push(key, String(val));
263
+ lowerKeys.add(kLower);
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ // ==========================================
270
+ // Strategy B: Deep scan various non-standard Context properties that may evolve into generic dictionaries
271
+ // ==========================================
272
+ const potentialDicts = [
273
+ context?._headers,
274
+ context?.headers,
275
+ context?.res?.headers,
276
+ ];
277
+ for (const dict of potentialDicts) {
278
+ if (dict && typeof dict === "object" && !(dict instanceof Set)) {
279
+ for (const key in dict) {
280
+ if (Object.prototype.hasOwnProperty.call(dict, key)) {
281
+ const kLower = key.toLowerCase();
282
+ if (!lowerKeys.has(kLower) && dict[key] !== undefined) {
283
+ rawHeaders.push(key, String(dict[key]));
284
+ lowerKeys.add(kLower);
285
+ }
286
+ }
287
+ }
288
+ }
289
+ }
290
+
291
+ // ==========================================
292
+ // Strategy C: Ultimate gap filling: Synchronously capture Node.js native response's standard response headers (security upgraded version)
293
+ // ==========================================
294
+ if (response) {
295
+ // 1. Intercept standard getHeaders() - Standard entry point for most modern Node.js
296
+ if (typeof response.getHeaders === "function") {
297
+ const nodeHeaders = response.getHeaders();
298
+ if (nodeHeaders) {
299
+ for (const key in nodeHeaders) {
300
+ const kLower = key.toLowerCase();
301
+ if (!lowerKeys.has(kLower)) {
302
+ const val = nodeHeaders[key];
303
+ rawHeaders.push(
304
+ key,
305
+ Array.isArray(val) ? val.join(", ") : String(val),
306
+ );
307
+ lowerKeys.add(kLower);
308
+ }
309
+ }
310
+ }
311
+ }
312
+
313
+ // 2. Intercept standard getHeaderNames() - As standard supplement for HTTP/2 or special streaming protocols
314
+ if (typeof response.getHeaderNames === "function") {
315
+ const names = response.getHeaderNames();
316
+ if (Array.isArray(names)) {
317
+ for (const key of names) {
318
+ const kLower = key.toLowerCase();
319
+ if (!lowerKeys.has(kLower)) {
320
+ const val = response.getHeader(key);
321
+ if (val !== undefined && val !== null) {
322
+ rawHeaders.push(
323
+ key,
324
+ Array.isArray(val) ? val.join(", ") : String(val),
325
+ );
326
+ lowerKeys.add(kLower);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ }
332
+
333
+ // 🟢 Removed response._headers detection code that would cause high-version Node.js crashes and deprecation warnings
334
+ }
335
+
336
+ // ==========================================
337
+ // Strategy D: Extract Body and convert to high-speed persistent Buffer
338
+ // ==========================================
339
+ let body = context._body || "";
340
+ const buffer = Buffer.isBuffer(body)
341
+ ? body
342
+ : Buffer.from(typeof body === "string" ? body : JSON.stringify(body));
343
+
344
+ // Complete length header
345
+ if (!lowerKeys.has("content-length")) {
346
+ rawHeaders.push("Content-Length", String(buffer.length));
347
+ }
348
+
349
+ methodCache.set(url, {
350
+ headers: rawHeaders,
351
+ status: context.statusCode || response.statusCode || 200,
352
+ buffer,
353
+ timestamp: Date.now(),
354
+ });
355
+
356
+ if (methodCache.size > this._cacheMaxSize) {
357
+ const firstKey = methodCache.keys().next().value;
358
+ methodCache.delete(firstKey);
359
+ }
360
+ }
361
+
362
+ getStats() {
363
+ return { ...this._stats, poolSize: CONTEXT_POOL.length };
364
+ }
365
+ clearCache() {
366
+ this._cache.clear();
367
+ }
368
+ precompile() {
369
+ this.compile();
370
+ this._compileSync();
371
+ return this;
372
+ }
373
+ }
374
+
375
+ export default AetherPipeline;