jerkjs 2.5.6 → 2.5.8
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 +26 -1
- package/ESTADISTICAS_RENDIMIENTO.md +106 -0
- package/README.md +67 -2
- package/debug_hook.js +11 -0
- package/doc-2.5/ADMIN_EXTENSION_COMMANDS_MANUAL.md +261 -0
- package/doc-2.5/ADMIN_EXTENSION_HOOK_EXAMPLE.md +28 -0
- package/doc-2.5/ADMIN_EXTENSION_INTEGRATION_MANUAL.md +232 -0
- package/doc-2.5/CACHE_SYSTEM_MAP.md +206 -0
- package/docs/CACHE_SYSTEM_MAP.md +206 -0
- package/index.js +7 -1
- package/lib/admin/AdminExtension.js +436 -0
- package/lib/admin/ModuleLoader.js +77 -0
- package/lib/admin/config.js +21 -0
- package/lib/admin/modules/CacheModule.js +145 -0
- package/lib/admin/modules/STATS_MODULE_README.md +98 -0
- package/lib/admin/modules/StatsModule.js +140 -0
- package/lib/admin/modules/SystemModule.js +140 -0
- package/lib/admin/modules/TimeModule.js +95 -0
- package/lib/cache/CacheHooks.js +141 -0
- package/lib/core/server.js +153 -10
- package/lib/mvc/viewEngine.js +26 -1
- package/lib/router/RouteMatcher.js +242 -54
- package/lib/utils/globalStats.js +16 -0
- package/package.json +2 -2
package/lib/core/server.js
CHANGED
|
@@ -13,6 +13,8 @@ const { Logger } = require('../utils/logger');
|
|
|
13
13
|
const { ErrorHandler } = require('../utils/errorHandler');
|
|
14
14
|
const { getMimeType } = require('../utils/mimeType');
|
|
15
15
|
const RouteMatcher = require('../router/RouteMatcher');
|
|
16
|
+
const AdminExtension = require('../admin/AdminExtension');
|
|
17
|
+
const { globalStats } = require('../utils/globalStats');
|
|
16
18
|
|
|
17
19
|
// Carga anticipada de módulos comunes para evitar cargas repetidas
|
|
18
20
|
let hooksInstance = null;
|
|
@@ -59,6 +61,42 @@ class APIServer {
|
|
|
59
61
|
|
|
60
62
|
// Inicializar el componente de enrutamiento
|
|
61
63
|
this.routeMatcher = new RouteMatcher();
|
|
64
|
+
|
|
65
|
+
// Inicializar la extensión de administración
|
|
66
|
+
this.adminExtension = null;
|
|
67
|
+
|
|
68
|
+
// Inicializar el sistema de hooks si no existe
|
|
69
|
+
if (!options.hooks) {
|
|
70
|
+
const { hooks } = require('../../index.js'); // Ruta corregida
|
|
71
|
+
this.hooks = hooks;
|
|
72
|
+
} else {
|
|
73
|
+
this.hooks = options.hooks;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Conectar el sistema de hooks al routeMatcher
|
|
77
|
+
this.routeMatcher.hooks = this.hooks;
|
|
78
|
+
|
|
79
|
+
// Propiedad para almacenar el viewEngine
|
|
80
|
+
this._viewEngine = null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Setter para el viewEngine que también conecta los hooks
|
|
85
|
+
*/
|
|
86
|
+
set viewEngine(engine) {
|
|
87
|
+
this._viewEngine = engine;
|
|
88
|
+
|
|
89
|
+
// Conectar el sistema de hooks al viewEngine si está disponible
|
|
90
|
+
if (this._viewEngine && this.hooks) {
|
|
91
|
+
this._viewEngine.hooks = this.hooks;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Getter para el viewEngine
|
|
97
|
+
*/
|
|
98
|
+
get viewEngine() {
|
|
99
|
+
return this._viewEngine;
|
|
62
100
|
}
|
|
63
101
|
|
|
64
102
|
/**
|
|
@@ -353,12 +391,18 @@ class APIServer {
|
|
|
353
391
|
// Crear handler para archivos estáticos
|
|
354
392
|
const staticHandler = this.createStaticFileHandler(routeConfig.static, routeConfig.path);
|
|
355
393
|
|
|
356
|
-
|
|
394
|
+
const route = {
|
|
357
395
|
method: routeConfig.method.toUpperCase(),
|
|
358
396
|
path: routeConfig.path,
|
|
359
397
|
handler: staticHandler,
|
|
360
398
|
isStatic: true // Marcar como ruta estática para posible procesamiento especial
|
|
361
|
-
}
|
|
399
|
+
};
|
|
400
|
+
this.routes.push(route);
|
|
401
|
+
|
|
402
|
+
// Disparar hook cuando se registra una ruta
|
|
403
|
+
if (this.hooks) {
|
|
404
|
+
this.hooks.doAction('route_registered', route);
|
|
405
|
+
}
|
|
362
406
|
|
|
363
407
|
return; // Salir después de procesar ruta estática
|
|
364
408
|
}
|
|
@@ -437,11 +481,17 @@ class APIServer {
|
|
|
437
481
|
};
|
|
438
482
|
|
|
439
483
|
// Agregar la ruta con el handler autenticado
|
|
440
|
-
|
|
484
|
+
const route = {
|
|
441
485
|
method: routeConfig.method.toUpperCase(),
|
|
442
486
|
path: routeConfig.path,
|
|
443
487
|
handler: authenticatedHandler
|
|
444
|
-
}
|
|
488
|
+
};
|
|
489
|
+
this.routes.push(route);
|
|
490
|
+
|
|
491
|
+
// Disparar hook cuando se registra una ruta
|
|
492
|
+
if (this.hooks) {
|
|
493
|
+
this.hooks.doAction('route_registered', route);
|
|
494
|
+
}
|
|
445
495
|
} else {
|
|
446
496
|
// Si no hay sessionManager en el servidor, agregar la ruta normalmente
|
|
447
497
|
this.routes.push({
|
|
@@ -488,27 +538,45 @@ class APIServer {
|
|
|
488
538
|
};
|
|
489
539
|
|
|
490
540
|
// Agregar la ruta con el handler autenticado
|
|
491
|
-
|
|
541
|
+
const route = {
|
|
492
542
|
method: routeConfig.method.toUpperCase(),
|
|
493
543
|
path: routeConfig.path,
|
|
494
544
|
handler: authenticatedHandler
|
|
495
|
-
}
|
|
545
|
+
};
|
|
546
|
+
this.routes.push(route);
|
|
547
|
+
|
|
548
|
+
// Disparar hook cuando se registra una ruta
|
|
549
|
+
if (this.hooks) {
|
|
550
|
+
this.hooks.doAction('route_registered', route);
|
|
551
|
+
}
|
|
496
552
|
} else {
|
|
497
553
|
// Si no hay authenticator en el servidor, agregar la ruta normalmente
|
|
498
|
-
|
|
554
|
+
const route = {
|
|
499
555
|
method: routeConfig.method.toUpperCase(),
|
|
500
556
|
path: routeConfig.path,
|
|
501
557
|
handler: finalHandler
|
|
502
|
-
}
|
|
558
|
+
};
|
|
559
|
+
this.routes.push(route);
|
|
560
|
+
|
|
561
|
+
// Disparar hook cuando se registra una ruta
|
|
562
|
+
if (this.hooks) {
|
|
563
|
+
this.hooks.doAction('route_registered', route);
|
|
564
|
+
}
|
|
503
565
|
}
|
|
504
566
|
}
|
|
505
567
|
} else {
|
|
506
568
|
// Si no hay autenticación requerida, agregar la ruta normalmente
|
|
507
|
-
|
|
569
|
+
const route = {
|
|
508
570
|
method: routeConfig.method.toUpperCase(),
|
|
509
571
|
path: routeConfig.path,
|
|
510
572
|
handler: finalHandler
|
|
511
|
-
}
|
|
573
|
+
};
|
|
574
|
+
this.routes.push(route);
|
|
575
|
+
|
|
576
|
+
// Disparar hook cuando se registra una ruta
|
|
577
|
+
if (this.hooks) {
|
|
578
|
+
this.hooks.doAction('route_registered', route);
|
|
579
|
+
}
|
|
512
580
|
}
|
|
513
581
|
}
|
|
514
582
|
|
|
@@ -579,12 +647,28 @@ class APIServer {
|
|
|
579
647
|
// Disparar hook después de iniciar el servidor
|
|
580
648
|
if (hooks) {
|
|
581
649
|
hooks.doAction('post_server_start', this);
|
|
650
|
+
|
|
651
|
+
// Disparar hook para inicializar extensiones después de que el servidor esté completamente iniciado
|
|
652
|
+
hooks.doAction('admin_extensions_initialize', this);
|
|
582
653
|
}
|
|
583
654
|
});
|
|
584
655
|
|
|
585
656
|
return this.server;
|
|
586
657
|
}
|
|
587
658
|
|
|
659
|
+
/**
|
|
660
|
+
* Inicializa la extensión de administración
|
|
661
|
+
* @param {Object} options - Opciones para la extensión de administración
|
|
662
|
+
*/
|
|
663
|
+
initializeAdminExtension(options = {}) {
|
|
664
|
+
if (!this.adminExtension) {
|
|
665
|
+
this.adminExtension = new AdminExtension(options);
|
|
666
|
+
this.adminExtension.initialize(this);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return this.adminExtension;
|
|
670
|
+
}
|
|
671
|
+
|
|
588
672
|
/**
|
|
589
673
|
* Detiene el servidor
|
|
590
674
|
*/
|
|
@@ -594,6 +678,11 @@ class APIServer {
|
|
|
594
678
|
this.logger.info('Servidor detenido');
|
|
595
679
|
});
|
|
596
680
|
}
|
|
681
|
+
|
|
682
|
+
// Cerrar la extensión de administración si está activa
|
|
683
|
+
if (this.adminExtension) {
|
|
684
|
+
this.adminExtension.close();
|
|
685
|
+
}
|
|
597
686
|
}
|
|
598
687
|
|
|
599
688
|
/**
|
|
@@ -643,6 +732,9 @@ class APIServer {
|
|
|
643
732
|
// Concatenar todos los chunks una sola vez
|
|
644
733
|
req.body = Buffer.concat(bodyChunks).toString();
|
|
645
734
|
|
|
735
|
+
// Actualizar estadísticas globales de bytes recibidos
|
|
736
|
+
globalStats.requestBytes += bodySize;
|
|
737
|
+
|
|
646
738
|
// Parsear body si es JSON
|
|
647
739
|
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
648
740
|
try {
|
|
@@ -802,9 +894,60 @@ class APIServer {
|
|
|
802
894
|
}
|
|
803
895
|
}
|
|
804
896
|
|
|
897
|
+
// Capturar el tamaño de la respuesta antes de enviarla
|
|
898
|
+
const originalWrite = res.write;
|
|
899
|
+
const originalEnd = res.end;
|
|
900
|
+
let responseData = [];
|
|
901
|
+
|
|
902
|
+
res.write = function(chunk) {
|
|
903
|
+
if (chunk) {
|
|
904
|
+
responseData.push(Buffer.from(chunk));
|
|
905
|
+
}
|
|
906
|
+
return originalWrite.apply(this, arguments);
|
|
907
|
+
};
|
|
908
|
+
|
|
909
|
+
res.end = function(chunk) {
|
|
910
|
+
if (chunk) {
|
|
911
|
+
responseData.push(Buffer.from(chunk));
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Almacenar los datos de la respuesta para estadísticas
|
|
915
|
+
res._data = Buffer.concat(responseData);
|
|
916
|
+
|
|
917
|
+
// Actualizar estadísticas globales de bytes enviados
|
|
918
|
+
if (res._data) {
|
|
919
|
+
const responseSize = Buffer.byteLength(res._data, 'utf8');
|
|
920
|
+
globalStats.responseBytes += responseSize;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
return originalEnd.apply(this, arguments);
|
|
924
|
+
};
|
|
925
|
+
|
|
926
|
+
// Actualizar estadísticas globales de solicitudes procesadas
|
|
927
|
+
globalStats.requestsProcessed++;
|
|
928
|
+
|
|
805
929
|
// Ejecutar handler de la ruta
|
|
806
930
|
await matchedRoute.route.handler(req, res);
|
|
807
931
|
|
|
932
|
+
// Actualizar estadísticas globales de respuestas enviadas
|
|
933
|
+
globalStats.responsesSent++;
|
|
934
|
+
|
|
935
|
+
// Registrar acceso a ruta
|
|
936
|
+
if (req.originalUrl) {
|
|
937
|
+
const routeKey = `${req.method} ${req.originalUrl}`;
|
|
938
|
+
if (!globalStats.routeAccesses.has(routeKey)) {
|
|
939
|
+
globalStats.routeAccesses.set(routeKey, 0);
|
|
940
|
+
}
|
|
941
|
+
globalStats.routeAccesses.set(routeKey, globalStats.routeAccesses.get(routeKey) + 1);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Registrar hit al endpoint
|
|
945
|
+
const endpointKey = `${req.method} ${matchedRoute.route.path}`;
|
|
946
|
+
if (!globalStats.endpointHits.has(endpointKey)) {
|
|
947
|
+
globalStats.endpointHits.set(endpointKey, 0);
|
|
948
|
+
}
|
|
949
|
+
globalStats.endpointHits.set(endpointKey, globalStats.endpointHits.get(endpointKey) + 1);
|
|
950
|
+
|
|
808
951
|
if (hooks) {
|
|
809
952
|
hooks.doAction('route_handler_executed', matchedRoute, req, res);
|
|
810
953
|
}
|
package/lib/mvc/viewEngine.js
CHANGED
|
@@ -274,7 +274,22 @@ class ViewEngine {
|
|
|
274
274
|
|
|
275
275
|
// Si el cache está habilitado, guardar la vista compilada (sin variables)
|
|
276
276
|
if (this.cacheEnabled) {
|
|
277
|
-
|
|
277
|
+
// Aplicar filter antes de cachear la vista
|
|
278
|
+
const contentToCache = this.hooks ?
|
|
279
|
+
this.hooks.applyFilters('before_view_cache', viewContent, viewPath) : viewContent;
|
|
280
|
+
|
|
281
|
+
// Verificar con hook si se debe cachear esta vista
|
|
282
|
+
const shouldCache = this.hooks ?
|
|
283
|
+
this.hooks.applyFilters('should_cache_view', true, viewPath, contentToCache) : true;
|
|
284
|
+
|
|
285
|
+
if (shouldCache) {
|
|
286
|
+
this.viewCache.set(viewPath, contentToCache);
|
|
287
|
+
|
|
288
|
+
// Disparar hook después de cachear
|
|
289
|
+
if (this.hooks) {
|
|
290
|
+
this.hooks.doAction('view_cached', viewPath, contentToCache);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
278
293
|
}
|
|
279
294
|
|
|
280
295
|
// Procesar el template con las variables
|
|
@@ -817,7 +832,17 @@ class ViewEngine {
|
|
|
817
832
|
* Limpia el cache de vistas
|
|
818
833
|
*/
|
|
819
834
|
clearCache() {
|
|
835
|
+
// Disparar hook antes de limpiar el caché
|
|
836
|
+
if (this.hooks) {
|
|
837
|
+
this.hooks.doAction('before_view_cache_clear', this);
|
|
838
|
+
}
|
|
839
|
+
|
|
820
840
|
this.viewCache.clear();
|
|
841
|
+
|
|
842
|
+
// Disparar hook después de limpiar el caché
|
|
843
|
+
if (this.hooks) {
|
|
844
|
+
this.hooks.doAction('view_cache_cleared');
|
|
845
|
+
}
|
|
821
846
|
}
|
|
822
847
|
}
|
|
823
848
|
|
|
@@ -1,31 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Componente especializado para la lógica de enrutado
|
|
2
|
+
* Componente especializado para la lógica de enrutado optimizado
|
|
3
3
|
* Implementación del componente router/RouteMatcher.js
|
|
4
|
-
* JERK Framework v2.
|
|
4
|
+
* JERK Framework v2.5.7 - Optimización de rendimiento con índices y pre-filtrado
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
class RouteMatcher {
|
|
8
8
|
/**
|
|
9
|
-
* Constructor del matcher de rutas
|
|
9
|
+
* Constructor del matcher de rutas optimizado
|
|
10
10
|
*/
|
|
11
11
|
constructor() {
|
|
12
12
|
// Cache de expresiones regulares para rutas parametrizadas
|
|
13
13
|
this.routeRegexCache = new Map();
|
|
14
|
+
|
|
15
|
+
// Índices para optimización
|
|
16
|
+
this.exactRoutes = new Map(); // Rutas exactas indexadas por método y path
|
|
17
|
+
this.routeBuckets = {}; // Rutas organizadas por método, número de segmentos y primer segmento
|
|
18
|
+
this.indexesValid = false;
|
|
19
|
+
|
|
20
|
+
// Referencia al sistema de hooks (se establecerá externamente)
|
|
21
|
+
this.hooks = null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Actualiza los índices basados en el conjunto actual de rutas
|
|
26
|
+
* @param {Array} routes - Array de rutas registradas
|
|
27
|
+
*/
|
|
28
|
+
updateIndexes(routes) {
|
|
29
|
+
// Limpiar índices existentes
|
|
30
|
+
this.exactRoutes.clear();
|
|
31
|
+
this.routeBuckets = {};
|
|
32
|
+
|
|
33
|
+
// Reconstruir índices
|
|
34
|
+
for (const route of routes) {
|
|
35
|
+
// Indexar rutas exactas
|
|
36
|
+
const methodPathKey = `${route.method}:${route.path}`;
|
|
37
|
+
this.exactRoutes.set(methodPathKey, route);
|
|
38
|
+
|
|
39
|
+
// Organizar rutas en buckets por método, número de segmentos y primer segmento
|
|
40
|
+
const segments = route.path.split('/').filter(s => s !== '');
|
|
41
|
+
const segmentCount = segments.length;
|
|
42
|
+
const firstSegment = segments.length > 0 ? segments[0] : '';
|
|
43
|
+
|
|
44
|
+
// Inicializar estructura de buckets si no existe
|
|
45
|
+
if (!this.routeBuckets[route.method]) {
|
|
46
|
+
this.routeBuckets[route.method] = {};
|
|
47
|
+
}
|
|
48
|
+
if (!this.routeBuckets[route.method][segmentCount]) {
|
|
49
|
+
this.routeBuckets[route.method][segmentCount] = {};
|
|
50
|
+
}
|
|
51
|
+
if (!this.routeBuckets[route.method][segmentCount][firstSegment]) {
|
|
52
|
+
this.routeBuckets[route.method][segmentCount][firstSegment] = [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Agregar ruta al bucket correspondiente
|
|
56
|
+
this.routeBuckets[route.method][segmentCount][firstSegment].push(route);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
this.indexesValid = true;
|
|
14
60
|
}
|
|
15
61
|
|
|
16
62
|
/**
|
|
17
|
-
* Método para encontrar una ruta coincidente
|
|
63
|
+
* Método para encontrar una ruta coincidente con optimizaciones
|
|
18
64
|
* @param {Array} routes - Array de rutas registradas
|
|
19
65
|
* @param {string} method - Método HTTP
|
|
20
66
|
* @param {string} pathname - Ruta a buscar
|
|
21
67
|
* @returns {Object|null} - Objeto de ruta encontrado o null
|
|
22
68
|
*/
|
|
23
69
|
findRoute(routes, method, pathname) {
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
70
|
+
// Actualizar índices si es necesario
|
|
71
|
+
if (!this.indexesValid || routes.length !== this.getStoredRoutesCount()) {
|
|
72
|
+
this.updateIndexes(routes);
|
|
73
|
+
}
|
|
28
74
|
|
|
75
|
+
// Búsqueda por ruta exacta (más rápida)
|
|
76
|
+
const exactMatch = this.findExactMatch(method, pathname);
|
|
29
77
|
if (exactMatch) {
|
|
30
78
|
return {
|
|
31
79
|
route: exactMatch,
|
|
@@ -33,45 +81,143 @@ class RouteMatcher {
|
|
|
33
81
|
};
|
|
34
82
|
}
|
|
35
83
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
84
|
+
// Pre-filtrar rutas candidatas usando índices
|
|
85
|
+
const pathnameSegments = pathname.split('/').filter(s => s !== '');
|
|
86
|
+
const segmentCount = pathnameSegments.length;
|
|
87
|
+
const firstSegment = pathnameSegments.length > 0 ? pathnameSegments[0] : '';
|
|
88
|
+
|
|
89
|
+
// Obtener rutas candidatas basadas en número de segmentos y primer segmento
|
|
90
|
+
const candidateRoutes = this.getCandidateRoutes(method, segmentCount, firstSegment, pathname);
|
|
91
|
+
|
|
92
|
+
// Buscar rutas estáticas que coincidan exactamente
|
|
93
|
+
const staticExactMatch = this.findStaticExactMatch(candidateRoutes, pathname);
|
|
94
|
+
if (staticExactMatch) {
|
|
95
|
+
return {
|
|
96
|
+
route: staticExactMatch,
|
|
97
|
+
params: {}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Buscar rutas parametrizadas entre los candidatos
|
|
102
|
+
const parametrizedMatch = this.findParametrizedMatch(candidateRoutes, pathname);
|
|
103
|
+
if (parametrizedMatch) {
|
|
104
|
+
return parametrizedMatch;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Buscar rutas estáticas (prefijos) entre los candidatos
|
|
108
|
+
const staticPrefixMatch = this.findStaticPrefixMatch(candidateRoutes, pathname);
|
|
109
|
+
if (staticPrefixMatch) {
|
|
110
|
+
return staticPrefixMatch;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Busca rutas exactas usando el índice
|
|
118
|
+
* @param {string} method - Método HTTP
|
|
119
|
+
* @param {string} pathname - Ruta a buscar
|
|
120
|
+
* @returns {Object|null} - Ruta exacta encontrada o null
|
|
121
|
+
*/
|
|
122
|
+
findExactMatch(method, pathname) {
|
|
123
|
+
const methodPathKey = `${method}:${pathname}`;
|
|
124
|
+
return this.exactRoutes.get(methodPathKey) || null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Obtiene rutas candidatas basadas en número de segmentos y primer segmento
|
|
129
|
+
* @param {string} method - Método HTTP
|
|
130
|
+
* @param {number} segmentCount - Número de segmentos de la ruta buscada
|
|
131
|
+
* @param {string} firstSegment - Primer segmento de la ruta buscada
|
|
132
|
+
* @param {string} pathname - Ruta completa buscada
|
|
133
|
+
* @returns {Array} - Array de rutas candidatas
|
|
134
|
+
*/
|
|
135
|
+
getCandidateRoutes(method, segmentCount, firstSegment, pathname) {
|
|
136
|
+
const candidates = [];
|
|
137
|
+
|
|
138
|
+
// Si existe un bucket para este método, número de segmentos y primer segmento
|
|
139
|
+
if (this.routeBuckets[method] &&
|
|
140
|
+
this.routeBuckets[method][segmentCount] &&
|
|
141
|
+
this.routeBuckets[method][segmentCount][firstSegment]) {
|
|
142
|
+
candidates.push(...this.routeBuckets[method][segmentCount][firstSegment]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// También agregar rutas de otros primeros segmentos para este método y número de segmentos
|
|
146
|
+
// (por si hay rutas parametrizadas que también podrían coincidir)
|
|
147
|
+
if (this.routeBuckets[method] && this.routeBuckets[method][segmentCount]) {
|
|
148
|
+
for (const segment in this.routeBuckets[method][segmentCount]) {
|
|
149
|
+
if (segment !== firstSegment) {
|
|
150
|
+
candidates.push(...this.routeBuckets[method][segmentCount][segment]);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Agregar rutas estáticas que podrían coincidir por prefijo
|
|
156
|
+
if (this.routeBuckets[method]) {
|
|
157
|
+
for (const count in this.routeBuckets[method]) {
|
|
158
|
+
for (const segment in this.routeBuckets[method][count]) {
|
|
159
|
+
const routes = this.routeBuckets[method][count][segment];
|
|
160
|
+
for (const route of routes) {
|
|
161
|
+
if (route.isStatic && pathname.startsWith(route.path)) {
|
|
162
|
+
candidates.push(route);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return candidates;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Busca rutas estáticas con coincidencia exacta entre candidatos
|
|
174
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
175
|
+
* @param {string} pathname - Ruta a buscar
|
|
176
|
+
* @returns {Object|null} - Ruta estática exacta encontrada o null
|
|
177
|
+
*/
|
|
178
|
+
findStaticExactMatch(candidateRoutes, pathname) {
|
|
179
|
+
for (const route of candidateRoutes) {
|
|
42
180
|
if (route.isStatic && route.path === pathname) {
|
|
43
|
-
return
|
|
44
|
-
route: route,
|
|
45
|
-
params: {}
|
|
46
|
-
};
|
|
181
|
+
return route;
|
|
47
182
|
}
|
|
48
183
|
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
49
186
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
params
|
|
65
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Busca rutas parametrizadas entre candidatos
|
|
189
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
190
|
+
* @param {string} pathname - Ruta a buscar
|
|
191
|
+
* @returns {Object|null} - Ruta parametrizada encontrada o null
|
|
192
|
+
*/
|
|
193
|
+
findParametrizedMatch(candidateRoutes, pathname) {
|
|
194
|
+
for (const route of candidateRoutes) {
|
|
195
|
+
if (this.isParametrizedRoute(route.path)) {
|
|
196
|
+
// Convertir ruta parametrizada a expresión regular
|
|
197
|
+
const routeRegex = this.pathToRegex(route.path);
|
|
198
|
+
const match = pathname.match(routeRegex);
|
|
199
|
+
|
|
200
|
+
if (match) {
|
|
201
|
+
const params = this.extractParams(route.path, pathname);
|
|
202
|
+
return {
|
|
203
|
+
route: route,
|
|
204
|
+
params: params
|
|
205
|
+
};
|
|
206
|
+
}
|
|
66
207
|
}
|
|
67
208
|
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
68
211
|
|
|
69
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Busca rutas estáticas por prefijo entre candidatos
|
|
214
|
+
* @param {Array} candidateRoutes - Rutas candidatas
|
|
215
|
+
* @param {string} pathname - Ruta a buscar
|
|
216
|
+
* @returns {Object|null} - Ruta estática por prefijo encontrada o null
|
|
217
|
+
*/
|
|
218
|
+
findStaticPrefixMatch(candidateRoutes, pathname) {
|
|
70
219
|
const staticMatches = [];
|
|
71
|
-
for (const route of
|
|
72
|
-
if (route.method !== method) continue;
|
|
73
|
-
|
|
74
|
-
// Para rutas estáticas, verificar si la ruta solicitada comienza con el prefijo de la ruta estática
|
|
220
|
+
for (const route of candidateRoutes) {
|
|
75
221
|
if (route.isStatic && pathname.startsWith(route.path)) {
|
|
76
222
|
// Verificar que sea exactamente el prefijo o que haya una barra después del prefijo
|
|
77
223
|
const remainingPath = pathname.substring(route.path.length);
|
|
@@ -85,27 +231,40 @@ class RouteMatcher {
|
|
|
85
231
|
}
|
|
86
232
|
}
|
|
87
233
|
|
|
88
|
-
|
|
89
|
-
// (tiene extensión), dar prioridad a la ruta estática más específica
|
|
90
|
-
const hasFileExtension = /\.[^.]+$/.test(pathname);
|
|
91
|
-
|
|
92
|
-
if (hasFileExtension && staticMatches.length > 0) {
|
|
234
|
+
if (staticMatches.length > 0) {
|
|
93
235
|
// Ordenar por especificidad (longitud del prefijo, descendente) y tomar la más específica
|
|
94
236
|
staticMatches.sort((a, b) => b.specificity - a.specificity);
|
|
95
|
-
// Para solicitudes de archivos, dar prioridad a rutas estáticas más específicas
|
|
96
|
-
return staticMatches[0]; // Devolver la coincidencia estática más específica
|
|
97
|
-
} else if (parametrizedMatches.length > 0) {
|
|
98
|
-
// Para solicitudes sin extensión o rutas API, dar prioridad a rutas parametrizadas
|
|
99
|
-
return parametrizedMatches[0]; // Devolver la primera coincidencia parametrizada
|
|
100
|
-
} else if (staticMatches.length > 0) {
|
|
101
|
-
// Si no hay coincidencias parametrizadas, usar la estática más específica
|
|
102
|
-
staticMatches.sort((a, b) => b.specificity - a.specificity);
|
|
103
237
|
return staticMatches[0];
|
|
104
238
|
}
|
|
105
239
|
|
|
106
240
|
return null;
|
|
107
241
|
}
|
|
108
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Determina si una ruta es parametrizada
|
|
245
|
+
* @param {string} path - Ruta a verificar
|
|
246
|
+
* @returns {boolean} - True si la ruta contiene parámetros
|
|
247
|
+
*/
|
|
248
|
+
isParametrizedRoute(path) {
|
|
249
|
+
return path.includes(':');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Obtiene el conteo total de rutas almacenadas en índices
|
|
254
|
+
* @returns {number} - Número total de rutas indexadas
|
|
255
|
+
*/
|
|
256
|
+
getStoredRoutesCount() {
|
|
257
|
+
let count = 0;
|
|
258
|
+
for (const method in this.routeBuckets) {
|
|
259
|
+
for (const segmentCount in this.routeBuckets[method]) {
|
|
260
|
+
for (const firstSegment in this.routeBuckets[method][segmentCount]) {
|
|
261
|
+
count += this.routeBuckets[method][segmentCount][firstSegment].length;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return count;
|
|
266
|
+
}
|
|
267
|
+
|
|
109
268
|
/**
|
|
110
269
|
* Convierte una ruta con parámetros a expresión regular
|
|
111
270
|
* @param {string} path - Ruta con posibles parámetros
|
|
@@ -131,8 +290,20 @@ class RouteMatcher {
|
|
|
131
290
|
const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
|
|
132
291
|
const regex = new RegExp(`^${regexPath}$`);
|
|
133
292
|
|
|
134
|
-
//
|
|
135
|
-
this.
|
|
293
|
+
// Verificar con hook si se debe cachear esta expresión regular
|
|
294
|
+
const shouldCache = this.hooks ?
|
|
295
|
+
this.hooks.applyFilters('should_cache_route_regex', true, path, regex) : true;
|
|
296
|
+
|
|
297
|
+
if (shouldCache) {
|
|
298
|
+
// Almacenar en caché
|
|
299
|
+
this.routeRegexCache.set(path, regex);
|
|
300
|
+
|
|
301
|
+
// Disparar hook después de cachear
|
|
302
|
+
if (this.hooks) {
|
|
303
|
+
this.hooks.doAction('route_regex_cached', path, regex);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
136
307
|
return regex;
|
|
137
308
|
}
|
|
138
309
|
|
|
@@ -169,6 +340,23 @@ class RouteMatcher {
|
|
|
169
340
|
|
|
170
341
|
return params;
|
|
171
342
|
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Limpia el caché de expresiones regulares
|
|
346
|
+
*/
|
|
347
|
+
clearCache() {
|
|
348
|
+
// Disparar hook antes de limpiar el caché
|
|
349
|
+
if (this.hooks) {
|
|
350
|
+
this.hooks.doAction('before_route_regex_cache_clear', this);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.routeRegexCache.clear();
|
|
354
|
+
|
|
355
|
+
// Disparar hook después de limpiar el caché
|
|
356
|
+
if (this.hooks) {
|
|
357
|
+
this.hooks.doAction('route_regex_cache_cleared');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
172
360
|
}
|
|
173
361
|
|
|
174
362
|
module.exports = RouteMatcher;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Almacenamiento global de estadísticas para el framework JERK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Crear un objeto global para almacenar estadísticas
|
|
6
|
+
const globalStats = {
|
|
7
|
+
requestsProcessed: 0,
|
|
8
|
+
responsesSent: 0,
|
|
9
|
+
requestBytes: 0,
|
|
10
|
+
responseBytes: 0,
|
|
11
|
+
routeAccesses: new Map(), // Contador de accesos por ruta
|
|
12
|
+
endpointHits: new Map(), // Contador de hits por endpoint
|
|
13
|
+
lastRequests: [] // Últimas solicitudes para análisis
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = { globalStats };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jerkjs",
|
|
3
|
-
"version": "2.5.
|
|
4
|
-
"description": "JERK Framework v2.5.
|
|
3
|
+
"version": "2.5.8",
|
|
4
|
+
"description": "JERK Framework v2.5.8 - A comprehensive framework for building secure and scalable APIs with frontend support, sessions, template engine, integration with qbuilderjs, complete MVC architecture with models, enhanced route loading from directory, improved model loading system, administration extension, and fixed routing issues with static and parametrized routes",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "echo \"Error: no test specified\" && exit 1",
|