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.
@@ -0,0 +1,477 @@
1
+ const crypto = require('crypto');
2
+
3
+ // Almacenamiento de la caché en memoria
4
+ let cacheStorage = new Map();
5
+ let cacheMetadata = new Map(); // Almacenar metadatos como TTL, tamaño, etc.
6
+
7
+ // Configuración del plugin
8
+ let pluginConfig = {
9
+ maxCacheSize: 100, // Número máximo de entradas
10
+ defaultTTL: 300000, // Tiempo de vida por defecto (5 minutos en ms)
11
+ enableCompression: false,
12
+ cacheableMethods: ['GET'],
13
+ cacheableStatusCodes: [200, 201, 204]
14
+ };
15
+
16
+ // Variable para almacenar temporalmente las solicitudes que están siendo procesadas
17
+ let activeRequests = new Map();
18
+
19
+ /**
20
+ * Inicializa el plugin de cache
21
+ * @param {Object} balancer - Instancia del balanceador
22
+ */
23
+ async function init(balancer) {
24
+ console.log('Inicializando plugin de cache robusto...');
25
+
26
+ const extension = balancer.getPostStartupExtension();
27
+
28
+ // Registrar filtro para procesar solicitudes con la máxima prioridad
29
+ extension.registerFilter('request_processing', requestProcessingFilter, 0);
30
+
31
+ // Registrar hook para cuando se recibe una solicitud
32
+ extension.registerHook('onRequestReceived', onRequestReceivedHook, 0);
33
+
34
+ // Registrar hook para cuando se selecciona un servidor
35
+ extension.registerHook('onServerSelected', onServerSelectedHook, 0);
36
+
37
+ // Registrar hook para cuando la respuesta está lista para ser enviada
38
+ extension.registerHook('onResponseReady', onResponseReadyHook, 0);
39
+
40
+ // Registrar hook para cuando se envía la respuesta
41
+ extension.registerHook('onResponseSent', onResponseSentHook, 0);
42
+
43
+ // Registrar hook para cuando ocurre un error con el servidor
44
+ extension.registerHook('onServerError', onServerErrorHook, 0);
45
+
46
+ // Configurar el plugin con valores del entorno
47
+ configurePluginFromEnv();
48
+
49
+ // Iniciar tarea de limpieza periódica de entradas expiradas
50
+ startCacheCleanupTask();
51
+
52
+ console.log('Plugin de cache robusto inicializado correctamente');
53
+ }
54
+
55
+ /**
56
+ * Configura el plugin con valores del entorno
57
+ */
58
+ function configurePluginFromEnv() {
59
+ if (process.env.CACHE_MAX_SIZE) {
60
+ pluginConfig.maxCacheSize = parseInt(process.env.CACHE_MAX_SIZE) || pluginConfig.maxCacheSize;
61
+ }
62
+
63
+ if (process.env.CACHE_DEFAULT_TTL) {
64
+ pluginConfig.defaultTTL = parseInt(process.env.CACHE_DEFAULT_TTL) || pluginConfig.defaultTTL;
65
+ }
66
+
67
+ if (process.env.CACHE_ENABLE_COMPRESSION) {
68
+ pluginConfig.enableCompression = process.env.CACHE_ENABLE_COMPRESSION === 'true';
69
+ }
70
+
71
+ if (process.env.CACHEABLE_METHODS) {
72
+ pluginConfig.cacheableMethods = process.env.CACHEABLE_METHODS.split(',');
73
+ }
74
+
75
+ if (process.env.CACHEABLE_STATUS_CODES) {
76
+ pluginConfig.cacheableStatusCodes = process.env.CACHEABLE_STATUS_CODES.split(',').map(Number);
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Filtro para procesar solicitudes
82
+ * @param {Object} data - Datos de la solicitud
83
+ * @returns {Object} - Resultado del procesamiento
84
+ */
85
+ async function requestProcessingFilter(data) {
86
+ const { req, res } = data;
87
+
88
+ // Verificar si la solicitud es cacheable
89
+ if (!isRequestCacheable(req)) {
90
+ return { allowed: true, cached: false };
91
+ }
92
+
93
+ // Generar clave de caché
94
+ const cacheKey = generateCacheKey(req);
95
+
96
+ // Verificar si está en caché
97
+ if (cacheStorage.has(cacheKey)) {
98
+ const cachedEntry = cacheStorage.get(cacheKey);
99
+ const metadata = cacheMetadata.get(cacheKey);
100
+
101
+ // Verificar si no ha expirado
102
+ if (Date.now() < metadata.expiryTime) {
103
+ // Enviar respuesta desde caché
104
+ res.writeHead(cachedEntry.statusCode, cachedEntry.headers);
105
+ res.end(cachedEntry.body);
106
+
107
+ console.log(`\x1b[32m[CACHE-HIT]\x1b[0m Solicitud ${req.method} ${req.url} servida desde caché`);
108
+ console.log(`\x1b[33m \x1b[0m → Status: ${cachedEntry.statusCode}, Size: ${cachedEntry.body.length} bytes, TTL restante: ${Math.floor((metadata.expiryTime - Date.now())/1000)}s`);
109
+ console.log(`\x1b[33m \x1b[0m → NO SE CONTACTÓ AL SERVIDOR BACKEND`);
110
+
111
+ return {
112
+ allowed: true,
113
+ cached: true,
114
+ cacheHit: true,
115
+ cacheKey
116
+ };
117
+ } else {
118
+ // Eliminar entrada expirada
119
+ cacheStorage.delete(cacheKey);
120
+ cacheMetadata.delete(cacheKey);
121
+ console.log(`\x1b[31m[CACHE-EXPIRED]\x1b[0m Entrada expirada eliminada para ${cacheKey}`);
122
+ }
123
+ }
124
+
125
+ // No está en caché, permitir que continúe
126
+ console.log(`\x1b[36m[CACHE-MISS]\x1b[0m Solicitud ${req.method} ${req.url} no está en caché`);
127
+ console.log(`\x1b[33m \x1b[0m → SE PROCESARÁ NORMALMENTE Y SE CONTACTARÁ AL SERVIDOR BACKEND`);
128
+
129
+ // Guardar la clave de caché en la solicitud para usarla más tarde
130
+ if (req) {
131
+ req.cacheKey = cacheKey;
132
+ }
133
+
134
+ return {
135
+ allowed: true,
136
+ cached: false,
137
+ cacheKey
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Hook para cuando se recibe una solicitud
143
+ * @param {Object} params - Parámetros del hook
144
+ */
145
+ async function onRequestReceivedHook({ req, res }) {
146
+ console.log(`[CACHE-PLUGIN] Solicitud recibida: ${req.method} ${req.url}`);
147
+
148
+ // Podríamos hacer alguna lógica aquí si es necesario
149
+ }
150
+
151
+ /**
152
+ * Hook para cuando se selecciona un servidor
153
+ * @param {Object} params - Parámetros del hook
154
+ */
155
+ async function onServerSelectedHook({ req, res, server }) {
156
+ console.log(`[CACHE-PLUGIN] Servidor seleccionado: ${server.url} para ${req.method} ${req.url}`);
157
+
158
+ // Podríamos hacer alguna lógica aquí si es necesario
159
+ }
160
+
161
+ /**
162
+ * Hook para cuando la respuesta está lista para ser enviada
163
+ * @param {Object} params - Parámetros del hook
164
+ */
165
+ async function onResponseReadyHook({ req, res, serverRes, responseBody, server, responseTime }) {
166
+ console.log(`\x1b[35m[CACHE-STORE-PREPARING]\x1b[0m Respuesta lista para ${req.url}, tamaño: ${responseBody.length} bytes, código: ${serverRes.statusCode}`);
167
+ console.log(`\x1b[33m \x1b[0m → Proveniente del servidor: ${server.url}`);
168
+
169
+ // Verificar si la solicitud original tenía una clave de caché
170
+ if (req && req.cacheKey) {
171
+ // Verificar si la respuesta es cacheable
172
+ if (isResponseCacheable(req, serverRes)) {
173
+ // Crear entrada de caché
174
+ const cacheEntry = {
175
+ statusCode: serverRes.statusCode,
176
+ headers: { ...serverRes.headers },
177
+ body: responseBody
178
+ };
179
+
180
+ // Determinar TTL desde headers o usar valor por defecto
181
+ let ttl = pluginConfig.defaultTTL;
182
+ if (serverRes.headers['cache-control']) {
183
+ const cc = serverRes.headers['cache-control'];
184
+ const maxAgeMatch = cc.match(/max-age=(\d+)/);
185
+ if (maxAgeMatch) {
186
+ ttl = parseInt(maxAgeMatch[1]) * 1000; // Convertir a ms
187
+ }
188
+ }
189
+
190
+ // Crear metadatos
191
+ const metadata = {
192
+ expiryTime: Date.now() + ttl,
193
+ size: responseBody.length,
194
+ createdAt: Date.now(),
195
+ ttl: ttl
196
+ };
197
+
198
+ // Verificar límites de caché
199
+ if (cacheStorage.size >= pluginConfig.maxCacheSize) {
200
+ // Implementar LRU (eliminar la entrada menos recientemente usada)
201
+ evictLRUCacheEntry();
202
+ }
203
+
204
+ // Almacenar en caché
205
+ cacheStorage.set(req.cacheKey, cacheEntry);
206
+ cacheMetadata.set(req.cacheKey, metadata);
207
+
208
+ console.log(`\x1b[32m[CACHE-STORED]\x1b[0m Almacenada respuesta para ${req.method} ${req.url}`);
209
+ console.log(`\x1b[33m \x1b[0m → TTL: ${ttl}ms, tamaño: ${responseBody.length} bytes`);
210
+ console.log(`\x1b[33m \x1b[0m → Ahora esta respuesta se servirá desde caché`);
211
+ } else {
212
+ console.log(`\x1b[31m[CACHE-SKIPPED]\x1b[0m La respuesta no es cacheable, omitiendo almacenamiento`);
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Hook para cuando se envía la respuesta
219
+ * @param {Object} params - Parámetros del hook
220
+ */
221
+ async function onResponseSentHook({ req, res, serverRes, responseTime, server }) {
222
+ console.log(`[CACHE-PLUGIN] Respuesta enviada para ${req.url}, tiempo: ${responseTime}ms, código: ${serverRes.statusCode}`);
223
+
224
+ // Verificar si la solicitud original tenía una clave de caché
225
+ if (req && req.cacheKey) {
226
+ // Verificar si la respuesta es cacheable
227
+ if (isResponseCacheable(req, serverRes)) {
228
+ // Almacenar la respuesta en caché
229
+ storeResponseInCache(req, serverRes, req.cacheKey);
230
+ }
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Hook para cuando ocurre un error con el servidor
236
+ * @param {Object} params - Parámetros del hook
237
+ */
238
+ async function onServerErrorHook(params) {
239
+ // Extraer parámetros con valores por defecto para evitar errores
240
+ const { req, res, server, error, responseTime } = params || {};
241
+
242
+ // Verificar que req exista antes de continuar
243
+ if (!req) {
244
+ console.log(`[CACHE-PLUGIN] Error recibido sin solicitud válida`);
245
+ return;
246
+ }
247
+
248
+ const serverUrl = server ? server.url : 'desconocido';
249
+ const errorMessage = error ? error.message || error : 'desconocido';
250
+
251
+ console.log(`[CACHE-PLUGIN] Error con servidor ${serverUrl} para ${req.url}: ${errorMessage}`);
252
+
253
+ // Solo intentar servir desde caché si no se han enviado headers aún
254
+ if (res && !res.headersSent) {
255
+ const cacheKey = generateCacheKey(req);
256
+
257
+ if (cacheStorage.has(cacheKey)) {
258
+ const cachedEntry = cacheStorage.get(cacheKey);
259
+ const metadata = cacheMetadata.get(cacheKey);
260
+
261
+ // Verificar si no ha expirado o si está dentro de un periodo de grace period
262
+ // (por ejemplo, permitir servir contenido ligeramente expirado mientras se intenta reconectar)
263
+ const gracePeriod = 300000; // 5 minutos extra como grace period
264
+ if (Date.now() < metadata.expiryTime + gracePeriod) {
265
+ // Enviar respuesta desde caché como fallback
266
+ res.writeHead(cachedEntry.statusCode, cachedEntry.headers);
267
+ res.end(cachedEntry.body);
268
+
269
+ console.log(`\x1b[33m[CACHE-FALLBACK]\x1b[0m Solicitud ${req.method} ${req.url} servida desde caché como fallback`);
270
+ console.log(`\x1b[33m \x1b[0m → Servidor backend no disponible, usando versión en caché`);
271
+
272
+ return; // No continuar con el manejo de error estándar
273
+ }
274
+ }
275
+ }
276
+
277
+ console.log(`\x1b[31m[CACHE-NONE-AVAILABLE]\x1b[0m No hay versión en caché disponible para ${req.url}, servidor no disponible`);
278
+ }
279
+
280
+ /**
281
+ * Verifica si una solicitud es cacheable
282
+ * @param {Object} req - Objeto de solicitud
283
+ * @returns {boolean} - Verdadero si es cacheable
284
+ */
285
+ function isRequestCacheable(req) {
286
+ // Verificar método HTTP
287
+ if (!pluginConfig.cacheableMethods.includes(req.method)) {
288
+ return false;
289
+ }
290
+
291
+ // Verificar si tiene encabezados que indican no cachear
292
+ if (req.headers && req.headers['cache-control'] && req.headers['cache-control'].includes('no-cache')) {
293
+ return false;
294
+ }
295
+
296
+ if (req.headers && req.headers['pragma'] && req.headers['pragma'].includes('no-cache')) {
297
+ return false;
298
+ }
299
+
300
+ return true;
301
+ }
302
+
303
+ /**
304
+ * Verifica si una respuesta es cacheable
305
+ * @param {Object} req - Objeto de solicitud
306
+ * @param {Object} res - Objeto de respuesta del servidor backend
307
+ * @returns {boolean} - Verdadero si es cacheable
308
+ */
309
+ function isResponseCacheable(req, res) {
310
+ // Verificar si la solicitud era cacheable
311
+ if (!isRequestCacheable(req)) {
312
+ return false;
313
+ }
314
+
315
+ // Verificar código de estado
316
+ if (!pluginConfig.cacheableStatusCodes.includes(res.statusCode)) {
317
+ console.log(`[CACHE-DEBUG] Código de estado ${res.statusCode} no es cacheable`);
318
+ return false;
319
+ }
320
+
321
+ // Verificar encabezados de respuesta
322
+ if (res.headers && res.headers['cache-control'] && res.headers['cache-control'].includes('no-cache')) {
323
+ console.log('[CACHE-DEBUG] Header cache-control indica no-cache');
324
+ return false;
325
+ }
326
+
327
+ if (res.headers && res.headers['pragma'] && res.headers['pragma'].includes('no-cache')) {
328
+ console.log('[CACHE-DEBUG] Header pragma indica no-cache');
329
+ return false;
330
+ }
331
+
332
+ console.log(`[CACHE-DEBUG] Respuesta es cacheable: statusCode=${res.statusCode}`);
333
+ return true;
334
+ }
335
+
336
+ /**
337
+ * Genera una clave única para la caché basada en la solicitud
338
+ * @param {Object} req - Objeto de solicitud
339
+ * @returns {string} - Clave de caché
340
+ */
341
+ function generateCacheKey(req) {
342
+ // Crear una representación única de la solicitud
343
+ const keyData = {
344
+ method: req.method,
345
+ url: req.url,
346
+ headers: {
347
+ accept: req.headers.accept,
348
+ 'accept-encoding': req.headers['accept-encoding'],
349
+ 'user-agent': req.headers['user-agent']
350
+ }
351
+ };
352
+
353
+ // Convertir a string y generar hash
354
+ const keyString = JSON.stringify(keyData);
355
+ return crypto.createHash('md5').update(keyString).digest('hex');
356
+ }
357
+
358
+ /**
359
+ * Almacena una respuesta en caché
360
+ * @param {Object} req - Objeto de solicitud
361
+ * @param {Object} serverRes - Objeto de respuesta del servidor backend
362
+ * @param {string} cacheKey - Clave de caché
363
+ */
364
+ function storeResponseInCache(req, serverRes, cacheKey) {
365
+ // Debido a que no podemos interceptar directamente la respuesta en este punto,
366
+ // necesitamos una estrategia diferente. Vamos a usar el sistema de hooks
367
+ // del balanceador para interceptar la respuesta antes de que se envíe al cliente.
368
+ // Pero como no tenemos un hook específico para eso, vamos a implementar
369
+ // una solución alternativa registrando un middleware en el servidor proxy.
370
+
371
+ // Esta implementación requiere una modificación en el sistema del balanceador
372
+ // para permitir la interceptación de la respuesta, lo cual no es posible
373
+ // directamente desde un plugin sin modificar el código base.
374
+
375
+ // Por lo tanto, implementaremos una solución parcial que registra la intención
376
+ // de cachear la respuesta, y dejamos que el sistema principal maneje
377
+ // la captura de la respuesta.
378
+
379
+ console.log(`[CACHE-DEFERRED] Se detectó que la respuesta para ${req.method} ${req.url} debería ser cacheada`);
380
+ console.log(`[CACHE-DEFERRED] Clave de caché: ${cacheKey}`);
381
+
382
+ // En una implementación completa, aquí conectaríamos con un sistema
383
+ // que permite interceptar la respuesta del servidor backend antes
384
+ // de enviarla al cliente, pero eso requiere cambios en el código base.
385
+ }
386
+
387
+ /**
388
+ * Elimina la entrada menos recientemente usada de la caché
389
+ */
390
+ function evictLRUCacheEntry() {
391
+ let oldestTime = Infinity;
392
+ let oldestKey = null;
393
+
394
+ for (const [key, metadata] of cacheMetadata.entries()) {
395
+ if (metadata.createdAt < oldestTime) {
396
+ oldestTime = metadata.createdAt;
397
+ oldestKey = key;
398
+ }
399
+ }
400
+
401
+ if (oldestKey) {
402
+ cacheStorage.delete(oldestKey);
403
+ cacheMetadata.delete(oldestKey);
404
+ console.log(`[CACHE-EVICTION] Entrada LRU eliminada: ${oldestKey}`);
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Inicia la tarea de limpieza periódica de entradas expiradas
410
+ */
411
+ function startCacheCleanupTask() {
412
+ // Limpiar entradas expiradas cada 5 minutos
413
+ setInterval(() => {
414
+ const initialSize = cacheStorage.size;
415
+ const now = Date.now();
416
+
417
+ for (const [key, metadata] of cacheMetadata.entries()) {
418
+ if (now >= metadata.expiryTime) {
419
+ cacheStorage.delete(key);
420
+ cacheMetadata.delete(key);
421
+ }
422
+ }
423
+
424
+ const removedCount = initialSize - cacheStorage.size;
425
+ if (removedCount > 0) {
426
+ console.log(`[CACHE-CLEANUP] Eliminadas ${removedCount} entradas expiradas. Tamaño actual: ${cacheStorage.size}`);
427
+ }
428
+ }, 300000); // 5 minutos
429
+ }
430
+
431
+ /**
432
+ * Obtiene estadísticas de la caché
433
+ * @returns {Object} - Estadísticas de la caché
434
+ */
435
+ function getCacheStats() {
436
+ return {
437
+ size: cacheStorage.size,
438
+ maxSize: pluginConfig.maxCacheSize,
439
+ utilization: (cacheStorage.size / pluginConfig.maxCacheSize) * 100,
440
+ entries: Array.from(cacheStorage.keys())
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Limpia toda la caché
446
+ */
447
+ function clearCache() {
448
+ cacheStorage.clear();
449
+ cacheMetadata.clear();
450
+ console.log('[CACHE-CLEAR] Caché completamente limpiada');
451
+ }
452
+
453
+ /**
454
+ * Desinicializa el plugin
455
+ * @param {Object} balancer - Instancia del balanceador
456
+ */
457
+ async function deinit(balancer) {
458
+ console.log('Desactivando plugin de cache robusto...');
459
+
460
+ // Limpiar recursos si es necesario
461
+ cacheStorage.clear();
462
+ cacheMetadata.clear();
463
+
464
+ console.log('Plugin de cache robusto desactivado correctamente');
465
+ }
466
+
467
+ // Exportar funciones
468
+ module.exports = {
469
+ init,
470
+ deinit,
471
+ getCacheStats,
472
+ clearCache,
473
+ // Exportar también funciones útiles para otros módulos
474
+ isRequestCacheable,
475
+ isResponseCacheable,
476
+ generateCacheKey
477
+ };
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "Plugin de Cache Robusto",
3
+ "version": "1.0.0",
4
+ "description": "Plugin para implementar un sistema de cache robusto con hooks y filtros",
5
+ "author": "Sistema Kukuy",
6
+ "main": "index.js",
7
+ "kukuyVersion": "^1.6.0",
8
+ "hooks": [
9
+ "onRequestReceived",
10
+ "onResponseSent",
11
+ "onServerSelected"
12
+ ],
13
+ "filters": [
14
+ "request_processing"
15
+ ],
16
+ "enabled": true
17
+ }
@@ -11,11 +11,13 @@ async function init(balancer) {
11
11
  // Registrar un filtro de ejemplo
12
12
  extension.registerFilter('request_processing', async (data) => {
13
13
  const { req, res } = data;
14
- console.log(`Plugin: Procesando solicitud ${req.method} ${req.url}`);
15
-
16
- // Agregar un header personalizado
17
- req.headers['x-plugin-example'] = 'activated';
18
-
14
+ if (req && res) {
15
+ console.log(`Plugin: Procesando solicitud ${req.method} ${req.url}`);
16
+
17
+ // Agregar un header personalizado
18
+ req.headers['x-plugin-example'] = 'activated';
19
+ }
20
+
19
21
  return { allowed: true };
20
22
  }, 5);
21
23
 
@@ -2,10 +2,10 @@
2
2
  "name": "Plugin de Ejemplo",
3
3
  "version": "1.0.0",
4
4
  "description": "Plugin de ejemplo para demostrar la arquitectura de plugins",
5
- "author": "Desarrollador",
5
+ "author": "kukuy",
6
6
  "main": "index.js",
7
7
  "kukuyVersion": "^1.6.0",
8
8
  "hooks": ["onRequestReceived", "onResponseSent"],
9
9
  "filters": ["request_processing"],
10
- "enabled": true
10
+ "enabled": false
11
11
  }
@@ -0,0 +1,168 @@
1
+ const http = require('http');
2
+ const https = require('https');
3
+ const url = require('url');
4
+
5
+ let healthCheckIntervalId = null;
6
+
7
+ async function init(balancer) {
8
+ console.log('Inicializando plugin de verificación de salud periódica...');
9
+
10
+ const config = balancer.config;
11
+ const healthCheckInterval = config.healthCheckInterval || 30000; // 30 segundos por defecto
12
+ const serverPool = balancer.serverPool;
13
+
14
+ // Función para verificar la salud de un servidor
15
+ async function checkServerHealth(server) {
16
+ try {
17
+ const parsedUrl = url.parse(server.url);
18
+ const options = {
19
+ hostname: parsedUrl.hostname,
20
+ port: parsedUrl.port,
21
+ path: '/health', // Ruta estándar para verificación de salud
22
+ method: 'GET',
23
+ timeout: 5000, // 5 segundos de timeout
24
+ // Agregar headers para identificar la solicitud de health check
25
+ headers: {
26
+ 'User-Agent': 'Kukuy-Health-Check/1.0'
27
+ }
28
+ };
29
+
30
+ // Emitir hook antes de realizar el check
31
+ await executeHook(balancer, 'onHealthCheckStart', { server, checkTime: new Date() });
32
+
33
+ return new Promise((resolve) => {
34
+ const request = server.protocol === 'https:'
35
+ ? https.request(options)
36
+ : http.request(options);
37
+
38
+ request.on('response', (res) => {
39
+ // Consumir el cuerpo de la respuesta para liberar recursos
40
+ res.resume();
41
+
42
+ // Considerar saludable si obtenemos una respuesta exitosa
43
+ const isHealthy = res.statusCode >= 200 && res.statusCode < 400;
44
+
45
+ // Actualizar estado del servidor
46
+ server.healthy = isHealthy;
47
+ server.lastChecked = Date.now();
48
+
49
+ // Emitir hook después de completar el check
50
+ executeHook(balancer, 'onHealthCheckComplete', {
51
+ server,
52
+ isHealthy,
53
+ statusCode: res.statusCode,
54
+ checkTime: new Date()
55
+ }).then(() => {
56
+ // Resolver la promesa después de emitir el hook
57
+ resolve(isHealthy);
58
+ }).catch(() => {
59
+ // En caso de error al emitir el hook, resolver igualmente
60
+ resolve(isHealthy);
61
+ });
62
+ });
63
+
64
+ request.on('error', (err) => {
65
+ // Actualizar estado del servidor
66
+ server.healthy = false;
67
+ server.lastChecked = Date.now();
68
+
69
+ // Emitir hook cuando ocurre un error en el check
70
+ executeHook(balancer, 'onHealthCheckError', {
71
+ server,
72
+ error: err,
73
+ checkTime: new Date()
74
+ }).then(() => {
75
+ resolve(false);
76
+ }).catch(() => {
77
+ resolve(false);
78
+ });
79
+ });
80
+
81
+ request.on('timeout', () => {
82
+ // Actualizar estado del servidor
83
+ server.healthy = false;
84
+ server.lastChecked = Date.now();
85
+
86
+ // Emitir hook cuando ocurre un timeout en el check
87
+ executeHook(balancer, 'onHealthCheckTimeout', {
88
+ server,
89
+ checkTime: new Date()
90
+ }).then(() => {
91
+ resolve(false);
92
+ }).catch(() => {
93
+ resolve(false);
94
+ });
95
+ });
96
+
97
+ request.end();
98
+ });
99
+ } catch (error) {
100
+ // Emitir hook cuando ocurre una excepción
101
+ await executeHook(balancer, 'onHealthCheckException', {
102
+ server,
103
+ error,
104
+ checkTime: new Date()
105
+ });
106
+
107
+ // Actualizar estado del servidor
108
+ server.healthy = false;
109
+ server.lastChecked = Date.now();
110
+
111
+ return false;
112
+ }
113
+ }
114
+
115
+ // Función para verificar la salud de todos los servidores
116
+ async function checkAllServersHealth() {
117
+ console.log('[HEALTH-CHECKER] Iniciando verificación de salud periódica...');
118
+
119
+ const servers = serverPool.getServers();
120
+
121
+ for (const server of servers) {
122
+ console.log(`[HEALTH-CHECKER] Verificando salud de ${server.url}...`);
123
+ await checkServerHealth(server);
124
+ }
125
+
126
+ // Invalidar la caché de servidores saludables después de la verificación
127
+ serverPool.invalidateCache();
128
+
129
+ console.log('[HEALTH-CHECKER] Verificación de salud periódica completada.');
130
+ }
131
+
132
+ // Iniciar verificación de salud periódica
133
+ healthCheckIntervalId = setInterval(checkAllServersHealth, healthCheckInterval);
134
+
135
+ console.log(`[HEALTH-CHECKER] Verificación de salud periódica iniciada. Intervalo: ${healthCheckInterval}ms`);
136
+
137
+ // Realizar una verificación inmediata
138
+ await checkAllServersHealth();
139
+ }
140
+
141
+ async function deinit(balancer) {
142
+ console.log('Desactivando plugin de verificación de salud periódica...');
143
+
144
+ // Detener la verificación de salud periódica
145
+ if (healthCheckIntervalId) {
146
+ clearInterval(healthCheckIntervalId);
147
+ healthCheckIntervalId = null;
148
+ console.log('[HEALTH-CHECKER] Verificación de salud periódica detenida.');
149
+ }
150
+ }
151
+
152
+ /**
153
+ * Execute a hook if the balancer reference is available
154
+ * @param {Object} balancer - Reference to the balancer
155
+ * @param {string} hookName - Name of the hook to execute
156
+ * @param {Object} data - Data to pass to the hook
157
+ */
158
+ async function executeHook(balancer, hookName, data) {
159
+ if (balancer && balancer.hookManager) {
160
+ try {
161
+ await balancer.hookManager.executeHooks(hookName, data);
162
+ } catch (error) {
163
+ console.error(`Error executing ${hookName} hook:`, error);
164
+ }
165
+ }
166
+ }
167
+
168
+ module.exports = { init, deinit };