jerkjs 2.0.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 +200 -0
- package/README.md +171 -0
- package/doc/EXTENSION_MANUAL.md +958 -0
- package/doc/FIREWALL_MANUAL.md +419 -0
- package/doc/HOOKS_REFERENCE_IMPROVED.md +599 -0
- package/doc/MANUAL_API_SDK.md +539 -0
- package/doc/MANUAL_MVC.md +397 -0
- package/doc/MARIADB_TOKENS_IMPLEMENTATION.md +113 -0
- package/doc/MIDDLEWARE_MANUAL.md +521 -0
- package/doc/OAUTH2_GOOGLE_MANUAL.md +408 -0
- package/doc/frontend-and-sessions.md +356 -0
- package/examples/advanced/controllers/productController.js +64 -0
- package/examples/advanced/controllers/userController.js +85 -0
- package/examples/advanced/routes.json +51 -0
- package/examples/advanced_example.js +93 -0
- package/examples/basic/controllers/userController.js +85 -0
- package/examples/basic_example.js +72 -0
- package/examples/frontend/README.md +71 -0
- package/examples/frontend/app.js +71 -0
- package/examples/frontend/controllers/apiController.js +39 -0
- package/examples/frontend/controllers/authController.js +220 -0
- package/examples/frontend/controllers/formController.js +47 -0
- package/examples/frontend/controllers/messageController.js +96 -0
- package/examples/frontend/controllers/pageController.js +178 -0
- package/examples/frontend/controllers/staticController.js +167 -0
- package/examples/frontend/routes.json +90 -0
- package/examples/mvc_example/app.js +138 -0
- package/examples/mvc_example/views/home/index.html +26 -0
- package/examples/mvc_example/views/home/simple.html +3 -0
- package/examples/mvc_example/views/layout.html +23 -0
- package/examples/mvc_example/views/test.html +3 -0
- package/examples/mvc_example/views/user/invalid.html +6 -0
- package/examples/mvc_example/views/user/list.html +36 -0
- package/examples/mvc_example/views/user/notfound.html +6 -0
- package/examples/mvc_example/views/user/profile.html +11 -0
- package/examples/mvc_routes_example/app.js +34 -0
- package/examples/mvc_routes_example/controllers/mainController.js +27 -0
- package/examples/mvc_routes_example/controllers/productController.js +47 -0
- package/examples/mvc_routes_example/controllers/userController.js +76 -0
- package/examples/mvc_routes_example/routes.json +30 -0
- package/examples/mvc_routes_example/views/layout.html +31 -0
- package/examples/mvc_routes_example/views/main/index.html +11 -0
- package/examples/mvc_routes_example/views/product/catalog.html +24 -0
- package/examples/mvc_routes_example/views/user/invalid.html +6 -0
- package/examples/mvc_routes_example/views/user/list.html +40 -0
- package/examples/mvc_routes_example/views/user/notfound.html +6 -0
- package/examples/mvc_routes_example/views/user/profile.html +18 -0
- package/examples/public/README.md +92 -0
- package/examples/public/app.js +72 -0
- package/examples/public/controllers/healthController.js +20 -0
- package/examples/public/controllers/mainController.js +22 -0
- package/examples/public/controllers/userController.js +139 -0
- package/examples/public/routes.json +51 -0
- package/examples/v2/README.md +72 -0
- package/examples/v2/app.js +74 -0
- package/examples/v2/app_fixed.js +74 -0
- package/examples/v2/controllers/authController.js +64 -0
- package/examples/v2/controllers/mainController.js +24 -0
- package/examples/v2/controllers/protectedController.js +12 -0
- package/examples/v2/controllers/userController.js +16 -0
- package/examples/v2/package.json +27 -0
- package/examples/v2/routes.json +30 -0
- package/examples/v2/test_api.sh +47 -0
- package/examples/v2/tokens_example.sqlite +0 -0
- package/examples/v2.1_firewall_demo/README.md +113 -0
- package/examples/v2.1_firewall_demo/app.js +182 -0
- package/examples/v2.1_firewall_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/README.md +85 -0
- package/examples/v2.1_hooks_demo/app.js +101 -0
- package/examples/v2.1_hooks_demo/controllers/hooksController.js +29 -0
- package/examples/v2.1_hooks_demo/controllers/mainController.js +18 -0
- package/examples/v2.1_hooks_demo/package.json +27 -0
- package/examples/v2.1_hooks_demo/routes.json +16 -0
- package/examples/v2.1_openapi_demo/README.md +82 -0
- package/examples/v2.1_openapi_demo/app.js +296 -0
- package/examples/v2.1_openapi_demo/package.json +26 -0
- package/examples/v2_cors/README.md +82 -0
- package/examples/v2_cors/app.js +108 -0
- package/examples/v2_cors/package.json +23 -0
- package/examples/v2_json_auth/README.md +83 -0
- package/examples/v2_json_auth/app.js +72 -0
- package/examples/v2_json_auth/controllers/authController.js +67 -0
- package/examples/v2_json_auth/controllers/mainController.js +16 -0
- package/examples/v2_json_auth/controllers/protectedController.js +12 -0
- package/examples/v2_json_auth/controllers/tokenController.js +28 -0
- package/examples/v2_json_auth/controllers/userController.js +15 -0
- package/examples/v2_json_auth/package.json +26 -0
- package/examples/v2_json_auth/routes.json +37 -0
- package/examples/v2_json_auth/tokens.json +20 -0
- package/examples/v2_mariadb_auth/README.md +94 -0
- package/examples/v2_mariadb_auth/app.js +81 -0
- package/examples/v2_mariadb_auth/controllers/authController.js +95 -0
- package/examples/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/examples/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/examples/v2_mariadb_auth/controllers/userController.js +17 -0
- package/examples/v2_mariadb_auth/package.json +27 -0
- package/examples/v2_mariadb_auth/routes.json +37 -0
- package/examples/v2_no_auth/README.md +75 -0
- package/examples/v2_no_auth/app.js +72 -0
- package/examples/v2_no_auth/controllers/healthController.js +14 -0
- package/examples/v2_no_auth/controllers/mainController.js +19 -0
- package/examples/v2_no_auth/controllers/productController.js +31 -0
- package/examples/v2_no_auth/controllers/publicController.js +16 -0
- package/examples/v2_no_auth/package.json +22 -0
- package/examples/v2_no_auth/routes.json +37 -0
- package/examples/v2_oauth/README.md +70 -0
- package/examples/v2_oauth/app.js +90 -0
- package/examples/v2_oauth/controllers/mainController.js +45 -0
- package/examples/v2_oauth/controllers/oauthController.js +247 -0
- package/examples/v2_oauth/controllers/protectedController.js +13 -0
- package/examples/v2_oauth/controllers/userController.js +17 -0
- package/examples/v2_oauth/package.json +26 -0
- package/examples/v2_oauth/routes.json +44 -0
- package/examples/v2_openapi/README.md +77 -0
- package/examples/v2_openapi/app.js +222 -0
- package/examples/v2_openapi/controllers/authController.js +52 -0
- package/examples/v2_openapi/controllers/mainController.js +26 -0
- package/examples/v2_openapi/controllers/productController.js +17 -0
- package/examples/v2_openapi/controllers/userController.js +27 -0
- package/examples/v2_openapi/package.json +26 -0
- package/examples/v2_openapi/routes.json +37 -0
- package/generate_token.js +10 -0
- package/index.js +85 -0
- package/jerk.jpg +0 -0
- package/lib/core/handler.js +86 -0
- package/lib/core/hooks.js +224 -0
- package/lib/core/router.js +204 -0
- package/lib/core/securityEnhancedServer.js +752 -0
- package/lib/core/server.js +369 -0
- package/lib/loader/controllerLoader.js +175 -0
- package/lib/loader/routeLoader.js +341 -0
- package/lib/middleware/auditLogger.js +208 -0
- package/lib/middleware/authenticator.js +565 -0
- package/lib/middleware/compressor.js +218 -0
- package/lib/middleware/cors.js +135 -0
- package/lib/middleware/firewall.js +443 -0
- package/lib/middleware/rateLimiter.js +210 -0
- package/lib/middleware/session.js +301 -0
- package/lib/middleware/validator.js +193 -0
- package/lib/mvc/controllerBase.js +207 -0
- package/lib/mvc/viewEngine.js +752 -0
- package/lib/utils/configParser.js +223 -0
- package/lib/utils/logger.js +145 -0
- package/lib/utils/mariadbTokenAdapter.js +226 -0
- package/lib/utils/openapiGenerator.js +140 -0
- package/lib/utils/sqliteTokenAdapter.js +224 -0
- package/lib/utils/tokenManager.js +254 -0
- package/package.json +47 -0
- package/v2examplle/v2_json_auth/README.md +83 -0
- package/v2examplle/v2_json_auth/app.js +72 -0
- package/v2examplle/v2_json_auth/controllers/authController.js +67 -0
- package/v2examplle/v2_json_auth/controllers/mainController.js +16 -0
- package/v2examplle/v2_json_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_json_auth/controllers/tokenController.js +28 -0
- package/v2examplle/v2_json_auth/controllers/userController.js +15 -0
- package/v2examplle/v2_json_auth/package.json +26 -0
- package/v2examplle/v2_json_auth/routes.json +37 -0
- package/v2examplle/v2_json_auth/tokens.json +20 -0
- package/v2examplle/v2_mariadb_auth/README.md +94 -0
- package/v2examplle/v2_mariadb_auth/app.js +81 -0
- package/v2examplle/v2_mariadb_auth/controllers/authController.js +95 -0
- package/v2examplle/v2_mariadb_auth/controllers/mainController.js +31 -0
- package/v2examplle/v2_mariadb_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_mariadb_auth/controllers/userController.js +17 -0
- package/v2examplle/v2_mariadb_auth/package.json +27 -0
- package/v2examplle/v2_mariadb_auth/routes.json +37 -0
- package/v2examplle/v2_sqlite_auth/README.md +72 -0
- package/v2examplle/v2_sqlite_auth/app.js +74 -0
- package/v2examplle/v2_sqlite_auth/app_fixed.js +74 -0
- package/v2examplle/v2_sqlite_auth/controllers/authController.js +64 -0
- package/v2examplle/v2_sqlite_auth/controllers/mainController.js +24 -0
- package/v2examplle/v2_sqlite_auth/controllers/protectedController.js +12 -0
- package/v2examplle/v2_sqlite_auth/controllers/userController.js +16 -0
- package/v2examplle/v2_sqlite_auth/package.json +27 -0
- package/v2examplle/v2_sqlite_auth/routes.json +30 -0
- package/v2examplle/v2_sqlite_auth/test_api.sh +47 -0
- package/v2examplle/v2_sqlite_auth/tokens_example.sqlite +0 -0
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Servidor HTTP básico para el framework API SDK
|
|
3
|
+
* Implementación del componente core/server.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const http = require('http');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const url = require('url');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const { Logger } = require('../utils/logger');
|
|
11
|
+
|
|
12
|
+
class APIServer {
|
|
13
|
+
/**
|
|
14
|
+
* Constructor del servidor
|
|
15
|
+
* @param {Object} options - Opciones de configuración del servidor
|
|
16
|
+
* @param {number} options.port - Puerto donde escuchará el servidor
|
|
17
|
+
* @param {string} options.host - Host donde escuchará el servidor
|
|
18
|
+
* @param {boolean} options.https - Habilitar servidor HTTPS
|
|
19
|
+
* @param {string} options.key - Ruta al archivo de clave privada para HTTPS
|
|
20
|
+
* @param {string} options.cert - Ruta al archivo de certificado para HTTPS
|
|
21
|
+
* @param {number} options.requestTimeout - Timeout para solicitudes en milisegundos
|
|
22
|
+
* @param {number} options.connectionTimeout - Timeout para conexiones en milisegundos
|
|
23
|
+
*/
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.port = options.port || 3000;
|
|
26
|
+
this.host = options.host || 'localhost';
|
|
27
|
+
this.https = options.https || false;
|
|
28
|
+
this.httpsOptions = {};
|
|
29
|
+
|
|
30
|
+
if (options.key && options.cert) {
|
|
31
|
+
this.httpsOptions = {
|
|
32
|
+
key: fs.readFileSync(options.key),
|
|
33
|
+
cert: fs.readFileSync(options.cert)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.requestTimeout = options.requestTimeout || 120000; // 2 minutos por defecto
|
|
38
|
+
this.connectionTimeout = options.connectionTimeout || 120000; // 2 minutos por defecto
|
|
39
|
+
this.maxBodySize = options.maxBodySize || 10 * 1024 * 1024; // 10MB por defecto
|
|
40
|
+
|
|
41
|
+
this.routes = [];
|
|
42
|
+
this.middlewares = [];
|
|
43
|
+
this.logger = new Logger();
|
|
44
|
+
this.server = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Método para agregar una ruta al servidor
|
|
49
|
+
* @param {string} method - Método HTTP (GET, POST, PUT, DELETE, etc.)
|
|
50
|
+
* @param {string} path - Ruta del endpoint
|
|
51
|
+
* @param {Function} handler - Función manejadora de la ruta
|
|
52
|
+
*/
|
|
53
|
+
addRoute(method, path, handler) {
|
|
54
|
+
this.routes.push({
|
|
55
|
+
method: method.toUpperCase(),
|
|
56
|
+
path,
|
|
57
|
+
handler
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Método para agregar middleware
|
|
63
|
+
* @param {Function} middleware - Función de middleware
|
|
64
|
+
*/
|
|
65
|
+
use(middleware) {
|
|
66
|
+
this.middlewares.push(middleware);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Método para encontrar una ruta coincidente
|
|
71
|
+
* @param {string} method - Método HTTP
|
|
72
|
+
* @param {string} pathname - Ruta a buscar
|
|
73
|
+
* @returns {Object|null} - Objeto de ruta encontrado o null
|
|
74
|
+
*/
|
|
75
|
+
findRoute(method, pathname) {
|
|
76
|
+
// Buscar ruta exacta primero
|
|
77
|
+
const exactMatch = this.routes.find(route =>
|
|
78
|
+
route.method === method && route.path === pathname
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (exactMatch) {
|
|
82
|
+
return {
|
|
83
|
+
route: exactMatch,
|
|
84
|
+
params: {}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Buscar rutas parametrizadas
|
|
89
|
+
for (const route of this.routes) {
|
|
90
|
+
if (route.method !== method) continue;
|
|
91
|
+
|
|
92
|
+
// Convertir ruta parametrizada a expresión regular
|
|
93
|
+
const routeRegex = this.pathToRegex(route.path);
|
|
94
|
+
const match = pathname.match(routeRegex);
|
|
95
|
+
|
|
96
|
+
if (match) {
|
|
97
|
+
const params = this.extractParams(route.path, pathname);
|
|
98
|
+
return {
|
|
99
|
+
route: route,
|
|
100
|
+
params
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Convierte una ruta con parámetros a expresión regular
|
|
110
|
+
* @param {string} path - Ruta con posibles parámetros
|
|
111
|
+
* @returns {RegExp} - Expresión regular para la ruta
|
|
112
|
+
*/
|
|
113
|
+
pathToRegex(path) {
|
|
114
|
+
// Escapar caracteres especiales de la ruta, excepto los parámetros
|
|
115
|
+
// Pero dejar : sin escapar ya que lo usaremos para identificar parámetros
|
|
116
|
+
let escapedPath = '';
|
|
117
|
+
for (let i = 0; i < path.length; i++) {
|
|
118
|
+
const char = path[i];
|
|
119
|
+
if (char.match(/[.+?^${}()|[\]\\]/) && !(i > 0 && path[i-1] === ':')) {
|
|
120
|
+
escapedPath += '\\' + char;
|
|
121
|
+
} else {
|
|
122
|
+
escapedPath += char;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Reemplazar parámetros :param con grupos de captura no greedy para evitar problemas de matching
|
|
126
|
+
const regexPath = escapedPath.replace(/:([a-zA-Z0-9_]+)/g, '([^/]+?)');
|
|
127
|
+
return new RegExp(`^${regexPath}$`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Extrae los parámetros de una ruta parametrizada
|
|
132
|
+
* @param {string} routePath - Ruta con parámetros (ej. /users/:id)
|
|
133
|
+
* @param {string} actualPath - Ruta real solicitada
|
|
134
|
+
* @returns {Object} - Objeto con los parámetros extraídos
|
|
135
|
+
*/
|
|
136
|
+
extractParams(routePath, actualPath) {
|
|
137
|
+
const params = {};
|
|
138
|
+
|
|
139
|
+
// Expresión regular para encontrar parámetros en la ruta
|
|
140
|
+
const paramNames = [];
|
|
141
|
+
const paramNameRegex = /:([a-zA-Z0-9_]+)/g;
|
|
142
|
+
let match;
|
|
143
|
+
|
|
144
|
+
while ((match = paramNameRegex.exec(routePath)) !== null) {
|
|
145
|
+
paramNames.push(match[1]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Crear expresión regular para extraer valores
|
|
149
|
+
const routeRegex = this.pathToRegex(routePath);
|
|
150
|
+
const values = actualPath.match(routeRegex);
|
|
151
|
+
|
|
152
|
+
if (values) {
|
|
153
|
+
// El primer elemento es la cadena completa, los demás son los valores capturados
|
|
154
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
155
|
+
if (values[i + 1]) {
|
|
156
|
+
params[paramNames[i]] = values[i + 1];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return params;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Inicia el servidor
|
|
166
|
+
*/
|
|
167
|
+
start() {
|
|
168
|
+
// Disparar hook antes de iniciar el servidor
|
|
169
|
+
const hooks = require('../../index.js').hooks;
|
|
170
|
+
if (hooks) {
|
|
171
|
+
hooks.doAction('pre_server_start', this);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (this.https && Object.keys(this.httpsOptions).length > 0) {
|
|
175
|
+
this.server = https.createServer(this.httpsOptions, this.handleRequest.bind(this));
|
|
176
|
+
} else {
|
|
177
|
+
this.server = http.createServer(this.handleRequest.bind(this));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Configurar timeouts
|
|
181
|
+
this.server.setTimeout(this.requestTimeout);
|
|
182
|
+
this.server.on('timeout', (socket) => {
|
|
183
|
+
this.logger.warn('Conexión expirada por timeout');
|
|
184
|
+
socket.end();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.server.on('connection', (socket) => {
|
|
188
|
+
// Configurar timeout de conexión
|
|
189
|
+
socket.setTimeout(this.connectionTimeout);
|
|
190
|
+
socket.on('timeout', () => {
|
|
191
|
+
this.logger.warn('Socket expirado por timeout');
|
|
192
|
+
socket.destroy();
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
this.server.listen(this.port, this.host, () => {
|
|
197
|
+
if (this.https && Object.keys(this.httpsOptions).length > 0) {
|
|
198
|
+
this.logger.info(`Servidor iniciado en https://${this.host}:${this.port}`);
|
|
199
|
+
} else {
|
|
200
|
+
this.logger.info(`Servidor iniciado en http://${this.host}:${this.port}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Disparar hook después de iniciar el servidor
|
|
204
|
+
if (hooks) {
|
|
205
|
+
hooks.doAction('post_server_start', this);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return this.server;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Detiene el servidor
|
|
214
|
+
*/
|
|
215
|
+
stop() {
|
|
216
|
+
if (this.server) {
|
|
217
|
+
this.server.close(() => {
|
|
218
|
+
this.logger.info('Servidor detenido');
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Maneja las solicitudes entrantes
|
|
225
|
+
* @param {Object} req - Objeto de solicitud HTTP
|
|
226
|
+
* @param {Object} res - Objeto de respuesta HTTP
|
|
227
|
+
*/
|
|
228
|
+
async handleRequest(req, res) {
|
|
229
|
+
const parsedUrl = url.parse(req.url, true);
|
|
230
|
+
const { pathname, query } = parsedUrl;
|
|
231
|
+
|
|
232
|
+
// Agregar propiedades útiles a la solicitud
|
|
233
|
+
req.query = query;
|
|
234
|
+
req.params = {};
|
|
235
|
+
req.body = '';
|
|
236
|
+
|
|
237
|
+
// Configurar límite de tamaño para el cuerpo de la solicitud desde configuración
|
|
238
|
+
let bodySize = 0;
|
|
239
|
+
|
|
240
|
+
// Capturar cuerpo de la solicitud con límite de tamaño
|
|
241
|
+
req.on('data', chunk => {
|
|
242
|
+
bodySize += chunk.length;
|
|
243
|
+
if (bodySize > this.maxBodySize) {
|
|
244
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
245
|
+
res.end(JSON.stringify({ error: 'Solicitud demasiado grande', details: `El cuerpo de la solicitud excede el límite permitido de ${this.maxBodySize} bytes` }));
|
|
246
|
+
req.destroy(); // Terminar la conexión
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
req.body += chunk.toString();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
req.on('end', async () => {
|
|
253
|
+
try {
|
|
254
|
+
// Parsear body si es JSON
|
|
255
|
+
if (req.headers['content-type'] && req.headers['content-type'].includes('application/json')) {
|
|
256
|
+
try {
|
|
257
|
+
req.body = JSON.parse(req.body);
|
|
258
|
+
} catch (e) {
|
|
259
|
+
req.body = {};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Verificar si es una solicitud OPTIONS preflight
|
|
264
|
+
// Si lo es, podríamos tener un middleware especial para manejarla
|
|
265
|
+
if (req.method === 'OPTIONS') {
|
|
266
|
+
// Ejecutar middlewares para manejar la solicitud OPTIONS (como CORS)
|
|
267
|
+
for (const middleware of this.middlewares) {
|
|
268
|
+
// Verificar si el middleware es una función antes de ejecutarla
|
|
269
|
+
if (typeof middleware === 'function') {
|
|
270
|
+
// Verificar si el middleware tiene firma (req, res, next)
|
|
271
|
+
if (middleware.length === 3) {
|
|
272
|
+
// Middleware con next
|
|
273
|
+
await new Promise((resolve, reject) => {
|
|
274
|
+
const next = (err) => {
|
|
275
|
+
if (err) {
|
|
276
|
+
reject(err);
|
|
277
|
+
} else {
|
|
278
|
+
resolve();
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const result = middleware(req, res, next);
|
|
282
|
+
// Si el middleware devuelve una promesa, esperarla
|
|
283
|
+
if (result && typeof result.then === 'function') {
|
|
284
|
+
result.then(resolve).catch(reject);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
} else {
|
|
288
|
+
// Middleware sin next
|
|
289
|
+
await middleware(req, res);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (res.finished) return; // Si el middleware respondió, salir
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Si después de ejecutar los middlewares la respuesta no se ha terminado,
|
|
297
|
+
// buscar una ruta específica o manejar como preflight genérico
|
|
298
|
+
const matchedRoute = this.findRoute(req.method, pathname);
|
|
299
|
+
if (matchedRoute) {
|
|
300
|
+
// Agregar parámetros a la solicitud
|
|
301
|
+
req.params = matchedRoute.params;
|
|
302
|
+
// Ejecutar handler de la ruta
|
|
303
|
+
await matchedRoute.route.handler(req, res);
|
|
304
|
+
} else {
|
|
305
|
+
// Para solicitudes OPTIONS que no tienen un handler específico,
|
|
306
|
+
// pero ya fueron manejadas por el middleware CORS, simplemente terminar
|
|
307
|
+
if (!res.finished) {
|
|
308
|
+
// Si no hay ruta específica para OPTIONS pero el middleware no respondió,
|
|
309
|
+
// devolver 204 para cumplir con CORS preflight
|
|
310
|
+
res.writeHead(204);
|
|
311
|
+
res.end();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
// Para otros métodos, seguir la lógica original
|
|
316
|
+
const matchedRoute = this.findRoute(req.method, pathname);
|
|
317
|
+
|
|
318
|
+
if (matchedRoute) {
|
|
319
|
+
// Agregar parámetros a la solicitud
|
|
320
|
+
req.params = matchedRoute.params;
|
|
321
|
+
|
|
322
|
+
// Ejecutar middlewares
|
|
323
|
+
for (const middleware of this.middlewares) {
|
|
324
|
+
// Verificar si el middleware es una función antes de ejecutarla
|
|
325
|
+
if (typeof middleware === 'function') {
|
|
326
|
+
// Verificar si el middleware tiene firma (req, res, next)
|
|
327
|
+
if (middleware.length === 3) {
|
|
328
|
+
// Middleware con next
|
|
329
|
+
await new Promise((resolve, reject) => {
|
|
330
|
+
const next = (err) => {
|
|
331
|
+
if (err) {
|
|
332
|
+
reject(err);
|
|
333
|
+
} else {
|
|
334
|
+
resolve();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
const result = middleware(req, res, next);
|
|
338
|
+
// Si el middleware devuelve una promesa, esperarla
|
|
339
|
+
if (result && typeof result.then === 'function') {
|
|
340
|
+
result.then(resolve).catch(reject);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
} else {
|
|
344
|
+
// Middleware sin next
|
|
345
|
+
await middleware(req, res);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (res.finished) return; // Si el middleware respondió, salir
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Ejecutar handler de la ruta
|
|
353
|
+
await matchedRoute.route.handler(req, res);
|
|
354
|
+
} else {
|
|
355
|
+
// Ruta no encontrada
|
|
356
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
357
|
+
res.end(JSON.stringify({ error: 'Ruta no encontrada', path: pathname }));
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
} catch (error) {
|
|
361
|
+
this.logger.error('Error procesando la solicitud:', error.message);
|
|
362
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
363
|
+
res.end(JSON.stringify({ error: 'Error interno del servidor', details: error.message }));
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
module.exports = APIServer;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carga de controladores para el framework API SDK
|
|
3
|
+
* Implementación del componente loader/controllerLoader.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
class ControllerLoader {
|
|
10
|
+
/**
|
|
11
|
+
* Constructor del cargador de controladores
|
|
12
|
+
*/
|
|
13
|
+
constructor() {
|
|
14
|
+
this.loadedControllers = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Método para cargar un controlador desde un archivo
|
|
19
|
+
* @param {string} controllerPath - Ruta al archivo del controlador
|
|
20
|
+
* @returns {Object} - Módulo del controlador cargado
|
|
21
|
+
*/
|
|
22
|
+
loadController(controllerPath) {
|
|
23
|
+
try {
|
|
24
|
+
// Disparar hook antes de cargar el controlador
|
|
25
|
+
const hooks = require('../../index.js').hooks;
|
|
26
|
+
if (hooks) {
|
|
27
|
+
hooks.doAction('pre_controller_load', controllerPath);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Resolver la ruta absoluta
|
|
31
|
+
const absolutePath = path.resolve(process.cwd(), controllerPath);
|
|
32
|
+
|
|
33
|
+
// Verificar si el archivo existe
|
|
34
|
+
if (!fs.existsSync(absolutePath)) {
|
|
35
|
+
throw new Error(`Archivo del controlador no encontrado: ${absolutePath}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cargar el módulo del controlador
|
|
39
|
+
const controllerModule = require(absolutePath);
|
|
40
|
+
|
|
41
|
+
// Guardar en cache
|
|
42
|
+
this.loadedControllers.set(absolutePath, controllerModule);
|
|
43
|
+
|
|
44
|
+
// Disparar hook después de cargar el controlador
|
|
45
|
+
if (hooks) {
|
|
46
|
+
hooks.doAction('post_controller_load', controllerModule, absolutePath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return controllerModule;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Error cargando controlador ${controllerPath}: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Método para cargar múltiples controladores desde un directorio
|
|
57
|
+
* @param {string} directoryPath - Ruta al directorio de controladores
|
|
58
|
+
* @param {Object} options - Opciones de carga
|
|
59
|
+
* @returns {Object} - Objeto con todos los controladores cargados
|
|
60
|
+
*/
|
|
61
|
+
loadControllersFromDirectory(directoryPath, options = {}) {
|
|
62
|
+
try {
|
|
63
|
+
// Resolver la ruta absoluta
|
|
64
|
+
const absoluteDirPath = path.resolve(process.cwd(), directoryPath);
|
|
65
|
+
|
|
66
|
+
// Verificar si el directorio existe
|
|
67
|
+
if (!fs.existsSync(absoluteDirPath)) {
|
|
68
|
+
throw new Error(`Directorio de controladores no encontrado: ${absoluteDirPath}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Obtener archivos del directorio
|
|
72
|
+
const files = fs.readdirSync(absoluteDirPath);
|
|
73
|
+
|
|
74
|
+
const controllers = {};
|
|
75
|
+
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
// Solo procesar archivos JavaScript
|
|
78
|
+
if (file.endsWith('.js')) {
|
|
79
|
+
const controllerName = path.basename(file, '.js');
|
|
80
|
+
const controllerPath = path.join(absoluteDirPath, file);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const controllerModule = this.loadController(controllerPath);
|
|
84
|
+
controllers[controllerName] = controllerModule;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (options.ignoreErrors) {
|
|
87
|
+
console.warn(`Advertencia: No se pudo cargar el controlador ${controllerPath}: ${error.message}`);
|
|
88
|
+
} else {
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return controllers;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Error cargando controladores desde el directorio ${directoryPath}: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Método para obtener un controlador del cache
|
|
103
|
+
* @param {string} controllerPath - Ruta al archivo del controlador
|
|
104
|
+
* @returns {Object|null} - Módulo del controlador o null si no está cargado
|
|
105
|
+
*/
|
|
106
|
+
getCachedController(controllerPath) {
|
|
107
|
+
const absolutePath = path.resolve(process.cwd(), controllerPath);
|
|
108
|
+
return this.loadedControllers.get(absolutePath) || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Método para limpiar el cache de controladores
|
|
113
|
+
*/
|
|
114
|
+
clearCache() {
|
|
115
|
+
// Eliminar módulos del cache de require para forzar recarga
|
|
116
|
+
for (const [path, module] of this.loadedControllers) {
|
|
117
|
+
delete require.cache[require.resolve(path)];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.loadedControllers.clear();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Método para recargar un controlador
|
|
125
|
+
* @param {string} controllerPath - Ruta al archivo del controlador
|
|
126
|
+
* @returns {Object} - Módulo del controlador recargado
|
|
127
|
+
*/
|
|
128
|
+
reloadController(controllerPath) {
|
|
129
|
+
// Eliminar del cache de require para forzar recarga
|
|
130
|
+
const absolutePath = path.resolve(process.cwd(), controllerPath);
|
|
131
|
+
if (require.cache[require.resolve(absolutePath)]) {
|
|
132
|
+
delete require.cache[require.resolve(absolutePath)];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Volver a cargar
|
|
136
|
+
return this.loadController(controllerPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Método para obtener la lista de controladores cargados
|
|
141
|
+
* @returns {Array} - Array con las rutas de los controladores cargados
|
|
142
|
+
*/
|
|
143
|
+
getLoadedControllersList() {
|
|
144
|
+
return Array.from(this.loadedControllers.keys());
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Método para verificar si un controlador está cargado
|
|
149
|
+
* @param {string} controllerPath - Ruta al archivo del controlador
|
|
150
|
+
* @returns {boolean} - True si está cargado, false en caso contrario
|
|
151
|
+
*/
|
|
152
|
+
isControllerLoaded(controllerPath) {
|
|
153
|
+
const absolutePath = path.resolve(process.cwd(), controllerPath);
|
|
154
|
+
return this.loadedControllers.has(absolutePath);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Método para cargar un controlador y obtener un handler específico
|
|
159
|
+
* @param {string} controllerPath - Ruta al archivo del controlador
|
|
160
|
+
* @param {string} handlerName - Nombre del handler a obtener
|
|
161
|
+
* @returns {Function|null} - Función handler o null si no existe
|
|
162
|
+
*/
|
|
163
|
+
getHandlerFromController(controllerPath, handlerName) {
|
|
164
|
+
const controller = this.loadController(controllerPath);
|
|
165
|
+
const handler = controller[handlerName];
|
|
166
|
+
|
|
167
|
+
if (typeof handler !== 'function') {
|
|
168
|
+
throw new Error(`El handler '${handlerName}' no es una función en el controlador: ${controllerPath}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return handler;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = ControllerLoader;
|