jerkjs 2.0.2 → 2.1.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/CHANGELOG.md +31 -0
- package/JERK_FRAMEWORK_DIAGRAM.txt +492 -0
- package/JERK_FRAMEWORK_DIAGRAM_MERMAID.mmd +124 -0
- package/JERK_FRAMEWORK_DOCUMENTATION.md +527 -0
- package/README.md +12 -2
- package/docs/guia_inicio_rapido_jerkjs.md +113 -0
- package/examples/hooks/app.js +136 -0
- package/examples/hooks/controllers/authController.js +54 -0
- package/examples/hooks/controllers/mainController.js +41 -0
- package/examples/hooks/controllers/productController.js +39 -0
- package/examples/hooks/controllers/userController.js +69 -0
- package/examples/hooks/routes.json +51 -0
- package/examples/hooks/views/home.html +50 -0
- package/index.js +11 -2
- package/jerk.jpg +0 -0
- package/lib/core/router.js +1 -0
- package/lib/core/securityEnhancedServer.js +1 -0
- package/lib/core/server.js +31 -8
- package/lib/loader/routeLoader.js +1 -0
- package/lib/middleware/compressor.js +16 -10
- package/lib/mvc/viewEngine.js +1 -0
- package/lib/utils/errorHandler.js +118 -0
- package/lib/utils/logger.js +65 -0
- package/package.json +8 -5
- package/README.md.backup +0 -169
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aplicación de ejemplo usando el framework JERK
|
|
3
|
+
* Demostrando routes.json, controladores y sistema de hooks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const jerk = require('jerkjs');
|
|
7
|
+
const {
|
|
8
|
+
APIServer,
|
|
9
|
+
Logger,
|
|
10
|
+
RouteLoader,
|
|
11
|
+
hooks
|
|
12
|
+
} = jerk;
|
|
13
|
+
|
|
14
|
+
// Crear instancia del logger
|
|
15
|
+
const logger = new Logger({ level: 'info' });
|
|
16
|
+
|
|
17
|
+
// Array para almacenar los controladores disponibles
|
|
18
|
+
let availableControllers = [];
|
|
19
|
+
|
|
20
|
+
// Hook que se ejecuta cuando se carga un controlador
|
|
21
|
+
hooks.addAction('post_controller_load', (controllerModule, absolutePath) => {
|
|
22
|
+
logger.info(`[[HOOK]] - Controlador cargado: ${absolutePath}`);
|
|
23
|
+
|
|
24
|
+
// Extraer información sobre las funciones disponibles en el controlador
|
|
25
|
+
const controllerName = absolutePath.split('/').pop().replace('.js', '');
|
|
26
|
+
|
|
27
|
+
// Verificar si es un objeto con métodos o una instancia
|
|
28
|
+
let handlerNames = [];
|
|
29
|
+
if (typeof controllerModule === 'object') {
|
|
30
|
+
// Si es un objeto (como una instancia de ControllerBase), obtener sus métodos
|
|
31
|
+
handlerNames = Object.getOwnPropertyNames(Object.getPrototypeOf(controllerModule))
|
|
32
|
+
.filter(prop => typeof controllerModule[prop] === 'function' && prop !== 'constructor');
|
|
33
|
+
} else if (typeof controllerModule === 'function') {
|
|
34
|
+
// Si es una clase, instanciarla y obtener sus métodos
|
|
35
|
+
const instance = new controllerModule();
|
|
36
|
+
handlerNames = Object.getOwnPropertyNames(Object.getPrototypeOf(instance))
|
|
37
|
+
.filter(prop => typeof instance[prop] === 'function' && prop !== 'constructor');
|
|
38
|
+
} else {
|
|
39
|
+
// Si es un objeto con funciones directamente
|
|
40
|
+
handlerNames = Object.keys(controllerModule).filter(key =>
|
|
41
|
+
typeof controllerModule[key] === 'function'
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Almacenar información sobre el controlador
|
|
46
|
+
availableControllers.push({
|
|
47
|
+
name: controllerName,
|
|
48
|
+
path: absolutePath,
|
|
49
|
+
handlers: handlerNames
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
logger.info(`[[HOOK]] - Handlers disponibles en ${controllerName}:`, handlerNames);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Hook que se ejecuta antes de cargar rutas
|
|
56
|
+
hooks.addAction('pre_route_load', (filePath, serverInstance) => {
|
|
57
|
+
logger.info(`[[HOOK]] - A punto de cargar rutas desde: ${filePath}`);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Hook que se ejecuta después de cargar rutas
|
|
61
|
+
hooks.addAction('post_route_load', (routes, serverInstance) => {
|
|
62
|
+
logger.info(`[[HOOK]] - Rutas cargadas exitosamente: ${routes.length} rutas`);
|
|
63
|
+
routes.forEach((route, index) => {
|
|
64
|
+
logger.info(`[[HOOK]] - Ruta ${index + 1}: ${route.method} ${route.path} -> ${route.handlerName || 'anonymous'}`);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Hook para registrar accesos al servidor
|
|
69
|
+
hooks.addAction('request_received', (req, res) => {
|
|
70
|
+
logger.info(`[[ACCESS]] - Nuevo acceso recibido: ${req.method} ${req.url} desde ${req.connection.remoteAddress || 'unknown'}`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Hook para registrar descargas o finalización de solicitudes
|
|
74
|
+
hooks.addAction('request_completed', (req, res) => {
|
|
75
|
+
logger.info(`[[DOWNLOAD/COMPLETED]] - Solicitud completada: ${req.method} ${req.url}`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
async function startServer() {
|
|
79
|
+
// Crear instancia del servidor
|
|
80
|
+
const server = new APIServer({
|
|
81
|
+
port: 11000,
|
|
82
|
+
host: 'localhost'
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
// Middleware para capturar inicio de solicitudes
|
|
87
|
+
server.use((req, res, next) => {
|
|
88
|
+
// Disparar hook de solicitud recibida
|
|
89
|
+
hooks.doAction('request_received', req, res);
|
|
90
|
+
|
|
91
|
+
// Guardar la función original de res.end
|
|
92
|
+
const originalEnd = res.end;
|
|
93
|
+
|
|
94
|
+
// Sobrescribir res.end para capturar cuando se completa la solicitud
|
|
95
|
+
res.end = function(chunk, encoding, callback) {
|
|
96
|
+
// Llamar a la función original
|
|
97
|
+
const result = originalEnd.call(this, chunk, encoding, callback);
|
|
98
|
+
|
|
99
|
+
// Disparar hook de solicitud completada
|
|
100
|
+
hooks.doAction('request_completed', req, res);
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
next();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Cargar rutas desde archivo JSON
|
|
109
|
+
const routeLoader = new RouteLoader();
|
|
110
|
+
await routeLoader.loadRoutes(server, './routes.json');
|
|
111
|
+
|
|
112
|
+
// Mostrar los controladores disponibles (usando hooks)
|
|
113
|
+
logger.info('\n=== CONTROLADORES DISPONIBLES ===');
|
|
114
|
+
availableControllers.forEach((controller, index) => {
|
|
115
|
+
logger.info(`[[CONTROLLER ${index + 1}]] - Nombre: ${controller.name}`);
|
|
116
|
+
logger.info(`[[PATH]] - Ruta: ${controller.path}`);
|
|
117
|
+
logger.info(`[[HANDLERS]] - Funciones: ${controller.handlers.join(', ')}`);
|
|
118
|
+
logger.info('---');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Iniciar el servidor
|
|
122
|
+
server.start();
|
|
123
|
+
|
|
124
|
+
logger.info(`\nServidor iniciado en http://localhost:${server.port}`);
|
|
125
|
+
logger.info('La aplicación está lista para recibir solicitudes');
|
|
126
|
+
|
|
127
|
+
} catch (error) {
|
|
128
|
+
logger.error('Error iniciando el servidor:', error.message);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Iniciar el servidor
|
|
134
|
+
startServer();
|
|
135
|
+
|
|
136
|
+
module.exports = { startServer };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlador de autenticación (authController)
|
|
3
|
+
* Maneja operaciones de login
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Datos simulados de usuarios para autenticación
|
|
7
|
+
const validUsers = [
|
|
8
|
+
{ username: 'admin', password: 'admin123' },
|
|
9
|
+
{ username: 'user', password: 'user123' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
// Función de login
|
|
13
|
+
function login(req, res) {
|
|
14
|
+
let body = '';
|
|
15
|
+
|
|
16
|
+
req.on('data', chunk => {
|
|
17
|
+
body += chunk.toString();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
req.on('end', () => {
|
|
21
|
+
try {
|
|
22
|
+
const credentials = JSON.parse(body);
|
|
23
|
+
const user = validUsers.find(u =>
|
|
24
|
+
u.username === credentials.username &&
|
|
25
|
+
u.password === credentials.password
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (user) {
|
|
29
|
+
// Simular generación de token JWT
|
|
30
|
+
const token = `fake-jwt-token-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
31
|
+
|
|
32
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
33
|
+
res.end(JSON.stringify({
|
|
34
|
+
success: true,
|
|
35
|
+
token: token,
|
|
36
|
+
message: 'Login exitoso'
|
|
37
|
+
}));
|
|
38
|
+
} else {
|
|
39
|
+
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
40
|
+
res.end(JSON.stringify({
|
|
41
|
+
success: false,
|
|
42
|
+
message: 'Credenciales inválidas'
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
47
|
+
res.end(JSON.stringify({ error: 'Datos inválidos' }));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
login
|
|
54
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlador principal (mainController)
|
|
3
|
+
* Maneja la página de inicio
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ControllerBase } = require('jerkjs');
|
|
7
|
+
|
|
8
|
+
class MainController extends ControllerBase {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
super(options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Función para manejar la ruta de inicio
|
|
14
|
+
home(req, res) {
|
|
15
|
+
// Establecer variables para la vista
|
|
16
|
+
this.set('title', 'Bienvenido a JERK Framework');
|
|
17
|
+
this.set('message', 'Esta es una aplicación de ejemplo usando JERK Framework');
|
|
18
|
+
|
|
19
|
+
// Renderizar la vista usando el motor de plantillas de JERK
|
|
20
|
+
this.render(res, 'home', {
|
|
21
|
+
endpoints: [
|
|
22
|
+
{ method: 'GET', path: '/users', description: 'Obtener todos los usuarios' },
|
|
23
|
+
{ method: 'GET', path: '/users/:id', description: 'Obtener usuario por ID' },
|
|
24
|
+
{ method: 'POST', path: '/users', description: 'Crear nuevo usuario' },
|
|
25
|
+
{ method: 'GET', path: '/products', description: 'Obtener todos los productos' },
|
|
26
|
+
{ method: 'GET', path: '/products/:id', description: 'Obtener producto por ID' },
|
|
27
|
+
{ method: 'POST', path: '/login', description: 'Iniciar sesión' }
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Exportar métodos directamente para que el RouteLoader pueda acceder a ellos
|
|
34
|
+
const controllerInstance = new MainController({ viewsPath: './views' });
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
home: (req, res) => {
|
|
38
|
+
controllerInstance.setRequestResponse(req, res);
|
|
39
|
+
controllerInstance.home(req, res);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlador de productos (productController)
|
|
3
|
+
* Maneja operaciones CRUD de productos
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Datos simulados de productos
|
|
7
|
+
let products = [
|
|
8
|
+
{ id: 1, name: 'Laptop', price: 1200, category: 'Electrónica' },
|
|
9
|
+
{ id: 2, name: 'Teléfono', price: 800, category: 'Electrónica' },
|
|
10
|
+
{ id: 3, name: 'Libro', price: 15, category: 'Educación' }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Obtener todos los productos
|
|
14
|
+
function getAllProducts(req, res) {
|
|
15
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
16
|
+
res.end(JSON.stringify(products));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Obtener un producto por ID
|
|
20
|
+
function getProductById(req, res) {
|
|
21
|
+
// Obtener el ID de los parámetros de la ruta
|
|
22
|
+
const urlParts = req.url.split('/');
|
|
23
|
+
const productId = parseInt(urlParts[urlParts.length - 1]);
|
|
24
|
+
|
|
25
|
+
const product = products.find(p => p.id === productId);
|
|
26
|
+
|
|
27
|
+
if (product) {
|
|
28
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
29
|
+
res.end(JSON.stringify(product));
|
|
30
|
+
} else {
|
|
31
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
32
|
+
res.end(JSON.stringify({ error: 'Producto no encontrado' }));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = {
|
|
37
|
+
getAllProducts,
|
|
38
|
+
getProductById
|
|
39
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Controlador de usuarios (userController)
|
|
3
|
+
* Maneja operaciones CRUD de usuarios
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Datos simulados de usuarios
|
|
7
|
+
let users = [
|
|
8
|
+
{ id: 1, name: 'Juan Pérez', email: 'juan@example.com', age: 30 },
|
|
9
|
+
{ id: 2, name: 'María García', email: 'maria@example.com', age: 25 },
|
|
10
|
+
{ id: 3, name: 'Pedro Rodríguez', email: 'pedro@example.com', age: 35 }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
// Obtener todos los usuarios
|
|
14
|
+
function getAllUsers(req, res) {
|
|
15
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
16
|
+
res.end(JSON.stringify(users));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Obtener un usuario por ID
|
|
20
|
+
function getUserById(req, res) {
|
|
21
|
+
// Obtener el ID de los parámetros de la ruta
|
|
22
|
+
const urlParts = req.url.split('/');
|
|
23
|
+
const userId = parseInt(urlParts[urlParts.length - 1]);
|
|
24
|
+
|
|
25
|
+
const user = users.find(u => u.id === userId);
|
|
26
|
+
|
|
27
|
+
if (user) {
|
|
28
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
29
|
+
res.end(JSON.stringify(user));
|
|
30
|
+
} else {
|
|
31
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
32
|
+
res.end(JSON.stringify({ error: 'Usuario no encontrado' }));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Crear un nuevo usuario
|
|
37
|
+
function createUser(req, res) {
|
|
38
|
+
let body = '';
|
|
39
|
+
|
|
40
|
+
req.on('data', chunk => {
|
|
41
|
+
body += chunk.toString();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
req.on('end', () => {
|
|
45
|
+
try {
|
|
46
|
+
const userData = JSON.parse(body);
|
|
47
|
+
const newUser = {
|
|
48
|
+
id: users.length + 1,
|
|
49
|
+
name: userData.name,
|
|
50
|
+
email: userData.email,
|
|
51
|
+
age: userData.age
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
users.push(newUser);
|
|
55
|
+
|
|
56
|
+
res.writeHead(201, { 'Content-Type': 'application/json' });
|
|
57
|
+
res.end(JSON.stringify(newUser));
|
|
58
|
+
} catch (error) {
|
|
59
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
60
|
+
res.end(JSON.stringify({ error: 'Datos inválidos' }));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = {
|
|
66
|
+
getAllUsers,
|
|
67
|
+
getUserById,
|
|
68
|
+
createUser
|
|
69
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"method": "GET",
|
|
4
|
+
"path": "/",
|
|
5
|
+
"controller": "./controllers/mainController.js",
|
|
6
|
+
"handler": "home",
|
|
7
|
+
"auth": "none"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"method": "GET",
|
|
11
|
+
"path": "/users",
|
|
12
|
+
"controller": "./controllers/userController.js",
|
|
13
|
+
"handler": "getAllUsers",
|
|
14
|
+
"auth": "none"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"method": "GET",
|
|
18
|
+
"path": "/users/:id",
|
|
19
|
+
"controller": "./controllers/userController.js",
|
|
20
|
+
"handler": "getUserById",
|
|
21
|
+
"auth": "none"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"method": "POST",
|
|
25
|
+
"path": "/users",
|
|
26
|
+
"controller": "./controllers/userController.js",
|
|
27
|
+
"handler": "createUser",
|
|
28
|
+
"auth": "required"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"method": "GET",
|
|
32
|
+
"path": "/products",
|
|
33
|
+
"controller": "./controllers/productController.js",
|
|
34
|
+
"handler": "getAllProducts",
|
|
35
|
+
"auth": "none"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"method": "GET",
|
|
39
|
+
"path": "/products/:id",
|
|
40
|
+
"controller": "./controllers/productController.js",
|
|
41
|
+
"handler": "getProductById",
|
|
42
|
+
"auth": "none"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"method": "POST",
|
|
46
|
+
"path": "/login",
|
|
47
|
+
"controller": "./controllers/authController.js",
|
|
48
|
+
"handler": "login",
|
|
49
|
+
"auth": "none"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{title}}</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
margin: 40px;
|
|
11
|
+
background-color: #f5f5f5;
|
|
12
|
+
}
|
|
13
|
+
.container {
|
|
14
|
+
max-width: 800px;
|
|
15
|
+
margin: 0 auto;
|
|
16
|
+
background: white;
|
|
17
|
+
padding: 20px;
|
|
18
|
+
border-radius: 8px;
|
|
19
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
20
|
+
}
|
|
21
|
+
h1 {
|
|
22
|
+
color: #333;
|
|
23
|
+
}
|
|
24
|
+
.api-info {
|
|
25
|
+
margin-top: 20px;
|
|
26
|
+
padding: 15px;
|
|
27
|
+
background-color: #e7f3ff;
|
|
28
|
+
border-left: 4px solid #2196F3;
|
|
29
|
+
}
|
|
30
|
+
.endpoint-item {
|
|
31
|
+
margin: 5px 0;
|
|
32
|
+
}
|
|
33
|
+
</style>
|
|
34
|
+
</head>
|
|
35
|
+
<body>
|
|
36
|
+
<div class="container">
|
|
37
|
+
<h1>{{title}}</h1>
|
|
38
|
+
<p>{{message}}</p>
|
|
39
|
+
|
|
40
|
+
<div class="api-info">
|
|
41
|
+
<h2>Endpoints disponibles:</h2>
|
|
42
|
+
<ul>
|
|
43
|
+
{{foreach:endpoints}}
|
|
44
|
+
<li class="endpoint-item"><strong>{{item.method}}</strong> {{item.path}} - {{item.description}}</li>
|
|
45
|
+
{{endforeach}}
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Punto de entrada del framework JERK
|
|
3
|
-
* JERK Framework v2.
|
|
3
|
+
* JERK Framework v2.1
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const APIServer = require('./lib/core/server');
|
|
@@ -34,6 +34,9 @@ const { SessionManager, sessionAuth } = require('./lib/middleware/session');
|
|
|
34
34
|
const ViewEngine = require('./lib/mvc/viewEngine');
|
|
35
35
|
const ControllerBase = require('./lib/mvc/controllerBase');
|
|
36
36
|
|
|
37
|
+
// Componentes de manejo de errores
|
|
38
|
+
const { ErrorHandler, ValidationError, AuthenticationError, DatabaseError } = require('./lib/utils/errorHandler');
|
|
39
|
+
|
|
37
40
|
// Exportar todos los componentes del framework
|
|
38
41
|
module.exports = {
|
|
39
42
|
// Componentes fundamentales (v1.0)
|
|
@@ -72,7 +75,13 @@ module.exports = {
|
|
|
72
75
|
|
|
73
76
|
// Componentes MVC (v2.3.0)
|
|
74
77
|
ViewEngine,
|
|
75
|
-
ControllerBase
|
|
78
|
+
ControllerBase,
|
|
79
|
+
|
|
80
|
+
// Componentes de manejo de errores (v2.4.0)
|
|
81
|
+
ErrorHandler,
|
|
82
|
+
ValidationError,
|
|
83
|
+
AuthenticationError,
|
|
84
|
+
DatabaseError
|
|
76
85
|
};
|
|
77
86
|
|
|
78
87
|
// También exportar clases individuales por conveniencia
|
package/jerk.jpg
CHANGED
|
Binary file
|
package/lib/core/router.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Sistema de enrutamiento avanzado para el framework JERK
|
|
3
3
|
* Implementación extendida del componente core/router.js
|
|
4
4
|
* Incluye soporte para rutas anidadas y otras mejoras
|
|
5
|
+
* JERK Framework v2.1 - Con cacheo de expresiones regulares para rutas parametrizadas
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
class Router {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Implementación completa del sistema de seguridad avanzada (WAF)
|
|
3
3
|
* usando el sistema de hooks y filters para extensibilidad
|
|
4
4
|
* Web Application Firewall con capacidades de detección y prevención de ataques
|
|
5
|
+
* JERK Framework v2.1 - Con optimizaciones de rendimiento
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const APIServer = require('../core/server');
|
package/lib/core/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Servidor HTTP básico para el framework JERK
|
|
3
3
|
* Implementación del componente core/server.js
|
|
4
|
+
* JERK Framework v2.1 - Con optimizaciones de rendimiento
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const http = require('http');
|
|
@@ -8,6 +9,7 @@ const https = require('https');
|
|
|
8
9
|
const url = require('url');
|
|
9
10
|
const fs = require('fs');
|
|
10
11
|
const { Logger } = require('../utils/logger');
|
|
12
|
+
const { ErrorHandler } = require('../utils/errorHandler');
|
|
11
13
|
|
|
12
14
|
class APIServer {
|
|
13
15
|
/**
|
|
@@ -42,6 +44,9 @@ class APIServer {
|
|
|
42
44
|
this.middlewares = [];
|
|
43
45
|
this.logger = new Logger();
|
|
44
46
|
this.server = null;
|
|
47
|
+
|
|
48
|
+
// Cache de expresiones regulares para rutas parametrizadas
|
|
49
|
+
this.routeRegexCache = new Map();
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
/**
|
|
@@ -111,8 +116,12 @@ class APIServer {
|
|
|
111
116
|
* @returns {RegExp} - Expresión regular para la ruta
|
|
112
117
|
*/
|
|
113
118
|
pathToRegex(path) {
|
|
114
|
-
//
|
|
115
|
-
|
|
119
|
+
// Verificar si ya está en caché
|
|
120
|
+
if (this.routeRegexCache.has(path)) {
|
|
121
|
+
return this.routeRegexCache.get(path);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Lógica de escape de caracteres especiales
|
|
116
125
|
let escapedPath = '';
|
|
117
126
|
for (let i = 0; i < path.length; i++) {
|
|
118
127
|
const char = path[i];
|
|
@@ -124,7 +133,11 @@ class APIServer {
|
|
|
124
133
|
}
|
|
125
134
|
// Reemplazar parámetros :param con grupos de captura no greedy para evitar problemas de matching
|
|
126
135
|
const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
|
|
127
|
-
|
|
136
|
+
const regex = new RegExp(`^${regexPath}$`);
|
|
137
|
+
|
|
138
|
+
// Almacenar en caché
|
|
139
|
+
this.routeRegexCache.set(path, regex);
|
|
140
|
+
return regex;
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
/**
|
|
@@ -177,6 +190,10 @@ class APIServer {
|
|
|
177
190
|
this.server = http.createServer(this.handleRequest.bind(this));
|
|
178
191
|
}
|
|
179
192
|
|
|
193
|
+
// Configurar keep-alive para conexiones persistentes
|
|
194
|
+
this.server.keepAliveTimeout = 60000; // 60 segundos
|
|
195
|
+
this.server.headersTimeout = 65000; // 65 segundos (mayor que keepAliveTimeout)
|
|
196
|
+
|
|
180
197
|
// Configurar timeouts
|
|
181
198
|
this.server.setTimeout(this.requestTimeout);
|
|
182
199
|
this.server.on('timeout', (socket) => {
|
|
@@ -200,6 +217,9 @@ class APIServer {
|
|
|
200
217
|
this.logger.info(`Servidor iniciado en http://${this.host}:${this.port}`);
|
|
201
218
|
}
|
|
202
219
|
|
|
220
|
+
// Verificar uso de memoria en el hook de inicio
|
|
221
|
+
this.logger.logMemoryUsage();
|
|
222
|
+
|
|
203
223
|
// Disparar hook después de iniciar el servidor
|
|
204
224
|
if (hooks) {
|
|
205
225
|
hooks.doAction('post_server_start', this);
|
|
@@ -232,7 +252,9 @@ class APIServer {
|
|
|
232
252
|
// Agregar propiedades útiles a la solicitud
|
|
233
253
|
req.query = query;
|
|
234
254
|
req.params = {};
|
|
235
|
-
|
|
255
|
+
|
|
256
|
+
// Inicializar array para acumular chunks del body
|
|
257
|
+
const bodyChunks = [];
|
|
236
258
|
|
|
237
259
|
// Configurar límite de tamaño para el cuerpo de la solicitud desde configuración
|
|
238
260
|
let bodySize = 0;
|
|
@@ -246,11 +268,14 @@ class APIServer {
|
|
|
246
268
|
req.destroy(); // Terminar la conexión
|
|
247
269
|
return;
|
|
248
270
|
}
|
|
249
|
-
|
|
271
|
+
bodyChunks.push(chunk);
|
|
250
272
|
});
|
|
251
273
|
|
|
252
274
|
req.on('end', async () => {
|
|
253
275
|
try {
|
|
276
|
+
// Concatenar todos los chunks una sola vez
|
|
277
|
+
req.body = Buffer.concat(bodyChunks).toString();
|
|
278
|
+
|
|
254
279
|
// Parsear body si es JSON
|
|
255
280
|
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
256
281
|
try {
|
|
@@ -358,9 +383,7 @@ class APIServer {
|
|
|
358
383
|
}
|
|
359
384
|
}
|
|
360
385
|
} catch (error) {
|
|
361
|
-
|
|
362
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
363
|
-
res.end(JSON.stringify({ error: 'Error interno del servidor', details: error.message }));
|
|
386
|
+
ErrorHandler.handle(error, req, res, this.logger);
|
|
364
387
|
}
|
|
365
388
|
});
|
|
366
389
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Middleware de compresión para el framework JERK
|
|
3
3
|
* Implementación del componente middleware/compressor.js
|
|
4
|
+
* JERK Framework v2.1 - Con optimizaciones de eficiencia
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
const zlib = require('zlib');
|
|
@@ -53,21 +54,26 @@ class Compressor {
|
|
|
53
54
|
const originalEnd = res.end;
|
|
54
55
|
const originalWriteHead = res.writeHead;
|
|
55
56
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// Sobrescribir res.write para capturar
|
|
57
|
+
// Array para almacenar los chunks de la respuesta
|
|
58
|
+
const responseChunks = [];
|
|
59
|
+
|
|
60
|
+
// Sobrescribir res.write para capturar los chunks
|
|
60
61
|
res.write = (chunk, encoding) => {
|
|
61
|
-
|
|
62
|
+
responseChunks.push(chunk);
|
|
62
63
|
};
|
|
63
64
|
|
|
64
65
|
// Sobrescribir res.end para comprimir antes de enviar
|
|
65
66
|
res.end = (chunk, encoding) => {
|
|
66
|
-
// Añadir el chunk final
|
|
67
|
+
// Añadir el chunk final a los chunks si existe
|
|
67
68
|
if (chunk) {
|
|
68
|
-
|
|
69
|
+
responseChunks.push(chunk);
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
// Concatenar todos los chunks una sola vez
|
|
73
|
+
const responseBody = Buffer.concat(responseChunks.map(c =>
|
|
74
|
+
typeof c === 'string' ? Buffer.from(c, encoding) : c
|
|
75
|
+
)).toString();
|
|
76
|
+
|
|
71
77
|
// Si el cuerpo es menor que el umbral, enviar sin comprimir
|
|
72
78
|
if (Buffer.byteLength(responseBody) < this.threshold) {
|
|
73
79
|
res.removeHeader('Content-Encoding'); // Asegurar que no haya encabezado de codificación
|
|
@@ -108,10 +114,10 @@ class Compressor {
|
|
|
108
114
|
// Establecer encabezados apropiados
|
|
109
115
|
res.setHeader('Content-Encoding', compressionMethod);
|
|
110
116
|
res.removeHeader('Content-Length'); // Eliminar Content-Length original
|
|
111
|
-
|
|
117
|
+
|
|
112
118
|
// Llamar al writeHead original
|
|
113
119
|
originalWriteHead.call(res);
|
|
114
|
-
|
|
120
|
+
|
|
115
121
|
// Enviar el cuerpo comprimido
|
|
116
122
|
originalEnd.call(res, compressed, encoding);
|
|
117
123
|
})
|
|
@@ -145,7 +151,7 @@ class Compressor {
|
|
|
145
151
|
if (contentType && contentType.includes('application/json')) {
|
|
146
152
|
// Convertir a string si no lo es
|
|
147
153
|
const jsonString = typeof data === 'string' ? data : JSON.stringify(data);
|
|
148
|
-
|
|
154
|
+
|
|
149
155
|
// Continuar con la lógica de compresión
|
|
150
156
|
// (similar a la implementación en middleware())
|
|
151
157
|
const acceptEncoding = req.headers['accept-encoding'];
|