From 918656a5b4915df1f6cb5c658694f6ffa93cf4ac Mon Sep 17 00:00:00 2001 From: DmitriyA Date: Mon, 21 Apr 2025 02:56:19 -0400 Subject: [PATCH] fixed a bug with multiple web socket connection --- .gitignore | 7 ++ package.json | 3 +- src/auth/auth.controller.ts | 2 +- src/metrics.gateway.ts | 109 ++++++++++++++--------------- src/prometheus-metric.interface.ts | 1 + src/prometheus.service.ts | 6 +- 6 files changed, 67 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index 4b56acf..19333b9 100644 --- a/.gitignore +++ b/.gitignore @@ -52,5 +52,12 @@ pids *.seed *.pid.lock +# Игнорировать .env файлы +.env +.env.local +.env.development +.env.production +.env.test + # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json diff --git a/package.json b/package.json index 388adcb..7e75df4 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "@types/passport-jwt": "^4.0.1", "@types/cookie-parser": "^1.4.8", "@nestjs/jwt": "^11.0.0", - "@nestjs/passport": "^11.0.5" + "@nestjs/passport": "^11.0.5", + "@nestjs/swagger": "11.1.4" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index da29a51..619bc4b 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -50,7 +50,7 @@ export class AuthController { const { access_token } = await this.authService.login(user); - res.cookie('access_token', access_token, { + res.cookie('accecdss_token', access_token, { httpOnly: true, secure: process.env.COOKIE_SECURE === 'true', sameSite: (process.env.COOKIE_SAME_SITE as 'strict' | 'lax' | 'none') || 'strict', diff --git a/src/metrics.gateway.ts b/src/metrics.gateway.ts index 2b68304..7584025 100644 --- a/src/metrics.gateway.ts +++ b/src/metrics.gateway.ts @@ -1,32 +1,33 @@ -import { WebSocketGateway, WebSocketServer, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage, } from '@nestjs/websockets'; +import { WebSocketGateway, WebSocketServer, OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect, SubscribeMessage } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; import { PrometheusService } from './prometheus.service'; import { Logger } from '@nestjs/common'; @WebSocketGateway({ cors: { - origin: process.env.FRONTEND_URL || 'http://192.168.2.39:5173', // Тот же origin что и в основном CORS + origin: process.env.FRONTEND_URL, methods: ['GET', 'POST'], credentials: true }, namespace: '/api/metrics-ws' }) - export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect { @WebSocketServer() server: Server; private readonly logger = new Logger(MetricsGateway.name); private activeSockets: Map = new Map(); + private metricSubscriptions = new Map void; + clients: Set; + }>(); constructor(private readonly prometheusService: PrometheusService) { } afterInit(server: Server) { this.logger.log('WebSocket Gateway initialized'); - this.logger.log('WebSocket server initialized successfully'); } handleConnection(client: Socket) { this.logger.log(`Client connected: ${client.id}`); - this.logger.log(`New client connected: ${client.id} from ${client.handshake.address}`); this.activeSockets.set(client.id, client); } @@ -37,22 +38,23 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat @SubscribeMessage('get-metrics') async handleGetMetrics(client: Socket, payload: any) { - const { metric, start, end, step, _t } = payload; - this.logger.log(`Received metrics request: ${metric}, start: ${start}, end: ${end}, step: ${step}`); + const { metric, start, end, step, isRangeQuery } = payload; try { - // Для запросов с диапазоном - просто возвращаем данные без подписки - if (start && end) { + if (isRangeQuery) { + // Обработка разового запроса const data = await this.prometheusService.fetchMetricsRange(metric, start, end, step); - client.emit('metrics-data', { metric, data }); + client.emit(`metrics-range-${metric}`, data); return; } - // Для запросов без диапазона (realtime) - запускаем подписку + // Исправлено: передаем функцию, а не клиента const stopUpdates = await this.sendPeriodicUpdates( metric, - step || 5000, // Используем переданный шаг или дефолтный - client + step || 5000, + (data) => { + client.emit('metrics-data', { metric, data }); + } ); client.on('disconnect', () => stopUpdates()); @@ -67,59 +69,52 @@ export class MetricsGateway implements OnGatewayInit, OnGatewayConnection, OnGat } } - @SubscribeMessage('get-metric-types') - async handleGetMetricTypes(client: Socket, payload: { metric: string }) { - try { - const type = await this.prometheusService.fetchMetricType(payload.metric); - const description = await this.prometheusService.fetchMetricDescription(payload.metric); - client.emit('metric-types', { - metric: payload.metric, - type, - description - }); - } catch (error) { - this.logger.log(`Error fetching metric types: ${error.message}`); - client.emit('metrics-error', { - metric: payload.metric, - error: error.message - }); - } - } - - @SubscribeMessage('get-all-metrics') - async handleGetAllMetrics(client: Socket) { - try { - const metrics = await this.prometheusService.fetchAllMetrics(); - client.emit('all-metrics', metrics); - } catch (error) { - this.logger.log(`Error fetching all metrics: ${error.message}`); - client.emit('metrics-error', { - error: error.message - }); - } - } - - - @SubscribeMessage('subscribe-metric') async handleSubscribeMetric(client: Socket, payload: { metric: string, interval?: number }) { - const stopUpdates = await this.sendPeriodicUpdates( - payload.metric, - payload.interval || 5000, // Добавляем значение по умолчанию - client // Передаем клиента - ); + const { metric } = payload; - // Сохраняем функцию остановки для этого клиента - client.on('disconnect', () => stopUpdates()); - client.on('unsubscribe-metric', () => stopUpdates()); + if (!this.metricSubscriptions.has(metric)) { + const stopUpdates = await this.sendPeriodicUpdates( + metric, + payload.interval || 5000, + (data) => { + this.server.emit('metrics-data', { metric, data }); + } + ); + + this.metricSubscriptions.set(metric, { + stopUpdates, + clients: new Set([client.id]) + }); + } else { + // Исправлено: добавлена проверка на существование подписки + const subscription = this.metricSubscriptions.get(metric); + if (subscription) { + subscription.clients.add(client.id); + } + } + + const unsubscribe = () => { + const subscription = this.metricSubscriptions.get(metric); + if (subscription) { + subscription.clients.delete(client.id); + if (subscription.clients.size === 0) { + subscription.stopUpdates(); + this.metricSubscriptions.delete(metric); + } + } + }; + + client.on('disconnect', unsubscribe); + client.on('unsubscribe-metric', unsubscribe); } // Метод для периодической отправки обновлений - async sendPeriodicUpdates(metric: string, interval: number, client: Socket) { + async sendPeriodicUpdates(metric: string, interval: number, callback: (data: any) => void) { const timer = setInterval(async () => { try { const data = await this.prometheusService.fetchMetrics(metric); - client.emit('metrics-data', { metric, data }); + callback(data); // Используем переданный callback } catch (error) { this.logger.error(`Error in periodic update for ${metric}: ${error.message}`); } diff --git a/src/prometheus-metric.interface.ts b/src/prometheus-metric.interface.ts index 2f30262..8131bc6 100644 --- a/src/prometheus-metric.interface.ts +++ b/src/prometheus-metric.interface.ts @@ -5,4 +5,5 @@ export interface PrometheusMetric { value: number; type: string; // Тип метрики ("gauge", "counter", и т. д.) description?: string; // Описание метрики + status?: string; // Добавляем поле для статуса } \ No newline at end of file diff --git a/src/prometheus.service.ts b/src/prometheus.service.ts index 333292c..e1275d9 100644 --- a/src/prometheus.service.ts +++ b/src/prometheus.service.ts @@ -66,7 +66,8 @@ export class PrometheusService { timestamp: entry.value[0] * 1000, value: parseFloat(entry.value[1]), type: metricType || 'unknown', - description: metricDescription, // Добавляем описание + description: metricDescription, + status: entry.metric.status || 'green', // Используем статус из Prometheus или 'green' по умолчанию })); } @@ -92,7 +93,8 @@ export class PrometheusService { timestamp: value[0] * 1000, value: parseFloat(value[1]), type: metricType || 'unknown', - description: metricDescription, // Добавляем описание + description: metricDescription, + status: entry.metric.status || 'green', // Используем статус из Prometheus или 'green' по умолчанию })) ); }