@morojs/moro 1.2.1 → 1.4.0
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/LICENSE +2 -2
- package/README.md +61 -7
- package/dist/core/config/file-loader.js +31 -25
- package/dist/core/config/file-loader.js.map +1 -1
- package/dist/core/config/schema.d.ts +2 -2
- package/dist/core/config/schema.js +1 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/types.d.ts +147 -0
- package/dist/core/config/types.js +124 -0
- package/dist/core/config/types.js.map +1 -0
- package/dist/core/config/typescript-loader.d.ts +6 -0
- package/dist/core/config/typescript-loader.js +268 -0
- package/dist/core/config/typescript-loader.js.map +1 -0
- package/dist/core/config/validation.d.ts +18 -0
- package/dist/core/config/validation.js +134 -0
- package/dist/core/config/validation.js.map +1 -0
- package/dist/core/docs/openapi-generator.js +6 -6
- package/dist/core/docs/openapi-generator.js.map +1 -1
- package/dist/core/docs/schema-to-openapi.d.ts +7 -0
- package/dist/core/docs/schema-to-openapi.js +124 -0
- package/dist/core/docs/schema-to-openapi.js.map +1 -0
- package/dist/core/docs/zod-to-openapi.d.ts +2 -0
- package/dist/core/docs/zod-to-openapi.js.map +1 -1
- package/dist/core/events/event-bus.js +4 -0
- package/dist/core/events/event-bus.js.map +1 -1
- package/dist/core/framework.d.ts +29 -6
- package/dist/core/framework.js +117 -18
- package/dist/core/framework.js.map +1 -1
- package/dist/core/http/http-server.d.ts +33 -0
- package/dist/core/http/http-server.js +329 -28
- package/dist/core/http/http-server.js.map +1 -1
- package/dist/core/networking/adapters/index.d.ts +3 -0
- package/dist/core/networking/adapters/index.js +10 -0
- package/dist/core/networking/adapters/index.js.map +1 -0
- package/dist/core/networking/adapters/socketio-adapter.d.ts +16 -0
- package/dist/core/networking/adapters/socketio-adapter.js +244 -0
- package/dist/core/networking/adapters/socketio-adapter.js.map +1 -0
- package/dist/core/networking/adapters/ws-adapter.d.ts +54 -0
- package/dist/core/networking/adapters/ws-adapter.js +383 -0
- package/dist/core/networking/adapters/ws-adapter.js.map +1 -0
- package/dist/core/networking/websocket-adapter.d.ts +171 -0
- package/dist/core/networking/websocket-adapter.js +5 -0
- package/dist/core/networking/websocket-adapter.js.map +1 -0
- package/dist/core/networking/websocket-manager.d.ts +53 -17
- package/dist/core/networking/websocket-manager.js +166 -108
- package/dist/core/networking/websocket-manager.js.map +1 -1
- package/dist/core/routing/index.d.ts +13 -13
- package/dist/core/routing/index.js.map +1 -1
- package/dist/core/utilities/container.d.ts +1 -0
- package/dist/core/utilities/container.js +11 -1
- package/dist/core/utilities/container.js.map +1 -1
- package/dist/core/validation/adapters.d.ts +51 -0
- package/dist/core/validation/adapters.js +135 -0
- package/dist/core/validation/adapters.js.map +1 -0
- package/dist/core/validation/index.d.ts +14 -11
- package/dist/core/validation/index.js +37 -26
- package/dist/core/validation/index.js.map +1 -1
- package/dist/core/validation/schema-interface.d.ts +36 -0
- package/dist/core/validation/schema-interface.js +68 -0
- package/dist/core/validation/schema-interface.js.map +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/moro.d.ts +8 -0
- package/dist/moro.js +339 -14
- package/dist/moro.js.map +1 -1
- package/dist/types/core.d.ts +17 -0
- package/package.json +42 -14
- package/src/core/config/file-loader.ts +34 -25
- package/src/core/config/schema.ts +1 -1
- package/src/core/config/types.ts +277 -0
- package/src/core/config/typescript-loader.ts +571 -0
- package/src/core/config/validation.ts +145 -0
- package/src/core/docs/openapi-generator.ts +7 -6
- package/src/core/docs/schema-to-openapi.ts +148 -0
- package/src/core/docs/zod-to-openapi.ts +2 -0
- package/src/core/events/event-bus.ts +5 -0
- package/src/core/framework.ts +121 -28
- package/src/core/http/http-server.ts +377 -28
- package/src/core/networking/adapters/index.ts +16 -0
- package/src/core/networking/adapters/socketio-adapter.ts +252 -0
- package/src/core/networking/adapters/ws-adapter.ts +425 -0
- package/src/core/networking/websocket-adapter.ts +217 -0
- package/src/core/networking/websocket-manager.ts +185 -127
- package/src/core/routing/index.ts +13 -13
- package/src/core/utilities/container.ts +14 -1
- package/src/core/validation/adapters.ts +147 -0
- package/src/core/validation/index.ts +60 -38
- package/src/core/validation/schema-interface.ts +100 -0
- package/src/index.ts +25 -2
- package/src/moro.ts +405 -15
- package/src/types/core.ts +18 -0
|
@@ -36,20 +36,73 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.middleware = exports.MoroHttpServer = void 0;
|
|
37
37
|
// src/core/http-server.ts
|
|
38
38
|
const http_1 = require("http");
|
|
39
|
-
const url_1 = require("url");
|
|
40
39
|
const zlib = __importStar(require("zlib"));
|
|
41
40
|
const util_1 = require("util");
|
|
42
41
|
const logger_1 = require("../logger");
|
|
43
42
|
const gzip = (0, util_1.promisify)(zlib.gzip);
|
|
44
43
|
const deflate = (0, util_1.promisify)(zlib.deflate);
|
|
45
44
|
class MoroHttpServer {
|
|
45
|
+
static getOptimalBuffer(size) {
|
|
46
|
+
// Find the smallest buffer that fits
|
|
47
|
+
for (const poolSize of MoroHttpServer.BUFFER_SIZES) {
|
|
48
|
+
if (size <= poolSize) {
|
|
49
|
+
const pool = MoroHttpServer.BUFFER_POOLS.get(poolSize);
|
|
50
|
+
return pool.length > 0 ? pool.pop() : Buffer.allocUnsafe(poolSize);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return Buffer.allocUnsafe(size);
|
|
54
|
+
}
|
|
55
|
+
static returnBuffer(buffer) {
|
|
56
|
+
// Return buffer to appropriate pool
|
|
57
|
+
const size = buffer.length;
|
|
58
|
+
if (MoroHttpServer.BUFFER_POOLS.has(size)) {
|
|
59
|
+
const pool = MoroHttpServer.BUFFER_POOLS.get(size);
|
|
60
|
+
if (pool.length < 50) {
|
|
61
|
+
// Don't let pools grow too large
|
|
62
|
+
pool.push(buffer);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
46
66
|
constructor() {
|
|
47
67
|
this.routes = [];
|
|
48
68
|
this.globalMiddleware = [];
|
|
49
69
|
this.compressionEnabled = true;
|
|
50
70
|
this.compressionThreshold = 1024;
|
|
51
71
|
this.logger = (0, logger_1.createFrameworkLogger)('HttpServer');
|
|
72
|
+
this.requestCounter = 0;
|
|
73
|
+
// Minimal object pooling for frequently created objects
|
|
74
|
+
this.paramObjectPool = [];
|
|
75
|
+
this.bufferPool = [];
|
|
76
|
+
this.maxPoolSize = 50;
|
|
77
|
+
// Request handler pooling to avoid function creation overhead
|
|
78
|
+
this.middlewareExecutionCache = new Map();
|
|
79
|
+
// Advanced route optimization: cache + static routes + segment grouping
|
|
80
|
+
this.routeCache = new Map();
|
|
81
|
+
this.staticRoutes = new Map();
|
|
82
|
+
this.dynamicRoutes = [];
|
|
83
|
+
this.routesBySegmentCount = new Map();
|
|
84
|
+
this.pathNormalizationCache = new Map();
|
|
85
|
+
// Ultra-fast CPU cache-friendly optimizations (Rust-level performance)
|
|
86
|
+
this.routeHitCount = new Map(); // Track route popularity for cache optimization
|
|
52
87
|
this.server = (0, http_1.createServer)(this.handleRequest.bind(this));
|
|
88
|
+
// Optimize server for high performance (conservative settings for compatibility)
|
|
89
|
+
this.server.keepAliveTimeout = 5000; // 5 seconds
|
|
90
|
+
this.server.headersTimeout = 6000; // 6 seconds
|
|
91
|
+
this.server.timeout = 30000; // 30 seconds request timeout
|
|
92
|
+
}
|
|
93
|
+
// Configure server for maximum performance (can disable all overhead)
|
|
94
|
+
configurePerformance(config = {}) {
|
|
95
|
+
if (config.compression !== undefined) {
|
|
96
|
+
this.compressionEnabled = config.compression.enabled;
|
|
97
|
+
if (config.compression.threshold !== undefined) {
|
|
98
|
+
this.compressionThreshold = config.compression.threshold;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Minimal mode - disable ALL overhead for pure speed
|
|
102
|
+
if (config.minimal) {
|
|
103
|
+
this.compressionEnabled = false;
|
|
104
|
+
this.compressionThreshold = Infinity; // Never compress
|
|
105
|
+
}
|
|
53
106
|
}
|
|
54
107
|
// Middleware management
|
|
55
108
|
use(middleware) {
|
|
@@ -79,14 +132,31 @@ class MoroHttpServer {
|
|
|
79
132
|
const { pattern, paramNames } = this.pathToRegex(path);
|
|
80
133
|
const handler = handlers.pop();
|
|
81
134
|
const middleware = handlers;
|
|
82
|
-
|
|
135
|
+
const route = {
|
|
83
136
|
method,
|
|
84
137
|
path,
|
|
85
138
|
pattern,
|
|
86
139
|
paramNames,
|
|
87
140
|
handler,
|
|
88
141
|
middleware,
|
|
89
|
-
}
|
|
142
|
+
};
|
|
143
|
+
this.routes.push(route);
|
|
144
|
+
// Organize routes for optimal lookup
|
|
145
|
+
if (paramNames.length === 0) {
|
|
146
|
+
// Static route - O(1) lookup
|
|
147
|
+
const staticKey = `${method}:${path}`;
|
|
148
|
+
this.staticRoutes.set(staticKey, route);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Dynamic route - organize by segment count for faster matching
|
|
152
|
+
this.dynamicRoutes.push(route);
|
|
153
|
+
const segments = path.split('/').filter(s => s.length > 0);
|
|
154
|
+
const segmentCount = segments.length;
|
|
155
|
+
if (!this.routesBySegmentCount.has(segmentCount)) {
|
|
156
|
+
this.routesBySegmentCount.set(segmentCount, []);
|
|
157
|
+
}
|
|
158
|
+
this.routesBySegmentCount.get(segmentCount).push(route);
|
|
159
|
+
}
|
|
90
160
|
}
|
|
91
161
|
pathToRegex(path) {
|
|
92
162
|
const paramNames = [];
|
|
@@ -106,12 +176,22 @@ class MoroHttpServer {
|
|
|
106
176
|
const httpReq = this.enhanceRequest(req);
|
|
107
177
|
const httpRes = this.enhanceResponse(res);
|
|
108
178
|
try {
|
|
109
|
-
//
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
179
|
+
// Optimized URL and query parsing
|
|
180
|
+
const urlString = req.url;
|
|
181
|
+
const queryIndex = urlString.indexOf('?');
|
|
182
|
+
if (queryIndex === -1) {
|
|
183
|
+
// No query string - fast path
|
|
184
|
+
httpReq.path = urlString;
|
|
185
|
+
httpReq.query = {};
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Has query string - parse efficiently
|
|
189
|
+
httpReq.path = urlString.substring(0, queryIndex);
|
|
190
|
+
httpReq.query = this.parseQueryString(urlString.substring(queryIndex + 1));
|
|
191
|
+
}
|
|
192
|
+
// Ultra-fast method checking - avoid array includes
|
|
193
|
+
const method = req.method;
|
|
194
|
+
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
|
115
195
|
httpReq.body = await this.parseBody(req);
|
|
116
196
|
}
|
|
117
197
|
// Execute hooks before request processing
|
|
@@ -130,13 +210,18 @@ class MoroHttpServer {
|
|
|
130
210
|
// Find matching route
|
|
131
211
|
const route = this.findRoute(req.method, httpReq.path);
|
|
132
212
|
if (!route) {
|
|
133
|
-
|
|
213
|
+
// Ultra-fast 404 response with pre-compiled buffer
|
|
214
|
+
httpRes.statusCode = 404;
|
|
215
|
+
httpRes.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
216
|
+
httpRes.setHeader('Content-Length', MoroHttpServer.RESPONSE_TEMPLATES.notFound.length);
|
|
217
|
+
httpRes.end(MoroHttpServer.RESPONSE_TEMPLATES.notFound);
|
|
134
218
|
return;
|
|
135
219
|
}
|
|
136
|
-
// Extract path parameters
|
|
220
|
+
// Extract path parameters - optimized with object pooling
|
|
137
221
|
const matches = httpReq.path.match(route.pattern);
|
|
138
222
|
if (matches) {
|
|
139
|
-
|
|
223
|
+
// Use pooled object for parameters
|
|
224
|
+
httpReq.params = this.acquireParamObject();
|
|
140
225
|
route.paramNames.forEach((name, index) => {
|
|
141
226
|
httpReq.params[name] = matches[index + 1];
|
|
142
227
|
});
|
|
@@ -193,14 +278,71 @@ class MoroHttpServer {
|
|
|
193
278
|
}
|
|
194
279
|
}
|
|
195
280
|
}
|
|
281
|
+
// Object pooling for parameter objects
|
|
282
|
+
acquireParamObject() {
|
|
283
|
+
const obj = this.paramObjectPool.pop();
|
|
284
|
+
if (obj) {
|
|
285
|
+
// Clear existing properties
|
|
286
|
+
Object.keys(obj).forEach(key => delete obj[key]);
|
|
287
|
+
return obj;
|
|
288
|
+
}
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
releaseParamObject(params) {
|
|
292
|
+
if (this.paramObjectPool.length < this.maxPoolSize) {
|
|
293
|
+
this.paramObjectPool.push(params);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
acquireBuffer(size) {
|
|
297
|
+
const buffer = this.bufferPool.find(b => b.length >= size);
|
|
298
|
+
if (buffer) {
|
|
299
|
+
this.bufferPool.splice(this.bufferPool.indexOf(buffer), 1);
|
|
300
|
+
return buffer.subarray(0, size);
|
|
301
|
+
}
|
|
302
|
+
return Buffer.allocUnsafe(size);
|
|
303
|
+
}
|
|
304
|
+
releaseBuffer(buffer) {
|
|
305
|
+
if (this.bufferPool.length < this.maxPoolSize && buffer.length <= 8192) {
|
|
306
|
+
this.bufferPool.push(buffer);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
streamLargeResponse(res, data) {
|
|
310
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
311
|
+
res.setHeader('Transfer-Encoding', 'chunked');
|
|
312
|
+
// Stream the response in chunks
|
|
313
|
+
const jsonString = JSON.stringify(data);
|
|
314
|
+
const chunkSize = 8192; // 8KB chunks
|
|
315
|
+
for (let i = 0; i < jsonString.length; i += chunkSize) {
|
|
316
|
+
const chunk = jsonString.substring(i, i + chunkSize);
|
|
317
|
+
res.write(chunk);
|
|
318
|
+
}
|
|
319
|
+
res.end();
|
|
320
|
+
}
|
|
321
|
+
normalizePath(path) {
|
|
322
|
+
// Check cache first
|
|
323
|
+
if (this.pathNormalizationCache.has(path)) {
|
|
324
|
+
return this.pathNormalizationCache.get(path);
|
|
325
|
+
}
|
|
326
|
+
// Fast normalization: remove trailing slash (except root), decode once
|
|
327
|
+
let normalized = path;
|
|
328
|
+
if (normalized.length > 1 && normalized.endsWith('/')) {
|
|
329
|
+
normalized = normalized.slice(0, -1);
|
|
330
|
+
}
|
|
331
|
+
// Cache result (limit cache size)
|
|
332
|
+
if (this.pathNormalizationCache.size < 200) {
|
|
333
|
+
this.pathNormalizationCache.set(path, normalized);
|
|
334
|
+
}
|
|
335
|
+
return normalized;
|
|
336
|
+
}
|
|
196
337
|
enhanceRequest(req) {
|
|
197
338
|
const httpReq = req;
|
|
198
|
-
httpReq.params =
|
|
339
|
+
httpReq.params = this.acquireParamObject();
|
|
199
340
|
httpReq.query = {};
|
|
200
341
|
httpReq.body = null;
|
|
201
342
|
httpReq.path = '';
|
|
202
343
|
httpReq.ip = req.socket.remoteAddress || '';
|
|
203
|
-
|
|
344
|
+
// Faster request ID generation
|
|
345
|
+
httpReq.requestId = Date.now().toString(36) + (++this.requestCounter).toString(36);
|
|
204
346
|
httpReq.headers = req.headers;
|
|
205
347
|
// Parse cookies
|
|
206
348
|
httpReq.cookies = this.parseCookies(req.headers.cookie || '');
|
|
@@ -228,29 +370,85 @@ class MoroHttpServer {
|
|
|
228
370
|
httpRes.json = async (data) => {
|
|
229
371
|
if (httpRes.headersSent)
|
|
230
372
|
return;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
373
|
+
// Ultra-fast JSON serialization with zero-copy buffers
|
|
374
|
+
let jsonString;
|
|
375
|
+
// Enhanced JSON optimization for common API patterns
|
|
376
|
+
if (data && typeof data === 'object' && 'success' in data) {
|
|
377
|
+
if ('data' in data && 'error' in data && !('total' in data)) {
|
|
378
|
+
// {success, data, error} pattern
|
|
379
|
+
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"error":${JSON.stringify(data.error)}}`;
|
|
380
|
+
}
|
|
381
|
+
else if ('data' in data && 'total' in data && !('error' in data)) {
|
|
382
|
+
// {success, data, total} pattern
|
|
383
|
+
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)},"total":${data.total}}`;
|
|
384
|
+
}
|
|
385
|
+
else if ('data' in data && !('error' in data) && !('total' in data)) {
|
|
386
|
+
// {success, data} pattern
|
|
387
|
+
jsonString = `{"success":${data.success},"data":${JSON.stringify(data.data)}}`;
|
|
388
|
+
}
|
|
389
|
+
else if ('error' in data && !('data' in data) && !('total' in data)) {
|
|
390
|
+
// {success, error} pattern
|
|
391
|
+
jsonString = `{"success":${data.success},"error":${JSON.stringify(data.error)}}`;
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
// Complex object - use standard JSON.stringify
|
|
395
|
+
jsonString = JSON.stringify(data);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
jsonString = JSON.stringify(data);
|
|
400
|
+
}
|
|
401
|
+
// Use buffer pool for zero-allocation responses
|
|
402
|
+
const estimatedSize = jsonString.length;
|
|
403
|
+
if (estimatedSize > 32768) {
|
|
404
|
+
// Large response - stream it
|
|
405
|
+
return this.streamLargeResponse(httpRes, data);
|
|
406
|
+
}
|
|
407
|
+
const buffer = MoroHttpServer.getOptimalBuffer(estimatedSize);
|
|
408
|
+
const actualLength = buffer.write(jsonString, 0, 'utf8');
|
|
409
|
+
// Slice to actual size to avoid sending extra bytes
|
|
410
|
+
const finalBuffer = actualLength === buffer.length ? buffer : buffer.subarray(0, actualLength);
|
|
411
|
+
// Optimized header setting - set multiple headers at once when possible
|
|
412
|
+
const headers = {
|
|
413
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
414
|
+
};
|
|
415
|
+
// Compression with buffer pool
|
|
416
|
+
if (this.compressionEnabled && finalBuffer.length > this.compressionThreshold) {
|
|
236
417
|
const acceptEncoding = httpRes.req.headers['accept-encoding'] || '';
|
|
237
418
|
if (acceptEncoding.includes('gzip')) {
|
|
238
|
-
const compressed = await gzip(
|
|
239
|
-
|
|
240
|
-
|
|
419
|
+
const compressed = await gzip(finalBuffer);
|
|
420
|
+
headers['Content-Encoding'] = 'gzip';
|
|
421
|
+
headers['Content-Length'] = compressed.length;
|
|
422
|
+
// Set all headers at once
|
|
423
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
424
|
+
httpRes.setHeader(key, value);
|
|
425
|
+
});
|
|
241
426
|
httpRes.end(compressed);
|
|
427
|
+
// Return buffer to pool after response
|
|
428
|
+
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
242
429
|
return;
|
|
243
430
|
}
|
|
244
431
|
else if (acceptEncoding.includes('deflate')) {
|
|
245
|
-
const compressed = await deflate(
|
|
246
|
-
|
|
247
|
-
|
|
432
|
+
const compressed = await deflate(finalBuffer);
|
|
433
|
+
headers['Content-Encoding'] = 'deflate';
|
|
434
|
+
headers['Content-Length'] = compressed.length;
|
|
435
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
436
|
+
httpRes.setHeader(key, value);
|
|
437
|
+
});
|
|
248
438
|
httpRes.end(compressed);
|
|
439
|
+
// Return buffer to pool after response
|
|
440
|
+
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
249
441
|
return;
|
|
250
442
|
}
|
|
251
443
|
}
|
|
252
|
-
|
|
253
|
-
|
|
444
|
+
headers['Content-Length'] = finalBuffer.length;
|
|
445
|
+
// Set all headers at once for better performance
|
|
446
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
447
|
+
httpRes.setHeader(key, value);
|
|
448
|
+
});
|
|
449
|
+
httpRes.end(finalBuffer);
|
|
450
|
+
// Return buffer to pool after response (zero-copy achievement!)
|
|
451
|
+
process.nextTick(() => MoroHttpServer.returnBuffer(buffer));
|
|
254
452
|
};
|
|
255
453
|
httpRes.send = (data) => {
|
|
256
454
|
if (httpRes.headersSent)
|
|
@@ -454,11 +652,71 @@ class MoroHttpServer {
|
|
|
454
652
|
}
|
|
455
653
|
return result;
|
|
456
654
|
}
|
|
655
|
+
parseQueryString(queryString) {
|
|
656
|
+
const result = {};
|
|
657
|
+
if (!queryString)
|
|
658
|
+
return result;
|
|
659
|
+
const pairs = queryString.split('&');
|
|
660
|
+
for (const pair of pairs) {
|
|
661
|
+
const equalIndex = pair.indexOf('=');
|
|
662
|
+
if (equalIndex === -1) {
|
|
663
|
+
result[decodeURIComponent(pair)] = '';
|
|
664
|
+
}
|
|
665
|
+
else {
|
|
666
|
+
const key = decodeURIComponent(pair.substring(0, equalIndex));
|
|
667
|
+
const value = decodeURIComponent(pair.substring(equalIndex + 1));
|
|
668
|
+
result[key] = value;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
457
673
|
findRoute(method, path) {
|
|
458
|
-
|
|
674
|
+
// Normalize path for consistent matching
|
|
675
|
+
const normalizedPath = this.normalizePath(path);
|
|
676
|
+
const cacheKey = `${method}:${normalizedPath}`;
|
|
677
|
+
// Track route popularity for hot path optimization
|
|
678
|
+
const hitCount = (this.routeHitCount.get(cacheKey) || 0) + 1;
|
|
679
|
+
this.routeHitCount.set(cacheKey, hitCount);
|
|
680
|
+
// Check cache first (hot path optimization)
|
|
681
|
+
if (this.routeCache.has(cacheKey)) {
|
|
682
|
+
const cachedRoute = this.routeCache.get(cacheKey);
|
|
683
|
+
// Promote frequently accessed routes to front of cache (LRU-like)
|
|
684
|
+
if (hitCount > MoroHttpServer.HOT_ROUTE_THRESHOLD && this.routeCache.size > 100) {
|
|
685
|
+
this.routeCache.delete(cacheKey);
|
|
686
|
+
this.routeCache.set(cacheKey, cachedRoute); // Move to end (most recent)
|
|
687
|
+
}
|
|
688
|
+
return cachedRoute;
|
|
689
|
+
}
|
|
690
|
+
// Phase 1: O(1) static route lookup
|
|
691
|
+
const staticRoute = this.staticRoutes.get(cacheKey);
|
|
692
|
+
if (staticRoute) {
|
|
693
|
+
this.routeCache.set(cacheKey, staticRoute);
|
|
694
|
+
return staticRoute;
|
|
695
|
+
}
|
|
696
|
+
// Phase 2: Optimized dynamic route matching by segment count
|
|
697
|
+
let route = null;
|
|
698
|
+
if (this.dynamicRoutes.length > 0) {
|
|
699
|
+
const segments = normalizedPath.split('/').filter(s => s.length > 0);
|
|
700
|
+
const candidateRoutes = this.routesBySegmentCount.get(segments.length) || this.dynamicRoutes;
|
|
701
|
+
// Only test routes with matching method and segment count
|
|
702
|
+
for (const candidateRoute of candidateRoutes) {
|
|
703
|
+
if (candidateRoute.method === method && candidateRoute.pattern.test(normalizedPath)) {
|
|
704
|
+
route = candidateRoute;
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
// Cache result (limit cache size to prevent memory leaks)
|
|
710
|
+
if (this.routeCache.size < 500) {
|
|
711
|
+
this.routeCache.set(cacheKey, route);
|
|
712
|
+
}
|
|
713
|
+
return route;
|
|
459
714
|
}
|
|
460
715
|
async executeMiddleware(middleware, req, res) {
|
|
461
716
|
for (const mw of middleware) {
|
|
717
|
+
// Short-circuit if response already sent
|
|
718
|
+
if (res.headersSent)
|
|
719
|
+
return;
|
|
462
720
|
await new Promise((resolve, reject) => {
|
|
463
721
|
let nextCalled = false;
|
|
464
722
|
const next = () => {
|
|
@@ -508,6 +766,49 @@ class MoroHttpServer {
|
|
|
508
766
|
}
|
|
509
767
|
}
|
|
510
768
|
exports.MoroHttpServer = MoroHttpServer;
|
|
769
|
+
// String interning for common values (massive memory savings)
|
|
770
|
+
MoroHttpServer.INTERNED_METHODS = new Map([
|
|
771
|
+
['GET', 'GET'],
|
|
772
|
+
['POST', 'POST'],
|
|
773
|
+
['PUT', 'PUT'],
|
|
774
|
+
['DELETE', 'DELETE'],
|
|
775
|
+
['PATCH', 'PATCH'],
|
|
776
|
+
['HEAD', 'HEAD'],
|
|
777
|
+
['OPTIONS', 'OPTIONS'],
|
|
778
|
+
]);
|
|
779
|
+
MoroHttpServer.INTERNED_HEADERS = new Map([
|
|
780
|
+
['content-type', 'content-type'],
|
|
781
|
+
['content-length', 'content-length'],
|
|
782
|
+
['authorization', 'authorization'],
|
|
783
|
+
['accept', 'accept'],
|
|
784
|
+
['user-agent', 'user-agent'],
|
|
785
|
+
['host', 'host'],
|
|
786
|
+
['connection', 'connection'],
|
|
787
|
+
['cache-control', 'cache-control'],
|
|
788
|
+
]);
|
|
789
|
+
// Pre-compiled response templates for ultra-common responses
|
|
790
|
+
MoroHttpServer.RESPONSE_TEMPLATES = {
|
|
791
|
+
notFound: Buffer.from('{"success":false,"error":"Not found"}'),
|
|
792
|
+
unauthorized: Buffer.from('{"success":false,"error":"Unauthorized"}'),
|
|
793
|
+
forbidden: Buffer.from('{"success":false,"error":"Forbidden"}'),
|
|
794
|
+
internalError: Buffer.from('{"success":false,"error":"Internal server error"}'),
|
|
795
|
+
methodNotAllowed: Buffer.from('{"success":false,"error":"Method not allowed"}'),
|
|
796
|
+
rateLimited: Buffer.from('{"success":false,"error":"Rate limit exceeded"}'),
|
|
797
|
+
};
|
|
798
|
+
// Ultra-fast buffer pool for zero-copy operations (Rust-level performance)
|
|
799
|
+
MoroHttpServer.BUFFER_SIZES = [64, 256, 1024, 4096, 16384];
|
|
800
|
+
MoroHttpServer.BUFFER_POOLS = new Map();
|
|
801
|
+
(() => {
|
|
802
|
+
// Pre-allocate buffer pools for zero-allocation responses
|
|
803
|
+
for (const size of MoroHttpServer.BUFFER_SIZES) {
|
|
804
|
+
MoroHttpServer.BUFFER_POOLS.set(size, []);
|
|
805
|
+
for (let i = 0; i < 50; i++) {
|
|
806
|
+
// 50 buffers per size
|
|
807
|
+
MoroHttpServer.BUFFER_POOLS.get(size).push(Buffer.allocUnsafe(size));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
})();
|
|
811
|
+
MoroHttpServer.HOT_ROUTE_THRESHOLD = 100; // Routes accessed 100+ times get hot path treatment
|
|
511
812
|
// Built-in middleware
|
|
512
813
|
exports.middleware = {
|
|
513
814
|
cors: (options = {}) => {
|