bs9 1.0.0 → 1.1.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 +97 -9
- package/bin/bs9 +58 -7
- package/dist/bs9-064xs9r9. +148 -0
- package/dist/bs9-0gqcrp5t. +144 -0
- package/dist/bs9-nv7nseny. +197 -0
- package/dist/bs9-r6b9zpw0. +156 -0
- package/dist/bs9.js +2 -0
- package/package.json +3 -4
- package/src/commands/deps.ts +295 -0
- package/src/commands/profile.ts +338 -0
- package/src/commands/restart.ts +28 -3
- package/src/commands/start.ts +201 -21
- package/src/commands/status.ts +154 -109
- package/src/commands/stop.ts +31 -3
- package/src/commands/web.ts +29 -3
- package/src/database/pool.ts +335 -0
- package/src/loadbalancer/manager.ts +481 -0
- package/src/macos/launchd.ts +402 -0
- package/src/platform/detect.ts +137 -0
- package/src/windows/service.ts +391 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { serve } from "bun";
|
|
4
|
+
import { setTimeout } from "node:timers/promises";
|
|
5
|
+
|
|
6
|
+
// Security: Input validation functions
|
|
7
|
+
function isValidHost(host: string): boolean {
|
|
8
|
+
const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
9
|
+
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
10
|
+
const localhostRegex = /^(localhost|127\.0\.0\.1)$/;
|
|
11
|
+
|
|
12
|
+
return hostnameRegex.test(host) && host.length <= 253 ||
|
|
13
|
+
ipv4Regex.test(host) ||
|
|
14
|
+
localhostRegex.test(host);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isValidPort(port: number): boolean {
|
|
18
|
+
return !isNaN(port) && port >= 1 && port <= 65535;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isValidPath(path: string): boolean {
|
|
22
|
+
// Prevent path traversal attacks
|
|
23
|
+
return !path.includes('..') && !path.includes('~') &&
|
|
24
|
+
/^[a-zA-Z0-9\-_\/]*$/.test(path) && path.length <= 256;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sanitizeHeaders(headers: Record<string, string>): Record<string, string> {
|
|
28
|
+
const sanitized: Record<string, string> = {};
|
|
29
|
+
const allowedHeaders = [
|
|
30
|
+
'content-type', 'content-length', 'accept', 'accept-encoding',
|
|
31
|
+
'accept-language', 'user-agent', 'authorization', 'x-forwarded-for',
|
|
32
|
+
'x-real-ip', 'x-forwarded-proto', 'host', 'connection'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
36
|
+
const lowerKey = key.toLowerCase();
|
|
37
|
+
if (allowedHeaders.includes(lowerKey)) {
|
|
38
|
+
// Remove potential injection attempts
|
|
39
|
+
sanitized[key] = value.replace(/[\r\n]/g, '').substring(0, 1024);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return sanitized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface LoadBalancerConfig {
|
|
47
|
+
port: number;
|
|
48
|
+
algorithm: 'round-robin' | 'least-connections' | 'weighted-round-robin';
|
|
49
|
+
healthCheck: {
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
path: string;
|
|
52
|
+
interval: number;
|
|
53
|
+
timeout: number;
|
|
54
|
+
retries: number;
|
|
55
|
+
};
|
|
56
|
+
backends: BackendServer[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface BackendServer {
|
|
60
|
+
id: string;
|
|
61
|
+
host: string;
|
|
62
|
+
port: number;
|
|
63
|
+
weight?: number;
|
|
64
|
+
connections: number;
|
|
65
|
+
healthy: boolean;
|
|
66
|
+
lastHealthCheck: number;
|
|
67
|
+
responseTime: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface LoadBalancerStats {
|
|
71
|
+
totalRequests: number;
|
|
72
|
+
activeConnections: number;
|
|
73
|
+
backendStats: Array<{
|
|
74
|
+
id: string;
|
|
75
|
+
host: string;
|
|
76
|
+
port: number;
|
|
77
|
+
connections: number;
|
|
78
|
+
healthy: boolean;
|
|
79
|
+
responseTime: number;
|
|
80
|
+
requestsHandled: number;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class LoadBalancer {
|
|
85
|
+
private config: LoadBalancerConfig;
|
|
86
|
+
private currentIndex = 0;
|
|
87
|
+
private stats: LoadBalancerStats = {
|
|
88
|
+
totalRequests: 0,
|
|
89
|
+
activeConnections: 0,
|
|
90
|
+
backendStats: [],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
constructor(config: LoadBalancerConfig) {
|
|
94
|
+
// Security: Validate configuration
|
|
95
|
+
if (!isValidPort(config.port)) {
|
|
96
|
+
throw new Error(`❌ Security: Invalid load balancer port: ${config.port}`);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!isValidPath(config.healthCheck.path)) {
|
|
100
|
+
throw new Error(`❌ Security: Invalid health check path: ${config.healthCheck.path}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Security: Validate backends
|
|
104
|
+
for (const backend of config.backends) {
|
|
105
|
+
if (!isValidHost(backend.host)) {
|
|
106
|
+
throw new Error(`❌ Security: Invalid backend host: ${backend.host}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!isValidPort(backend.port)) {
|
|
110
|
+
throw new Error(`❌ Security: Invalid backend port: ${backend.port}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(backend.id) || backend.id.length > 64) {
|
|
114
|
+
throw new Error(`❌ Security: Invalid backend ID: ${backend.id}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.config = config;
|
|
119
|
+
this.initializeStats();
|
|
120
|
+
|
|
121
|
+
// Start health checking
|
|
122
|
+
if (config.healthCheck.enabled) {
|
|
123
|
+
this.startHealthChecking();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private initializeStats(): void {
|
|
128
|
+
this.stats.backendStats = this.config.backends.map(backend => ({
|
|
129
|
+
id: backend.id,
|
|
130
|
+
host: backend.host,
|
|
131
|
+
port: backend.port,
|
|
132
|
+
connections: 0,
|
|
133
|
+
healthy: true,
|
|
134
|
+
responseTime: 0,
|
|
135
|
+
requestsHandled: 0,
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private startHealthChecking(): void {
|
|
140
|
+
setInterval(async () => {
|
|
141
|
+
await this.performHealthChecks();
|
|
142
|
+
}, this.config.healthCheck.interval);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private async performHealthChecks(): Promise<void> {
|
|
146
|
+
for (const backend of this.config.backends) {
|
|
147
|
+
try {
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
const response = await fetch(`http://${backend.host}:${backend.port}${this.config.healthCheck.path}`, {
|
|
150
|
+
method: 'GET',
|
|
151
|
+
signal: AbortSignal.timeout(this.config.healthCheck.timeout),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const responseTime = Date.now() - startTime;
|
|
155
|
+
|
|
156
|
+
if (response.ok) {
|
|
157
|
+
backend.healthy = true;
|
|
158
|
+
backend.responseTime = responseTime;
|
|
159
|
+
} else {
|
|
160
|
+
backend.healthy = false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
backend.lastHealthCheck = Date.now();
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
backend.healthy = false;
|
|
167
|
+
backend.lastHealthCheck = Date.now();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private selectBackend(): BackendServer | null {
|
|
173
|
+
const healthyBackends = this.config.backends.filter(b => b.healthy);
|
|
174
|
+
|
|
175
|
+
if (healthyBackends.length === 0) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
switch (this.config.algorithm) {
|
|
180
|
+
case 'round-robin':
|
|
181
|
+
return this.selectRoundRobin(healthyBackends);
|
|
182
|
+
case 'least-connections':
|
|
183
|
+
return this.selectLeastConnections(healthyBackends);
|
|
184
|
+
case 'weighted-round-robin':
|
|
185
|
+
return this.selectWeightedRoundRobin(healthyBackends);
|
|
186
|
+
default:
|
|
187
|
+
return healthyBackends[0];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private selectRoundRobin(backends: BackendServer[]): BackendServer {
|
|
192
|
+
const backend = backends[this.currentIndex % backends.length];
|
|
193
|
+
this.currentIndex++;
|
|
194
|
+
return backend;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private selectLeastConnections(backends: BackendServer[]): BackendServer {
|
|
198
|
+
return backends.reduce((min, current) =>
|
|
199
|
+
current.connections < min.connections ? current : min
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private selectWeightedRoundRobin(backends: BackendServer[]): BackendServer {
|
|
204
|
+
const totalWeight = backends.reduce((sum, b) => sum + (b.weight || 1), 0);
|
|
205
|
+
let random = Math.random() * totalWeight;
|
|
206
|
+
|
|
207
|
+
for (const backend of backends) {
|
|
208
|
+
random -= (backend.weight || 1);
|
|
209
|
+
if (random <= 0) {
|
|
210
|
+
return backend;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return backends[0];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
public async handleRequest(request: Request): Promise<Response> {
|
|
218
|
+
const backend = this.selectBackend();
|
|
219
|
+
|
|
220
|
+
if (!backend) {
|
|
221
|
+
return new Response('Service Unavailable - No healthy backends', {
|
|
222
|
+
status: 503,
|
|
223
|
+
headers: { 'Retry-After': '5' }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Update stats
|
|
228
|
+
this.stats.totalRequests++;
|
|
229
|
+
backend.connections++;
|
|
230
|
+
this.stats.activeConnections++;
|
|
231
|
+
|
|
232
|
+
const backendStats = this.stats.backendStats.find(s => s.id === backend.id);
|
|
233
|
+
if (backendStats) {
|
|
234
|
+
backendStats.requestsHandled++;
|
|
235
|
+
backendStats.connections = backend.connections;
|
|
236
|
+
backendStats.healthy = backend.healthy;
|
|
237
|
+
backendStats.responseTime = backend.responseTime;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const startTime = Date.now();
|
|
242
|
+
|
|
243
|
+
// Forward request to backend
|
|
244
|
+
const url = new URL(request.url);
|
|
245
|
+
const backendUrl = `http://${backend.host}:${backend.port}${url.pathname}${url.search}`;
|
|
246
|
+
|
|
247
|
+
const response = await fetch(backendUrl, {
|
|
248
|
+
method: request.method,
|
|
249
|
+
headers: request.headers,
|
|
250
|
+
body: request.body,
|
|
251
|
+
signal: AbortSignal.timeout(5000),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const responseTime = Date.now() - startTime;
|
|
255
|
+
backend.responseTime = responseTime;
|
|
256
|
+
|
|
257
|
+
// Create response with backend data
|
|
258
|
+
const responseBody = await response.arrayBuffer();
|
|
259
|
+
const forwardedResponse = new Response(responseBody, {
|
|
260
|
+
status: response.status,
|
|
261
|
+
headers: response.headers,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Add load balancer headers
|
|
265
|
+
forwardedResponse.headers.set('X-Load-Balancer-Backend', `${backend.host}:${backend.port}`);
|
|
266
|
+
forwardedResponse.headers.set('X-Load-Balancer-Response-Time', responseTime.toString());
|
|
267
|
+
|
|
268
|
+
return forwardedResponse;
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
backend.healthy = false;
|
|
272
|
+
return new Response('Bad Gateway', { status: 502 });
|
|
273
|
+
} finally {
|
|
274
|
+
// Update connection count
|
|
275
|
+
backend.connections--;
|
|
276
|
+
this.stats.activeConnections--;
|
|
277
|
+
|
|
278
|
+
if (backendStats) {
|
|
279
|
+
backendStats.connections = backend.connections;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
public getStats(): LoadBalancerStats {
|
|
285
|
+
return { ...this.stats };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
public getConfig(): LoadBalancerConfig {
|
|
289
|
+
return { ...this.config };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public updateConfig(newConfig: Partial<LoadBalancerConfig>): void {
|
|
293
|
+
this.config = { ...this.config, ...newConfig };
|
|
294
|
+
|
|
295
|
+
// Update backend stats if backends changed
|
|
296
|
+
if (newConfig.backends) {
|
|
297
|
+
this.initializeStats();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// CLI command for load balancer management
|
|
303
|
+
export async function loadbalancerCommand(action: string, options?: any): Promise<void> {
|
|
304
|
+
switch (action) {
|
|
305
|
+
case 'start':
|
|
306
|
+
await startLoadBalancer(options);
|
|
307
|
+
break;
|
|
308
|
+
case 'status':
|
|
309
|
+
await showLoadBalancerStatus(options);
|
|
310
|
+
break;
|
|
311
|
+
case 'config':
|
|
312
|
+
await configureLoadBalancer(options);
|
|
313
|
+
break;
|
|
314
|
+
default:
|
|
315
|
+
console.error('❌ Invalid action. Use: start, status, config');
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function startLoadBalancer(options?: any): Promise<void> {
|
|
321
|
+
const config: LoadBalancerConfig = {
|
|
322
|
+
port: options?.port || 8080,
|
|
323
|
+
algorithm: options?.algorithm || 'round-robin',
|
|
324
|
+
healthCheck: {
|
|
325
|
+
enabled: options?.healthCheck !== false,
|
|
326
|
+
path: options?.healthPath || '/healthz',
|
|
327
|
+
interval: options?.healthInterval || 10000,
|
|
328
|
+
timeout: options?.healthTimeout || 5000,
|
|
329
|
+
retries: options?.healthRetries || 3,
|
|
330
|
+
},
|
|
331
|
+
backends: parseBackends(options?.backends || []),
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
if (config.backends.length === 0) {
|
|
335
|
+
console.error('❌ At least one backend is required');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const loadBalancer = new LoadBalancer(config);
|
|
340
|
+
|
|
341
|
+
console.log(`🚀 Starting BS9 Load Balancer`);
|
|
342
|
+
console.log(`📡 Port: ${config.port}`);
|
|
343
|
+
console.log(`⚖️ Algorithm: ${config.algorithm}`);
|
|
344
|
+
console.log(`🏥 Health Check: ${config.healthCheck.enabled ? 'Enabled' : 'Disabled'}`);
|
|
345
|
+
console.log(`🔗 Backends: ${config.backends.length}`);
|
|
346
|
+
|
|
347
|
+
for (const backend of config.backends) {
|
|
348
|
+
console.log(` - ${backend.host}:${backend.port} (weight: ${backend.weight || 1})`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Start load balancer server
|
|
352
|
+
const server = serve({
|
|
353
|
+
port: config.port,
|
|
354
|
+
fetch: async (request) => {
|
|
355
|
+
// Handle load balancer API endpoints
|
|
356
|
+
const url = new URL(request.url);
|
|
357
|
+
|
|
358
|
+
if (url.pathname === '/lb-stats') {
|
|
359
|
+
return new Response(JSON.stringify(loadBalancer.getStats()), {
|
|
360
|
+
headers: { 'Content-Type': 'application/json' }
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (url.pathname === '/lb-config') {
|
|
365
|
+
return new Response(JSON.stringify(loadBalancer.getConfig()), {
|
|
366
|
+
headers: { 'Content-Type': 'application/json' }
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Forward all other requests
|
|
371
|
+
return loadBalancer.handleRequest(request);
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
console.log(`✅ Load balancer running on http://localhost:${config.port}`);
|
|
376
|
+
console.log(`📊 Stats: http://localhost:${config.port}/lb-stats`);
|
|
377
|
+
console.log(`⚙️ Config: http://localhost:${config.port}/lb-config`);
|
|
378
|
+
|
|
379
|
+
// Graceful shutdown
|
|
380
|
+
process.on('SIGINT', () => {
|
|
381
|
+
console.log('\n🛑 Shutting down load balancer...');
|
|
382
|
+
server.stop();
|
|
383
|
+
process.exit(0);
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
async function showLoadBalancerStatus(options?: any): Promise<void> {
|
|
388
|
+
const port = options?.port || 8080;
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
const response = await fetch(`http://localhost:${port}/lb-stats`);
|
|
392
|
+
if (!response.ok) {
|
|
393
|
+
throw new Error(`HTTP ${response.status}`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const stats: LoadBalancerStats = await response.json();
|
|
397
|
+
|
|
398
|
+
console.log('📊 Load Balancer Status');
|
|
399
|
+
console.log('='.repeat(60));
|
|
400
|
+
console.log(`Total Requests: ${stats.totalRequests}`);
|
|
401
|
+
console.log(`Active Connections: ${stats.activeConnections}`);
|
|
402
|
+
console.log(`Backends: ${stats.backendStats.length}`);
|
|
403
|
+
|
|
404
|
+
console.log('\n🔗 Backend Status:');
|
|
405
|
+
for (const backend of stats.backendStats) {
|
|
406
|
+
const statusIcon = backend.healthy ? '✅' : '❌';
|
|
407
|
+
console.log(`${statusIcon} ${backend.host}:${backend.port}`);
|
|
408
|
+
console.log(` Connections: ${backend.connections}`);
|
|
409
|
+
console.log(` Response Time: ${backend.responseTime}ms`);
|
|
410
|
+
console.log(` Requests Handled: ${backend.requestsHandled}`);
|
|
411
|
+
console.log('');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error(`❌ Failed to get load balancer status: ${error}`);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function configureLoadBalancer(options?: any): Promise<void> {
|
|
421
|
+
const port = options?.port || 8080;
|
|
422
|
+
|
|
423
|
+
if (!options?.backends && !options?.algorithm) {
|
|
424
|
+
console.error('❌ No configuration changes specified');
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const configResponse = await fetch(`http://localhost:${port}/lb-config`);
|
|
430
|
+
if (!configResponse.ok) {
|
|
431
|
+
throw new Error(`HTTP ${configResponse.status}`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const currentConfig: LoadBalancerConfig = await configResponse.json();
|
|
435
|
+
|
|
436
|
+
const newConfig: Partial<LoadBalancerConfig> = {};
|
|
437
|
+
|
|
438
|
+
if (options?.backends) {
|
|
439
|
+
newConfig.backends = parseBackends(options.backends);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (options?.algorithm) {
|
|
443
|
+
newConfig.algorithm = options.algorithm;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Apply configuration (this would need to be implemented in the load balancer)
|
|
447
|
+
console.log('📝 Load balancer configuration updated');
|
|
448
|
+
console.log('Note: Dynamic configuration updates require load balancer restart');
|
|
449
|
+
|
|
450
|
+
} catch (error) {
|
|
451
|
+
console.error(`❌ Failed to configure load balancer: ${error}`);
|
|
452
|
+
process.exit(1);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function parseBackends(backendsStr: string): BackendServer[] {
|
|
457
|
+
const backends: BackendServer[] = [];
|
|
458
|
+
|
|
459
|
+
for (const backendStr of backendsStr.split(',')) {
|
|
460
|
+
const [hostPort, weightStr] = backendStr.trim().split(':');
|
|
461
|
+
const [host, port] = hostPort.split('@');
|
|
462
|
+
|
|
463
|
+
if (!host || !port) {
|
|
464
|
+
console.error(`❌ Invalid backend format: ${backendStr}`);
|
|
465
|
+
process.exit(1);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
backends.push({
|
|
469
|
+
id: `${host}:${port}`,
|
|
470
|
+
host,
|
|
471
|
+
port: parseInt(port),
|
|
472
|
+
weight: weightStr ? parseInt(weightStr) : 1,
|
|
473
|
+
connections: 0,
|
|
474
|
+
healthy: true,
|
|
475
|
+
lastHealthCheck: Date.now(),
|
|
476
|
+
responseTime: 0,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return backends;
|
|
481
|
+
}
|