kukuy 1.5.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 +159 -188
- package/balancer.log +30 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +125 -0
- 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 +41 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- 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 -3
- package/servers_real.json +5 -0
- package/src/core/Balancer.js +176 -39
- package/src/core/ServerPool.js +2 -2
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/FilterChain.js +2 -9
- package/src/extensibility/HookManager.js +1 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +231 -0
- package/src/utils/HealthChecker.js +61 -6
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -125
- package/LICENSE +0 -680
- package/README-SSL.md +0 -165
- package/captura.png +0 -0
- package/kukuu1.webp +0 -0
- package/kukuy.workspace +0 -11
- package/optimize-mariadb.sh +0 -152
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
- package/test_optimization.js +0 -54
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin de ejemplo para Kukuy
|
|
3
|
+
* Demuestra cómo crear un plugin que se carga automáticamente
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
async function init(balancer) {
|
|
7
|
+
console.log('Inicializando plugin de ejemplo...');
|
|
8
|
+
|
|
9
|
+
const extension = balancer.getPostStartupExtension();
|
|
10
|
+
|
|
11
|
+
// Registrar un filtro de ejemplo
|
|
12
|
+
extension.registerFilter('request_processing', async (data) => {
|
|
13
|
+
const { req, res } = data;
|
|
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
|
+
|
|
21
|
+
return { allowed: true };
|
|
22
|
+
}, 5);
|
|
23
|
+
|
|
24
|
+
// Registrar un hook para cuando se recibe una solicitud
|
|
25
|
+
extension.registerHook('onRequestReceived', async ({ req, res }) => {
|
|
26
|
+
console.log(`Plugin: Solicitud recibida - ${req.method} ${req.url}`);
|
|
27
|
+
}, 5);
|
|
28
|
+
|
|
29
|
+
// Registrar un hook para cuando se envía la respuesta
|
|
30
|
+
extension.registerHook('onResponseSent', async ({ req, res, serverRes, responseTime }) => {
|
|
31
|
+
console.log(`Plugin: Respuesta enviada para ${req.url} en ${responseTime}ms`);
|
|
32
|
+
}, 5);
|
|
33
|
+
|
|
34
|
+
console.log('Plugin de ejemplo inicializado correctamente');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function deinit(balancer) {
|
|
38
|
+
console.log('Desactivando plugin de ejemplo...');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { init, deinit };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Plugin de Ejemplo",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin de ejemplo para demostrar la arquitectura de plugins",
|
|
5
|
+
"author": "kukuy",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"kukuyVersion": "^1.6.0",
|
|
8
|
+
"hooks": ["onRequestReceived", "onResponseSent"],
|
|
9
|
+
"filters": ["request_processing"],
|
|
10
|
+
"enabled": false
|
|
11
|
+
}
|