kukuy 1.6.0 → 1.9.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/README.md +59 -3
- package/balancer.log +28 -0
- package/kukuy-plugins/README.md +77 -43
- package/kukuy-plugins/cache-plugin/index.js +477 -0
- package/kukuy-plugins/cache-plugin/manifest.json +17 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +7 -5
- package/kukuy-plugins/ejemplo-plugin/manifest.json +2 -2
- package/kukuy-plugins/health-checker/index.js +168 -0
- package/kukuy-plugins/health-checker/manifest.json +16 -0
- package/kukuy-plugins/health-monitor/index.js +58 -0
- package/kukuy-plugins/health-monitor/manifest.json +16 -0
- package/kukuy-plugins/redirect-plugin/index.js +172 -0
- package/kukuy-plugins/redirect-plugin/manifest.json +15 -0
- package/package.json +7 -4
- package/src/core/Balancer.js +100 -34
- package/src/core/ServerPool.js +2 -2
- package/src/extensibility/FilterChain.js +2 -9
- package/src/extensibility/HookManager.js +1 -0
- package/src/plugins/PluginManager.js +48 -0
- package/src/utils/HealthChecker.js +61 -6
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Verificador de Salud Periódica",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin para verificar periódicamente la salud de los servidores backend",
|
|
5
|
+
"author": "Sistema Kukuy",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"kukuyVersion": "^1.6.0",
|
|
8
|
+
"hooks": [
|
|
9
|
+
"onHealthCheckStart",
|
|
10
|
+
"onHealthCheckComplete",
|
|
11
|
+
"onHealthCheckError",
|
|
12
|
+
"onHealthCheckTimeout",
|
|
13
|
+
"onHealthCheckException"
|
|
14
|
+
],
|
|
15
|
+
"enabled": true
|
|
16
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin de Monitor de Salud de Servidores para Kukuy
|
|
3
|
+
* Monitorea eventos de verificación de salud de servidores backend
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
async function init(balancer) {
|
|
7
|
+
console.log('Inicializando plugin de monitor de salud de servidores...');
|
|
8
|
+
|
|
9
|
+
const extension = balancer.getPostStartupExtension();
|
|
10
|
+
|
|
11
|
+
// Hook que se ejecuta cuando comienza la verificación de salud de un servidor
|
|
12
|
+
extension.registerHook('onHealthCheckStart', async ({ server, checkTime }) => {
|
|
13
|
+
console.log(`[HEALTH-MONITOR] Iniciando verificación de salud para servidor: ${server.url} (${checkTime})`);
|
|
14
|
+
}, 5);
|
|
15
|
+
|
|
16
|
+
// Hook que se ejecuta cuando se completa la verificación de salud
|
|
17
|
+
extension.registerHook('onHealthCheckComplete', async ({ server, isHealthy, statusCode, checkTime }) => {
|
|
18
|
+
const status = isHealthy ? 'SALUDABLE' : 'NO SALUDABLE';
|
|
19
|
+
console.log(`[HEALTH-MONITOR] Verificación completada para ${server.url} - Estado: ${status} - Código: ${statusCode} (${checkTime})`);
|
|
20
|
+
|
|
21
|
+
// Aquí podrías agregar lógica adicional como alertas o métricas personalizadas
|
|
22
|
+
if (!isHealthy) {
|
|
23
|
+
console.warn(`[HEALTH-MONITOR] ¡Alerta! Servidor ${server.url} no está saludable`);
|
|
24
|
+
}
|
|
25
|
+
}, 5);
|
|
26
|
+
|
|
27
|
+
// Hook que se ejecuta cuando ocurre un error durante la verificación de salud
|
|
28
|
+
extension.registerHook('onHealthCheckError', async ({ server, error, checkTime }) => {
|
|
29
|
+
console.error(`[HEALTH-MONITOR] Error en verificación de salud para ${server.url}: ${error.message} (${checkTime})`);
|
|
30
|
+
|
|
31
|
+
// Lógica para manejar errores de verificación
|
|
32
|
+
console.warn(`[HEALTH-MONITOR] ¡Error crítico! Servidor ${server.url} no pudo ser verificado`);
|
|
33
|
+
}, 10); // Prioridad alta para errores
|
|
34
|
+
|
|
35
|
+
// Hook que se ejecuta cuando ocurre un timeout durante la verificación de salud
|
|
36
|
+
extension.registerHook('onHealthCheckTimeout', async ({ server, checkTime }) => {
|
|
37
|
+
console.warn(`[HEALTH-MONITOR] Timeout en verificación de salud para ${server.url} (${checkTime})`);
|
|
38
|
+
|
|
39
|
+
// Lógica para manejar timeouts
|
|
40
|
+
console.warn(`[HEALTH-MONITOR] ¡Timeout! Servidor ${server.url} no respondió a tiempo`);
|
|
41
|
+
}, 10); // Prioridad alta para timeouts
|
|
42
|
+
|
|
43
|
+
// Hook que se ejecuta cuando ocurre una excepción durante la verificación de salud
|
|
44
|
+
extension.registerHook('onHealthCheckException', async ({ server, error, checkTime }) => {
|
|
45
|
+
console.error(`[HEALTH-MONITOR] Excepción en verificación de salud para ${server.url}: ${error.message} (${checkTime})`);
|
|
46
|
+
|
|
47
|
+
// Lógica para manejar excepciones
|
|
48
|
+
console.error(`[HEALTH-MONITOR] ¡Excepción crítica! Error inesperado verificando ${server.url}`);
|
|
49
|
+
}, 10); // Prioridad alta para excepciones
|
|
50
|
+
|
|
51
|
+
console.log('Plugin de monitor de salud de servidores inicializado correctamente');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function deinit(balancer) {
|
|
55
|
+
console.log('Desactivando plugin de monitor de salud de servidores...');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { init, deinit };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Monitor de Salud de Servidores",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin para monitorear eventos de verificación de salud de servidores backend",
|
|
5
|
+
"author": "Sistema Kukuy",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"kukuyVersion": "^1.6.0",
|
|
8
|
+
"hooks": [
|
|
9
|
+
"onHealthCheckStart",
|
|
10
|
+
"onHealthCheckComplete",
|
|
11
|
+
"onHealthCheckError",
|
|
12
|
+
"onHealthCheckTimeout",
|
|
13
|
+
"onHealthCheckException"
|
|
14
|
+
],
|
|
15
|
+
"enabled": true
|
|
16
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin de Redirección para Kukuy
|
|
3
|
+
* Permite implementar reglas de redirección personalizadas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Reglas de redirección
|
|
7
|
+
let redirectRules = [
|
|
8
|
+
// Ejemplo: redirigir /old-path a /new-path permanentemente
|
|
9
|
+
// { pattern: /^\/old-path$/, target: '/new-path', type: 'permanent' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inicializa el plugin de redirección
|
|
14
|
+
* @param {Object} balancer - Instancia del balanceador
|
|
15
|
+
*/
|
|
16
|
+
async function init(balancer) {
|
|
17
|
+
console.log('Inicializando plugin de redirección...');
|
|
18
|
+
|
|
19
|
+
const extension = balancer.getPostStartupExtension();
|
|
20
|
+
|
|
21
|
+
// Registrar filtro para procesar solicitudes
|
|
22
|
+
extension.registerFilter('request_processing', requestProcessingFilter, 2);
|
|
23
|
+
|
|
24
|
+
// Registrar hook para cuando se recibe una solicitud
|
|
25
|
+
extension.registerHook('onRequestReceived', onRequestReceivedHook, 2);
|
|
26
|
+
|
|
27
|
+
// Cargar reglas de redirección desde el entorno
|
|
28
|
+
loadRedirectRulesFromEnv();
|
|
29
|
+
|
|
30
|
+
console.log('Plugin de redirección inicializado correctamente');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Carga reglas de redirección desde variables de entorno
|
|
35
|
+
*/
|
|
36
|
+
function loadRedirectRulesFromEnv() {
|
|
37
|
+
if (process.env.REDIRECT_RULES) {
|
|
38
|
+
try {
|
|
39
|
+
const rules = JSON.parse(process.env.REDIRECT_RULES);
|
|
40
|
+
redirectRules = [...redirectRules, ...rules];
|
|
41
|
+
console.log(`[REDIRECT-PLUGIN] Cargadas ${rules.length} reglas de redirección desde entorno`);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error('[REDIRECT-PLUGIN] Error al parsear reglas de redirección desde entorno:', error.message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Filtro para procesar solicitudes
|
|
50
|
+
* @param {Object} data - Datos de la solicitud
|
|
51
|
+
* @returns {Object} - Resultado del procesamiento
|
|
52
|
+
*/
|
|
53
|
+
async function requestProcessingFilter(data) {
|
|
54
|
+
const { req, res } = data;
|
|
55
|
+
|
|
56
|
+
// Buscar una regla de redirección que coincida
|
|
57
|
+
for (const rule of redirectRules) {
|
|
58
|
+
if (rule.pattern instanceof RegExp) {
|
|
59
|
+
// Si el patrón es una expresión regular
|
|
60
|
+
if (rule.pattern.test(req.url)) {
|
|
61
|
+
// Encontramos una coincidencia, realizar la redirección
|
|
62
|
+
const target = typeof rule.target === 'function'
|
|
63
|
+
? rule.target(req.url)
|
|
64
|
+
: rule.target;
|
|
65
|
+
|
|
66
|
+
const statusCode = rule.type === 'permanent' ? 301 : 302;
|
|
67
|
+
|
|
68
|
+
res.writeHead(statusCode, {
|
|
69
|
+
'Location': target,
|
|
70
|
+
'Content-Type': 'text/html'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
res.end(`<html><body>Redirigido a <a href="${target}">${target}</a></body></html>`);
|
|
74
|
+
|
|
75
|
+
console.log(`[REDIRECT-PLUGIN] Redirección ${statusCode} de ${req.url} a ${target}`);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
allowed: false, // Indicar que no debe continuar con el procesamiento normal
|
|
79
|
+
redirected: true,
|
|
80
|
+
redirectUrl: target,
|
|
81
|
+
statusCode
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
} else if (typeof rule.pattern === 'string') {
|
|
85
|
+
// Si el patrón es una cadena, verificar coincidencia exacta o parcial
|
|
86
|
+
if (req.url === rule.pattern || (rule.partialMatch && req.url.startsWith(rule.pattern))) {
|
|
87
|
+
// Encontramos una coincidencia, realizar la redirección
|
|
88
|
+
const target = typeof rule.target === 'function'
|
|
89
|
+
? rule.target(req.url)
|
|
90
|
+
: rule.target;
|
|
91
|
+
|
|
92
|
+
const statusCode = rule.type === 'permanent' ? 301 : 302;
|
|
93
|
+
|
|
94
|
+
res.writeHead(statusCode, {
|
|
95
|
+
'Location': target,
|
|
96
|
+
'Content-Type': 'text/html'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
res.end(`<html><body>Redirigido a <a href="${target}">${target}</a></body></html>`);
|
|
100
|
+
|
|
101
|
+
console.log(`[REDIRECT-PLUGIN] Redirección ${statusCode} de ${req.url} a ${target}`);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
allowed: false, // Indicar que no debe continuar con el procesamiento normal
|
|
105
|
+
redirected: true,
|
|
106
|
+
redirectUrl: target,
|
|
107
|
+
statusCode
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// No se encontró ninguna regla de redirección, continuar normalmente
|
|
114
|
+
return { allowed: true, redirected: false };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Hook para cuando se recibe una solicitud
|
|
119
|
+
* @param {Object} params - Parámetros del hook
|
|
120
|
+
*/
|
|
121
|
+
async function onRequestReceivedHook({ req, res }) {
|
|
122
|
+
console.log(`[REDIRECT-PLUGIN] Solicitud recibida: ${req.method} ${req.url}`);
|
|
123
|
+
|
|
124
|
+
// Podríamos hacer alguna lógica aquí si es necesario
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Agrega una nueva regla de redirección
|
|
129
|
+
* @param {Object} rule - Regla de redirección
|
|
130
|
+
*/
|
|
131
|
+
function addRedirectRule(rule) {
|
|
132
|
+
redirectRules.push(rule);
|
|
133
|
+
console.log(`[REDIRECT-PLUGIN] Añadida nueva regla de redirección: ${rule.pattern} -> ${rule.target}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Obtiene todas las reglas de redirección
|
|
138
|
+
* @returns {Array} - Array de reglas de redirección
|
|
139
|
+
*/
|
|
140
|
+
function getRedirectRules() {
|
|
141
|
+
return redirectRules;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Limpia todas las reglas de redirección
|
|
146
|
+
*/
|
|
147
|
+
function clearRedirectRules() {
|
|
148
|
+
redirectRules = [];
|
|
149
|
+
console.log('[REDIRECT-PLUGIN] Reglas de redirección limpiadas');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Desinicializa el plugin
|
|
154
|
+
* @param {Object} balancer - Instancia del balanceador
|
|
155
|
+
*/
|
|
156
|
+
async function deinit(balancer) {
|
|
157
|
+
console.log('Desactivando plugin de redirección...');
|
|
158
|
+
|
|
159
|
+
// Limpiar recursos si es necesario
|
|
160
|
+
redirectRules = [];
|
|
161
|
+
|
|
162
|
+
console.log('Plugin de redirección desactivado correctamente');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Exportar funciones
|
|
166
|
+
module.exports = {
|
|
167
|
+
init,
|
|
168
|
+
deinit,
|
|
169
|
+
addRedirectRule,
|
|
170
|
+
getRedirectRules,
|
|
171
|
+
clearRedirectRules
|
|
172
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Plugin de Redirección",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin para implementar redirecciones basadas en reglas personalizadas",
|
|
5
|
+
"author": "Sistema Kukuy",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"kukuyVersion": "^1.6.0",
|
|
8
|
+
"hooks": [
|
|
9
|
+
"onRequestReceived"
|
|
10
|
+
],
|
|
11
|
+
"filters": [
|
|
12
|
+
"request_processing"
|
|
13
|
+
],
|
|
14
|
+
"enabled": false
|
|
15
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kukuy",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Balanceador de carga
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Balanceador de carga con soporte para hooks, filtros y cache robusto",
|
|
5
5
|
"main": "kukuy.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"start": "node kukuy.js",
|
|
@@ -9,13 +9,16 @@
|
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
|
-
"backend",
|
|
13
12
|
"load-balancer",
|
|
14
13
|
"round-robin",
|
|
15
14
|
"ip-hash",
|
|
16
15
|
"proxy",
|
|
17
16
|
"http",
|
|
18
|
-
"https"
|
|
17
|
+
"https",
|
|
18
|
+
"hooks",
|
|
19
|
+
"filters",
|
|
20
|
+
"cache",
|
|
21
|
+
"plugins"
|
|
19
22
|
],
|
|
20
23
|
"author": "Benjamín Sánchez Cárdenas",
|
|
21
24
|
"license": "GPL-3.0-or-later",
|
package/src/core/Balancer.js
CHANGED
|
@@ -39,7 +39,7 @@ class Balancer {
|
|
|
39
39
|
this.config = ConfigManager.getInstance();
|
|
40
40
|
this.hookManager = new ExtendedHookManager();
|
|
41
41
|
this.filterChain = new ExtendedFilterChain();
|
|
42
|
-
this.serverPool = new ServerPool();
|
|
42
|
+
this.serverPool = new ServerPool(this);
|
|
43
43
|
this.algorithmManager = new AlgorithmManager();
|
|
44
44
|
this.routeLoader = new RouteLoader();
|
|
45
45
|
this.logger = new Logger();
|
|
@@ -175,13 +175,16 @@ class Balancer {
|
|
|
175
175
|
|
|
176
176
|
// Cargar plugins después de iniciar componentes principales
|
|
177
177
|
await this.pluginManager.loadPlugins();
|
|
178
|
+
|
|
179
|
+
// Imprimir información sobre los plugins disponibles y activos
|
|
180
|
+
this.pluginManager.printPluginsInfo();
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
async handleRequest(clientReq, clientRes) {
|
|
181
184
|
try {
|
|
182
185
|
// Aplicar filters antes de procesar la solicitud
|
|
183
186
|
const filterData = { req: clientReq, res: clientRes, allowed: true, statusCode: 200, message: '' };
|
|
184
|
-
const filteredResult = await this.filterChain.applyFilters('
|
|
187
|
+
const filteredResult = await this.filterChain.applyFilters('request_processing', clientReq, clientRes);
|
|
185
188
|
|
|
186
189
|
if (!filteredResult || !filteredResult.allowed) {
|
|
187
190
|
const statusCode = filteredResult?.statusCode || 403;
|
|
@@ -191,27 +194,16 @@ class Balancer {
|
|
|
191
194
|
return;
|
|
192
195
|
}
|
|
193
196
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const targetServer = await this.getTargetServer(clientReq.url, clientReq.method, clientReq);
|
|
199
|
-
|
|
200
|
-
if (!targetServer) {
|
|
201
|
-
clientRes.writeHead(404);
|
|
202
|
-
clientRes.end('No available servers');
|
|
197
|
+
// Verificar si el filtro indicó que la respuesta se sirvió desde caché
|
|
198
|
+
if (filteredResult?.cached && filteredResult?.cacheHit) {
|
|
199
|
+
// La respuesta ya fue enviada desde el filtro de caché, terminamos aquí
|
|
200
|
+
// No debemos continuar con ningún otro procesamiento
|
|
203
201
|
return;
|
|
204
202
|
}
|
|
205
203
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
res: clientRes,
|
|
210
|
-
server: targetServer
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Reenviar solicitud al servidor backend
|
|
214
|
-
this.proxyRequest(clientReq, clientRes, targetServer);
|
|
204
|
+
// Si llegamos aquí, no hubo cache hit, continuar con el flujo normal
|
|
205
|
+
// y permitir que se ejecuten los hooks y se contacte al servidor backend
|
|
206
|
+
await this.processRequestNormally(clientReq, clientRes);
|
|
215
207
|
|
|
216
208
|
} catch (error) {
|
|
217
209
|
this.logger.error(`Error manejando solicitud: ${error.message}`);
|
|
@@ -228,6 +220,36 @@ class Balancer {
|
|
|
228
220
|
}
|
|
229
221
|
}
|
|
230
222
|
|
|
223
|
+
async processRequestNormally(clientReq, clientRes) {
|
|
224
|
+
// Emitir hook cuando se recibe una solicitud
|
|
225
|
+
await this.hookManager.executeHooks('onRequestReceived', { req: clientReq, res: clientRes });
|
|
226
|
+
|
|
227
|
+
// Determinar la ruta objetivo
|
|
228
|
+
const targetServer = await this.getTargetServer(clientReq.url, clientReq.method, clientReq);
|
|
229
|
+
|
|
230
|
+
if (!targetServer) {
|
|
231
|
+
clientRes.writeHead(404);
|
|
232
|
+
clientRes.end('No available servers');
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Emitir hook después de seleccionar servidor
|
|
237
|
+
await this.hookManager.executeHooks('onServerSelected', {
|
|
238
|
+
req: clientReq,
|
|
239
|
+
res: clientRes,
|
|
240
|
+
server: targetServer
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Verificar si después de los hooks la respuesta ya fue enviada
|
|
244
|
+
// (esto cubre el caso de onServerError u otros hooks que puedan enviar respuesta desde la caché)
|
|
245
|
+
if (clientRes.headersSent) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Reenviar solicitud al servidor backend
|
|
250
|
+
this.proxyRequest(clientReq, clientRes, targetServer);
|
|
251
|
+
}
|
|
252
|
+
|
|
231
253
|
async getTargetServer(requestUrl, method, clientReq = null) {
|
|
232
254
|
// Usar RouteLoader para encontrar la ruta coincidente
|
|
233
255
|
const matchedRoute = this.routeLoader.findMatchingRoute(requestUrl, method);
|
|
@@ -283,12 +305,6 @@ class Balancer {
|
|
|
283
305
|
clientReq.pipe(proxyReq);
|
|
284
306
|
|
|
285
307
|
proxyReq.on('response', (serverRes) => {
|
|
286
|
-
// Copiar headers de la respuesta
|
|
287
|
-
clientRes.writeHead(serverRes.statusCode, serverRes.headers);
|
|
288
|
-
|
|
289
|
-
// Enviar cuerpo de la respuesta al cliente
|
|
290
|
-
serverRes.pipe(clientRes);
|
|
291
|
-
|
|
292
308
|
// Calcular tiempo de respuesta
|
|
293
309
|
const responseTime = Date.now() - startTime;
|
|
294
310
|
|
|
@@ -313,16 +329,66 @@ class Balancer {
|
|
|
313
329
|
// Actualizar estado del servidor en las métricas
|
|
314
330
|
this.metricsCollector.updateServerStatus(server.id, 'online');
|
|
315
331
|
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
server,
|
|
322
|
-
responseTime
|
|
332
|
+
// Capturar el cuerpo de la respuesta para posibles plugins
|
|
333
|
+
let responseData = [];
|
|
334
|
+
|
|
335
|
+
serverRes.on('data', (chunk) => {
|
|
336
|
+
responseData.push(chunk);
|
|
323
337
|
});
|
|
324
338
|
|
|
325
|
-
|
|
339
|
+
serverRes.on('end', () => {
|
|
340
|
+
const responseBody = Buffer.concat(responseData);
|
|
341
|
+
|
|
342
|
+
// Emitir hook antes de enviar la respuesta al cliente
|
|
343
|
+
// Esto permite a los plugins interceptar la respuesta
|
|
344
|
+
this.hookManager.executeHooks('onResponseReady', {
|
|
345
|
+
req: clientReq,
|
|
346
|
+
res: clientRes,
|
|
347
|
+
serverRes,
|
|
348
|
+
responseBody,
|
|
349
|
+
server,
|
|
350
|
+
responseTime
|
|
351
|
+
}).then(() => {
|
|
352
|
+
// Si algún plugin ha manejado la respuesta, no la enviamos aquí
|
|
353
|
+
if (!clientRes.headersSent) {
|
|
354
|
+
// Copiar headers de la respuesta
|
|
355
|
+
clientRes.writeHead(serverRes.statusCode, serverRes.headers);
|
|
356
|
+
|
|
357
|
+
// Enviar cuerpo de la respuesta al cliente
|
|
358
|
+
clientRes.end(responseBody);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Emitir hook cuando se envía la respuesta
|
|
362
|
+
this.hookManager.executeHooks('onResponseSent', {
|
|
363
|
+
req: clientReq,
|
|
364
|
+
res: clientRes,
|
|
365
|
+
serverRes,
|
|
366
|
+
server,
|
|
367
|
+
responseTime
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
resolve(); // Resolver la promesa al completar la solicitud
|
|
371
|
+
}).catch(err => {
|
|
372
|
+
console.error('Error ejecutando hook onResponseReady:', err);
|
|
373
|
+
|
|
374
|
+
// En caso de error, enviar la respuesta de todas formas
|
|
375
|
+
if (!clientRes.headersSent) {
|
|
376
|
+
clientRes.writeHead(serverRes.statusCode, serverRes.headers);
|
|
377
|
+
clientRes.end(responseBody);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Emitir hook cuando se envía la respuesta
|
|
381
|
+
this.hookManager.executeHooks('onResponseSent', {
|
|
382
|
+
req: clientReq,
|
|
383
|
+
res: clientRes,
|
|
384
|
+
serverRes,
|
|
385
|
+
server,
|
|
386
|
+
responseTime
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
resolve(); // Resolver la promesa al completar la solicitud
|
|
390
|
+
});
|
|
391
|
+
});
|
|
326
392
|
});
|
|
327
393
|
|
|
328
394
|
proxyReq.on('error', async (err) => {
|
package/src/core/ServerPool.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const { HealthChecker } = require('../utils/HealthChecker');
|
|
2
2
|
|
|
3
3
|
class ServerPool {
|
|
4
|
-
constructor() {
|
|
4
|
+
constructor(balancer = null) {
|
|
5
5
|
this.servers = [];
|
|
6
6
|
this.healthyServers = []; // Caché de servidores saludables
|
|
7
7
|
this.serversById = new Map(); // Acceso rápido por ID
|
|
8
8
|
this.serversByTag = new Map(); // Caché de servidores por tag
|
|
9
|
-
this.healthChecker = new HealthChecker();
|
|
9
|
+
this.healthChecker = new HealthChecker(balancer);
|
|
10
10
|
this.nextId = 1;
|
|
11
11
|
this.cacheValid = false; // Indicador de validez de caché
|
|
12
12
|
}
|
|
@@ -35,14 +35,6 @@ class FilterChain {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async applyFilters(type, req, res, additionalData = {}) {
|
|
38
|
-
// Verificar si la solicitud puede ser atendida desde caché
|
|
39
|
-
const cacheResult = await this.cachingFilter.apply(req, res, additionalData);
|
|
40
|
-
|
|
41
|
-
// Si fue un hit de caché, retornar inmediatamente
|
|
42
|
-
if (cacheResult.cached && cacheResult.cacheHit) {
|
|
43
|
-
return cacheResult;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
38
|
// Usar el sistema de filtros de la librería para otros filtros
|
|
47
39
|
// Creamos un objeto combinado para pasar al filtro
|
|
48
40
|
const filterData = { req, res, ...additionalData, allowed: true, statusCode: 200, message: '' };
|
|
@@ -51,7 +43,8 @@ class FilterChain {
|
|
|
51
43
|
const processedData = this.hooks.applyFilters('request_processing', filterData);
|
|
52
44
|
|
|
53
45
|
// Devolver el resultado procesado
|
|
54
|
-
|
|
46
|
+
// Nota: El manejo de caché ahora se delega a los plugins registrados
|
|
47
|
+
return processedData;
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
// Filtro de autenticación básico
|
|
@@ -178,6 +178,54 @@ class PluginManager {
|
|
|
178
178
|
isActive: plugin.isActive
|
|
179
179
|
}));
|
|
180
180
|
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Print information about available and loaded plugins to stdout
|
|
184
|
+
*/
|
|
185
|
+
printPluginsInfo() {
|
|
186
|
+
console.log('\n========== INFORMACIÓN DE PLUGINS ==========');
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(this.pluginPath)) {
|
|
189
|
+
console.log(`Directorio de plugins no encontrado: ${this.pluginPath}`);
|
|
190
|
+
console.log('=============================================\n');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const allItems = fs.readdirSync(this.pluginPath);
|
|
195
|
+
const pluginDirs = allItems.filter(item => this.isDirectory(path.join(this.pluginPath, item)));
|
|
196
|
+
|
|
197
|
+
console.log(`Plugins encontrados en ${this.pluginPath}: ${pluginDirs.length}`);
|
|
198
|
+
|
|
199
|
+
for (const pluginDir of pluginDirs) {
|
|
200
|
+
const pluginPath = path.join(this.pluginPath, pluginDir);
|
|
201
|
+
const manifestPath = path.join(pluginPath, 'manifest.json');
|
|
202
|
+
|
|
203
|
+
if (fs.existsSync(manifestPath)) {
|
|
204
|
+
try {
|
|
205
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
206
|
+
|
|
207
|
+
if (this.plugins.has(pluginDir)) {
|
|
208
|
+
const plugin = this.plugins.get(pluginDir);
|
|
209
|
+
console.log(`✓ ACTIVO - ${pluginDir} (v${manifest.version}): ${manifest.description}`);
|
|
210
|
+
} else {
|
|
211
|
+
if (manifest.enabled !== false) {
|
|
212
|
+
console.log(`○ DISPONIBLE - ${pluginDir} (v${manifest.version}): ${manifest.description}`);
|
|
213
|
+
} else {
|
|
214
|
+
console.log(`○ DESHABILITADO - ${pluginDir} (v${manifest.version}): ${manifest.description} (manifest: enabled=false)`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.log(`✗ ERROR - ${pluginDir}: Error leyendo manifest.json`);
|
|
219
|
+
}
|
|
220
|
+
} else {
|
|
221
|
+
console.log(`✗ SIN MANIFEST - ${pluginDir}: No tiene archivo manifest.json`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const loadedCount = this.plugins.size;
|
|
226
|
+
console.log(`\nTotal de plugins activos: ${loadedCount}`);
|
|
227
|
+
console.log('=============================================\n');
|
|
228
|
+
}
|
|
181
229
|
}
|
|
182
230
|
|
|
183
231
|
module.exports = { PluginManager };
|