kukuy 1.4.0 → 1.6.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/.env.ssl +22 -0
- package/README.md +107 -171
- package/balancer.log +2 -0
- package/certs/auto/certificate.crt +22 -0
- package/certs/auto/private.key +28 -0
- package/kukuy-plugins/README.md +91 -0
- package/kukuy-plugins/ejemplo-plugin/index.js +39 -0
- package/kukuy-plugins/ejemplo-plugin/manifest.json +11 -0
- package/kukuy.js +51 -5
- package/package.json +8 -2
- package/servers_real.json +5 -0
- package/src/algorithms/IPHashAlgorithm.js +25 -13
- package/src/algorithms/RoundRobinAlgorithm.js +25 -27
- package/src/core/Balancer.js +201 -128
- package/src/core/ServerPool.js +46 -5
- package/src/extensibility/ExtendedFilterChain.js +90 -0
- package/src/extensibility/ExtendedHookManager.js +87 -0
- package/src/extensibility/PostStartupExtension.js +97 -0
- package/src/plugins/PluginManager.js +183 -0
- package/src/utils/HealthChecker.js +11 -5
- package/src/utils/ProfessionalMetrics.js +41 -24
- package/start-ssl-config.sh +24 -0
- package/start-ssl.sh +26 -0
- package/webpage/index.html +1 -1
- package/.ctagsd/ctagsd.json +0 -954
- package/.ctagsd/file_list.txt +0 -100
- package/.ctagsd/tags.db +0 -0
- package/CHANGELOG.md +0 -101
- package/LICENSE +0 -680
- package/captura.png +0 -0
- package/kukuy.workspace +0 -11
- package/restart-balancer.sh +0 -10
- package/scripts/load_test.py +0 -151
- package/stress-test.js +0 -190
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kukuy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Balanceador de carga Backend",
|
|
5
5
|
"main": "kukuy.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
10
10
|
},
|
|
11
11
|
"keywords": [
|
|
12
|
+
"backend",
|
|
12
13
|
"load-balancer",
|
|
13
14
|
"round-robin",
|
|
14
15
|
"ip-hash",
|
|
@@ -22,5 +23,10 @@
|
|
|
22
23
|
"jerk-hooked-lib": "^2.0.0",
|
|
23
24
|
"ws": "^8.19.0"
|
|
24
25
|
},
|
|
25
|
-
"type": "commonjs"
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"homepage": "https://bsanchez.unaux.com/",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://gitlab.com/bytedogssyndicate1/kukuy"
|
|
31
|
+
}
|
|
26
32
|
}
|
package/servers_real.json
CHANGED
|
@@ -9,6 +9,10 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
|
|
|
9
9
|
super();
|
|
10
10
|
// Mapa para almacenar la asociación persistente IP -> índice de servidor
|
|
11
11
|
this.ipToServerIndexMap = new Map();
|
|
12
|
+
// Caché para servidores filtrados
|
|
13
|
+
this.filteredServersCache = new Map();
|
|
14
|
+
this.cacheTimestamp = 0;
|
|
15
|
+
this.cacheValidity = 1000; // 1 segundo de validez de caché
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
/**
|
|
@@ -22,10 +26,23 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
|
|
|
22
26
|
return null;
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
//
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
// Obtener timestamp actual
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
|
|
32
|
+
// Verificar si la caché es válida
|
|
33
|
+
let availableServers;
|
|
34
|
+
if (now - this.cacheTimestamp > this.cacheValidity || !this.filteredServersCache.has(servers)) {
|
|
35
|
+
// Filtrar servidores activos y saludables
|
|
36
|
+
availableServers = servers.filter(server =>
|
|
37
|
+
server.active !== false && server.healthy && server.failedAttempts < 5
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Actualizar caché
|
|
41
|
+
this.filteredServersCache.set(servers, availableServers);
|
|
42
|
+
this.cacheTimestamp = now;
|
|
43
|
+
} else {
|
|
44
|
+
availableServers = this.filteredServersCache.get(servers);
|
|
45
|
+
}
|
|
29
46
|
|
|
30
47
|
if (availableServers.length === 0) {
|
|
31
48
|
return null;
|
|
@@ -43,7 +60,6 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
|
|
|
43
60
|
const cachedServer = availableServers[serverIndex];
|
|
44
61
|
if (cachedServer && cachedServer.healthy && cachedServer.active) {
|
|
45
62
|
// Servidor sigue disponible, usarlo
|
|
46
|
-
console.log(`IPHash: IP=${clientIP} reutilizando servidor previamente asignado (índice ${serverIndex})`);
|
|
47
63
|
return cachedServer;
|
|
48
64
|
} else {
|
|
49
65
|
// Servidor ya no está disponible, eliminar la asociación
|
|
@@ -61,9 +77,6 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
|
|
|
61
77
|
|
|
62
78
|
const selectedServer = availableServers[serverIndex];
|
|
63
79
|
|
|
64
|
-
// Log para mostrar el mapa de hash
|
|
65
|
-
console.log(`IPHash: IP=${clientIP}, Hash=${hash}, Asignando servidor índice ${serverIndex} (ID: ${selectedServer.id}, URL: ${selectedServer.url})`);
|
|
66
|
-
|
|
67
80
|
return selectedServer;
|
|
68
81
|
}
|
|
69
82
|
|
|
@@ -104,12 +117,11 @@ class IPHashAlgorithm extends LoadBalancingAlgorithm {
|
|
|
104
117
|
*/
|
|
105
118
|
simpleHash(str) {
|
|
106
119
|
let hash = 0;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
hash = ((hash << 5) - hash) +
|
|
110
|
-
hash = hash & hash; // Convertir a 32-bit integer
|
|
120
|
+
const len = str.length;
|
|
121
|
+
for (let i = 0; i < len; i++) {
|
|
122
|
+
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
111
123
|
}
|
|
112
|
-
return Math.abs(hash);
|
|
124
|
+
return Math.abs(hash | 0); // Convertir a 32-bit integer
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
/**
|
|
@@ -8,6 +8,10 @@ class RoundRobinAlgorithm extends LoadBalancingAlgorithm {
|
|
|
8
8
|
constructor() {
|
|
9
9
|
super();
|
|
10
10
|
this.currentIndex = 0;
|
|
11
|
+
// Caché para servidores filtrados
|
|
12
|
+
this.filteredServersCache = new Map();
|
|
13
|
+
this.cacheTimestamp = 0;
|
|
14
|
+
this.cacheValidity = 1000; // 1 segundo de validez de caché
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -21,39 +25,33 @@ class RoundRobinAlgorithm extends LoadBalancingAlgorithm {
|
|
|
21
25
|
return null;
|
|
22
26
|
}
|
|
23
27
|
|
|
24
|
-
//
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// Obtener timestamp actual
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
|
|
31
|
+
// Verificar si la caché es válida
|
|
32
|
+
let availableServers;
|
|
33
|
+
if (now - this.cacheTimestamp > this.cacheValidity || !this.filteredServersCache.has(servers)) {
|
|
34
|
+
// Filtrar servidores activos y saludables
|
|
35
|
+
availableServers = servers.filter(server =>
|
|
36
|
+
server.active !== false && server.healthy && server.failedAttempts < 5
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Actualizar caché
|
|
40
|
+
this.filteredServersCache.set(servers, availableServers);
|
|
41
|
+
this.cacheTimestamp = now;
|
|
42
|
+
} else {
|
|
43
|
+
availableServers = this.filteredServersCache.get(servers);
|
|
44
|
+
}
|
|
28
45
|
|
|
29
46
|
if (availableServers.length === 0) {
|
|
30
47
|
return null;
|
|
31
48
|
}
|
|
32
49
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const candidateIndex = (this.currentIndex + i) % availableServers.length;
|
|
37
|
-
const candidateServer = availableServers[candidateIndex];
|
|
38
|
-
|
|
39
|
-
// Verificar si el servidor candidato está realmente disponible
|
|
40
|
-
if (this.isServerAvailable(candidateServer)) {
|
|
41
|
-
this.currentIndex = candidateIndex + 1; // Preparar para la próxima selección
|
|
42
|
-
return candidateServer;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Si ningún servidor está disponible, devolver null
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
50
|
+
// Obtener servidor usando round robin sin bucle
|
|
51
|
+
const server = availableServers[this.currentIndex % availableServers.length];
|
|
52
|
+
this.currentIndex = (this.currentIndex + 1) % availableServers.length;
|
|
49
53
|
|
|
50
|
-
|
|
51
|
-
isServerAvailable(server) {
|
|
52
|
-
// Un servidor está disponible si:
|
|
53
|
-
// 1. Está marcado como healthy
|
|
54
|
-
// 2. Está activo
|
|
55
|
-
// 3. No ha superado el número máximo de intentos fallidos
|
|
56
|
-
return server.healthy && server.active && server.failedAttempts < 5;
|
|
54
|
+
return server;
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
/**
|
package/src/core/Balancer.js
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const url = require('url');
|
|
21
|
-
const {
|
|
22
|
-
const {
|
|
21
|
+
const { ExtendedHookManager } = require('../extensibility/ExtendedHookManager');
|
|
22
|
+
const { ExtendedFilterChain } = require('../extensibility/ExtendedFilterChain');
|
|
23
23
|
const { ServerPool } = require('./ServerPool');
|
|
24
24
|
const { AlgorithmManager } = require('../algorithms/AlgorithmManager');
|
|
25
25
|
const { ConfigManager } = require('../config/ConfigManager');
|
|
@@ -31,12 +31,14 @@ const { HttpsBalancer } = require('../protocol/HttpsBalancer');
|
|
|
31
31
|
const { ProfessionalMetrics } = require('../utils/ProfessionalMetrics');
|
|
32
32
|
const { WebDashboard } = require('../dashboard/WebDashboard');
|
|
33
33
|
const { WebSocketServer } = require('../dashboard/WebSocketServer');
|
|
34
|
+
const { PostStartupExtension } = require('../extensibility/PostStartupExtension');
|
|
35
|
+
const { PluginManager } = require('../plugins/PluginManager');
|
|
34
36
|
|
|
35
37
|
class Balancer {
|
|
36
38
|
constructor() {
|
|
37
39
|
this.config = ConfigManager.getInstance();
|
|
38
|
-
this.hookManager = new
|
|
39
|
-
this.filterChain = new
|
|
40
|
+
this.hookManager = new ExtendedHookManager();
|
|
41
|
+
this.filterChain = new ExtendedFilterChain();
|
|
40
42
|
this.serverPool = new ServerPool();
|
|
41
43
|
this.algorithmManager = new AlgorithmManager();
|
|
42
44
|
this.routeLoader = new RouteLoader();
|
|
@@ -56,6 +58,64 @@ class Balancer {
|
|
|
56
58
|
|
|
57
59
|
// Inicializar servidor WebSocket para datos en tiempo real
|
|
58
60
|
this.webSocketServer = new WebSocketServer(this);
|
|
61
|
+
|
|
62
|
+
// Inicializar sistema de extensión post-inicio
|
|
63
|
+
this.postStartupExtension = new PostStartupExtension(this);
|
|
64
|
+
|
|
65
|
+
// Activar plugin de depuración si está habilitado
|
|
66
|
+
this.initializeDebugPlugin();
|
|
67
|
+
|
|
68
|
+
// Inicializar sistema de plugins
|
|
69
|
+
this.pluginManager = new PluginManager(this);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
initializeDebugPlugin() {
|
|
73
|
+
// Verificar si el plugin de depuración está habilitado
|
|
74
|
+
const debugEnabled = process.env.DEBUG_REQUEST_HEADERS === 'true' ||
|
|
75
|
+
process.env.KUKUY_DEBUG_HEADERS === 'true';
|
|
76
|
+
|
|
77
|
+
if (debugEnabled) {
|
|
78
|
+
// Registrar hooks para mostrar encabezados y servidor de destino
|
|
79
|
+
|
|
80
|
+
// Hook para mostrar encabezados cuando se recibe una solicitud
|
|
81
|
+
this.hookManager.addHook('onRequestReceived', ({ req, res }) => {
|
|
82
|
+
console.log('\n--- Nueva Solicitud Recibida ---');
|
|
83
|
+
console.log(`Método: ${req.method}`);
|
|
84
|
+
console.log(`URL: ${req.url}`);
|
|
85
|
+
console.log('Encabezados:');
|
|
86
|
+
|
|
87
|
+
// Mostrar todos los encabezados de la solicitud
|
|
88
|
+
for (const [header, value] of Object.entries(req.headers)) {
|
|
89
|
+
console.log(` ${header}: ${value}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log('-----------------------------\n');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Hook para mostrar el servidor de destino asignado
|
|
96
|
+
this.hookManager.addHook('onServerSelected', ({ req, res, server }) => {
|
|
97
|
+
console.log('\n--- Servidor de Destino Asignado ---');
|
|
98
|
+
console.log(`Solicitud: ${req.method} ${req.url}`);
|
|
99
|
+
console.log(`Servidor destino: ${server.url}`);
|
|
100
|
+
console.log(`ID del servidor: ${server.id}`);
|
|
101
|
+
console.log(`Protocolo: ${server.protocol}`);
|
|
102
|
+
console.log(`Host: ${server.host}`);
|
|
103
|
+
console.log(`Etiquetas: ${server.tags.join(', ') || 'ninguna'}`);
|
|
104
|
+
console.log('----------------------------------\n');
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Hook para mostrar información cuando se envía la respuesta
|
|
108
|
+
this.hookManager.addHook('onResponseSent', ({ req, res, serverRes, responseTime }) => {
|
|
109
|
+
console.log('\n--- Respuesta Enviada ---');
|
|
110
|
+
console.log(`Solicitud: ${req.method} ${req.url}`);
|
|
111
|
+
console.log(`Código de estado: ${serverRes.statusCode}`);
|
|
112
|
+
console.log(`Tiempo de respuesta: ${responseTime}ms`);
|
|
113
|
+
console.log('------------------------\n');
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log('Plugin de depuración de solicitud activado');
|
|
117
|
+
console.log('Mostrando encabezados de solicitud y servidores de destino en stdout');
|
|
118
|
+
}
|
|
59
119
|
}
|
|
60
120
|
|
|
61
121
|
loadConfiguration() {
|
|
@@ -88,7 +148,7 @@ class Balancer {
|
|
|
88
148
|
}
|
|
89
149
|
}
|
|
90
150
|
|
|
91
|
-
start() {
|
|
151
|
+
async start() {
|
|
92
152
|
// Crear servidor HTTP usando HttpBalancer
|
|
93
153
|
if (this.config.httpPort) {
|
|
94
154
|
this.httpBalancer = new HttpBalancer(this.config.httpPort, this.handleRequest.bind(this));
|
|
@@ -112,6 +172,9 @@ class Balancer {
|
|
|
112
172
|
// Iniciar servidor WebSocket para datos en tiempo real
|
|
113
173
|
const wsPort = process.env.WEBSOCKET_PORT || 8083;
|
|
114
174
|
this.webSocketServer.start(wsPort);
|
|
175
|
+
|
|
176
|
+
// Cargar plugins después de iniciar componentes principales
|
|
177
|
+
await this.pluginManager.loadPlugins();
|
|
115
178
|
}
|
|
116
179
|
|
|
117
180
|
async handleRequest(clientReq, clientRes) {
|
|
@@ -178,11 +241,12 @@ class Balancer {
|
|
|
178
241
|
|
|
179
242
|
if (matchedRoute && matchedRoute.target) {
|
|
180
243
|
// Si hay una ruta específica, usar solo los servidores definidos para esa ruta
|
|
181
|
-
const routeServers = this.serverPool.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
244
|
+
const routeServers = this.serverPool.getServersByTag(matchedRoute.target);
|
|
245
|
+
if (routeServers && routeServers.length > 0) {
|
|
246
|
+
const healthyRouteServers = routeServers.filter(server => server.healthy && server.active);
|
|
247
|
+
if (healthyRouteServers.length > 0) {
|
|
248
|
+
return this.algorithmManager.selectServer(healthyRouteServers, requestContext);
|
|
249
|
+
}
|
|
186
250
|
}
|
|
187
251
|
}
|
|
188
252
|
|
|
@@ -197,10 +261,10 @@ class Balancer {
|
|
|
197
261
|
let retryCount = 0;
|
|
198
262
|
const maxRetries = this.serverPool.getHealthyServers().length; // Máximo de reintentos según servidores saludables
|
|
199
263
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const parsedUrl = url.parse(
|
|
264
|
+
// Función auxiliar para manejar la solicitud
|
|
265
|
+
const makeRequest = (server) => {
|
|
266
|
+
return new Promise((resolve, reject) => {
|
|
267
|
+
const parsedUrl = url.parse(server.url);
|
|
204
268
|
const targetOptions = {
|
|
205
269
|
hostname: parsedUrl.hostname,
|
|
206
270
|
port: parsedUrl.port,
|
|
@@ -209,133 +273,134 @@ class Balancer {
|
|
|
209
273
|
headers: clientReq.headers
|
|
210
274
|
};
|
|
211
275
|
|
|
212
|
-
const httpModule =
|
|
276
|
+
const httpModule = server.protocol === 'https:' ? require('https') : require('http');
|
|
213
277
|
const proxyReq = httpModule.request(targetOptions);
|
|
214
278
|
|
|
215
279
|
// Registrar intento de conexión a servidor target
|
|
216
|
-
this.balancerLogger.logTargetSelection(
|
|
280
|
+
this.balancerLogger.logTargetSelection(server, 'round_robin');
|
|
217
281
|
|
|
218
282
|
// Enviar cuerpo de la solicitud
|
|
219
283
|
clientReq.pipe(proxyReq);
|
|
220
284
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
responseTime,
|
|
248
|
-
statusCode: serverRes.statusCode
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// Actualizar estado del servidor en las métricas
|
|
252
|
-
this.metricsCollector.updateServerStatus(targetServer.id, 'online');
|
|
253
|
-
|
|
254
|
-
// Emitir hook cuando se envía la respuesta
|
|
255
|
-
this.hookManager.executeHooks('onResponseSent', {
|
|
256
|
-
req: clientReq,
|
|
257
|
-
res: clientRes,
|
|
258
|
-
serverRes,
|
|
259
|
-
server: targetServer,
|
|
260
|
-
responseTime
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
resolve(); // Resolver la promesa al completar la solicitud
|
|
285
|
+
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
|
+
// Calcular tiempo de respuesta
|
|
293
|
+
const responseTime = Date.now() - startTime;
|
|
294
|
+
|
|
295
|
+
// Registrar métricas profesionales
|
|
296
|
+
this.metricsCollector.recordRequest(
|
|
297
|
+
clientReq.method,
|
|
298
|
+
clientReq.url,
|
|
299
|
+
server.id,
|
|
300
|
+
responseTime,
|
|
301
|
+
serverRes.statusCode,
|
|
302
|
+
serverRes.statusCode < 400
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Registrar servidor online
|
|
306
|
+
this.balancerLogger.logOnlineTarget(server, {
|
|
307
|
+
url: clientReq.url,
|
|
308
|
+
method: clientReq.method,
|
|
309
|
+
responseTime,
|
|
310
|
+
statusCode: serverRes.statusCode
|
|
264
311
|
});
|
|
265
312
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.logger.error(`Error conectando con servidor ${targetServer.url}: ${err.message}`);
|
|
270
|
-
|
|
271
|
-
// Registrar servidor offline
|
|
272
|
-
this.balancerLogger.logOfflineTarget(targetServer, err, {
|
|
273
|
-
url: clientReq.url,
|
|
274
|
-
method: clientReq.method,
|
|
275
|
-
responseTime
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
// Actualizar estado del servidor en las métricas
|
|
279
|
-
this.metricsCollector.updateServerStatus(targetServer.id, 'offline');
|
|
280
|
-
|
|
281
|
-
// Registrar métricas profesionales para error
|
|
282
|
-
this.metricsCollector.recordRequest(
|
|
283
|
-
clientReq.method,
|
|
284
|
-
clientReq.url,
|
|
285
|
-
targetServer.id,
|
|
286
|
-
responseTime,
|
|
287
|
-
502,
|
|
288
|
-
false
|
|
289
|
-
);
|
|
290
|
-
|
|
291
|
-
// Marcar servidor como fallido
|
|
292
|
-
this.serverPool.markServerAsFailed(targetServer.id);
|
|
293
|
-
|
|
294
|
-
// Emitir hook cuando ocurre un error con el servidor
|
|
295
|
-
await this.hookManager.executeHooks('onServerError', {
|
|
296
|
-
req: clientReq,
|
|
297
|
-
res: clientRes,
|
|
298
|
-
server: targetServer,
|
|
299
|
-
error: err,
|
|
300
|
-
responseTime
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
// Si hay más servidores disponibles, reintentar con otro servidor
|
|
304
|
-
if (retryCount < maxRetries) {
|
|
305
|
-
retryCount++;
|
|
306
|
-
|
|
307
|
-
// Obtener un nuevo servidor objetivo (excluyendo el que falló)
|
|
308
|
-
const availableServers = this.serverPool.getHealthyServers().filter(s => s.id !== targetServer.id);
|
|
309
|
-
if (availableServers.length > 0) {
|
|
310
|
-
// Seleccionar un nuevo servidor
|
|
311
|
-
targetServer = availableServers[(retryCount - 1) % availableServers.length];
|
|
312
|
-
console.log(`Reintentando con servidor alternativo: ${targetServer.url}`);
|
|
313
|
-
|
|
314
|
-
// Volver a intentar la solicitud con el nuevo servidor
|
|
315
|
-
setTimeout(() => {
|
|
316
|
-
this.proxyRequest(clientReq, clientRes, targetServer).then(resolve).catch(reject);
|
|
317
|
-
}, 10); // Pequeña pausa antes de reintentar
|
|
318
|
-
|
|
319
|
-
return; // Salir para evitar resolver/rechazar la promesa dos veces
|
|
320
|
-
}
|
|
321
|
-
}
|
|
313
|
+
// Actualizar estado del servidor en las métricas
|
|
314
|
+
this.metricsCollector.updateServerStatus(server.id, 'online');
|
|
322
315
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
316
|
+
// Emitir hook cuando se envía la respuesta
|
|
317
|
+
this.hookManager.executeHooks('onResponseSent', {
|
|
318
|
+
req: clientReq,
|
|
319
|
+
res: clientRes,
|
|
320
|
+
serverRes,
|
|
321
|
+
server,
|
|
322
|
+
responseTime
|
|
323
|
+
});
|
|
328
324
|
|
|
329
|
-
|
|
325
|
+
resolve(); // Resolver la promesa al completar la solicitud
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
proxyReq.on('error', async (err) => {
|
|
329
|
+
const responseTime = Date.now() - startTime;
|
|
330
|
+
|
|
331
|
+
this.logger.error(`Error conectando con servidor ${server.url}: ${err.message}`);
|
|
332
|
+
|
|
333
|
+
// Registrar servidor offline
|
|
334
|
+
this.balancerLogger.logOfflineTarget(server, err, {
|
|
335
|
+
url: clientReq.url,
|
|
336
|
+
method: clientReq.method,
|
|
337
|
+
responseTime
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Actualizar estado del servidor en las métricas
|
|
341
|
+
this.metricsCollector.updateServerStatus(server.id, 'offline');
|
|
342
|
+
|
|
343
|
+
// Registrar métricas profesionales para error
|
|
344
|
+
this.metricsCollector.recordRequest(
|
|
345
|
+
clientReq.method,
|
|
346
|
+
clientReq.url,
|
|
347
|
+
server.id,
|
|
348
|
+
responseTime,
|
|
349
|
+
502,
|
|
350
|
+
false
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// Marcar servidor como fallido
|
|
354
|
+
this.serverPool.markServerAsFailed(server.id);
|
|
355
|
+
|
|
356
|
+
// Emitir hook cuando ocurre un error con el servidor
|
|
357
|
+
await this.hookManager.executeHooks('onServerError', {
|
|
358
|
+
req: clientReq,
|
|
359
|
+
res: clientRes,
|
|
360
|
+
server,
|
|
361
|
+
error: err,
|
|
362
|
+
responseTime
|
|
330
363
|
});
|
|
364
|
+
|
|
365
|
+
// Si hay más servidores disponibles, reintentar con otro servidor
|
|
366
|
+
if (retryCount < maxRetries) {
|
|
367
|
+
retryCount++;
|
|
368
|
+
|
|
369
|
+
// Obtener un nuevo servidor objetivo (excluyendo los que ya fallaron)
|
|
370
|
+
const availableServers = this.serverPool.getHealthyServers().filter(s => s.id !== server.id);
|
|
371
|
+
if (availableServers.length > 0) {
|
|
372
|
+
// Seleccionar un nuevo servidor
|
|
373
|
+
const nextServer = availableServers[(retryCount - 1) % availableServers.length];
|
|
374
|
+
console.log(`Reintentando con servidor alternativo: ${nextServer.url}`);
|
|
375
|
+
|
|
376
|
+
// Volver a intentar la solicitud con el nuevo servidor
|
|
377
|
+
setTimeout(() => {
|
|
378
|
+
makeRequest(nextServer).then(resolve).catch(reject);
|
|
379
|
+
}, 10); // Pequeña pausa antes de reintentar
|
|
380
|
+
|
|
381
|
+
return; // Salir para evitar resolver/rechazar la promesa dos veces
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Si no hay más reintentos posibles, enviar error al cliente
|
|
386
|
+
if (!clientRes.headersSent) {
|
|
387
|
+
clientRes.writeHead(502);
|
|
388
|
+
clientRes.end('Bad Gateway');
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
resolve(); // Resolver para evitar múltiples llamadas
|
|
331
392
|
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Intentar enviar la solicitud con reintento si el servidor inicial falla
|
|
397
|
+
try {
|
|
398
|
+
await makeRequest(targetServer);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error('Error en proxyRequest:', error);
|
|
401
|
+
if (!clientRes.headersSent) {
|
|
402
|
+
clientRes.writeHead(500);
|
|
403
|
+
clientRes.end('Internal Server Error');
|
|
339
404
|
}
|
|
340
405
|
}
|
|
341
406
|
}
|
|
@@ -348,6 +413,14 @@ class Balancer {
|
|
|
348
413
|
this.httpsBalancer.stop();
|
|
349
414
|
}
|
|
350
415
|
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Get the post-startup extension API
|
|
419
|
+
* @returns {PostStartupExtension} The extension API
|
|
420
|
+
*/
|
|
421
|
+
getPostStartupExtension() {
|
|
422
|
+
return this.postStartupExtension;
|
|
423
|
+
}
|
|
351
424
|
}
|
|
352
425
|
|
|
353
426
|
module.exports = { Balancer };
|