| |
| """ |
| Setup WebSocket Server for Real-Time Features |
| |
| This script implements: |
| - WebSocket server for real-time communication |
| - Client-side WebSocket management |
| - Real-time event handling and broadcasting |
| - Connection management and reconnection logic |
| - Live status updates for workflows |
| """ |
|
|
| import asyncio |
| from collections import defaultdict |
| from dataclasses import dataclass, field |
| from datetime import datetime, timedelta |
| from enum import Enum |
| import json |
| import logging |
| import os |
| import sys |
| import threading |
| import time |
| from typing import Any, Callable, Dict, List, Optional, Set |
| import uuid |
| import websockets |
| from websockets.server import WebSocketServerProtocol |
|
|
| |
| sys.path.append(os.path.dirname(os.path.abspath(__file__))) |
|
|
| |
| from working_enhanced_workflow_engine import working_enhanced_workflow_engine |
|
|
| logger = logging.getLogger(__name__) |
|
|
|
|
| class WebSocketEventType(Enum): |
| """WebSocket event types""" |
| WORKFLOW_UPDATE = "workflow_update" |
| EXECUTION_STATUS = "execution_status" |
| SERVICE_STATUS = "service_status" |
| NOTIFICATION = "notification" |
| COLLABORATION = "collaboration" |
| SYSTEM_UPDATE = "system_update" |
| USER_ACTIVITY = "user_activity" |
| ERROR = "error" |
| HEARTBEAT = "heartbeat" |
|
|
|
|
| class ConnectionState(Enum): |
| """WebSocket connection state""" |
| CONNECTING = "connecting" |
| CONNECTED = "connected" |
| DISCONNECTING = "disconnecting" |
| DISCONNECTED = "disconnected" |
| RECONNECTING = "reconnecting" |
| ERROR = "error" |
|
|
|
|
| @dataclass |
| class WebSocketConnection: |
| """WebSocket connection information""" |
| connection_id: str |
| websocket: WebSocketServerProtocol |
| user_id: str |
| session_id: Optional[str] = None |
| connected_at: datetime = field(default_factory=datetime.now) |
| last_activity: datetime = field(default_factory=datetime.now) |
| subscriptions: Set[str] = field(default_factory=set) |
| state: ConnectionState = ConnectionState.CONNECTED |
| metadata: Dict[str, Any] = field(default_factory=dict) |
|
|
|
|
| @dataclass |
| class WebSocketEvent: |
| """WebSocket event message""" |
| event_id: str |
| event_type: WebSocketEventType |
| payload: Dict[str, Any] |
| timestamp: datetime = field(default_factory=datetime.now) |
| user_id: Optional[str] = None |
| session_id: Optional[str] = None |
| target_connections: List[str] = field(default_factory=list) |
| broadcast: bool = False |
|
|
|
|
| class RealTimeWebSocketServer: |
| """Real-time WebSocket server for workflow updates""" |
| |
| def __init__(self, host: str = "localhost", port: int = 8765): |
| self.host = host |
| self.port = port |
| self.connections: Dict[str, WebSocketConnection] = {} |
| self.user_connections: Dict[str, Set[str]] = defaultdict(set) |
| self.session_connections: Dict[str, Set[str]] = defaultdict(set) |
| self.subscriptions: Dict[str, Set[str]] = defaultdict(set) |
| |
| self.server = None |
| self.running = False |
| self.event_handlers = {} |
| self.connection_handlers = {} |
| |
| |
| self.metrics = { |
| "total_connections": 0, |
| "active_connections": 0, |
| "events_sent": 0, |
| "events_received": 0, |
| "errors": 0, |
| "start_time": None |
| } |
| |
| |
| self._initialize_event_handlers() |
| self._initialize_connection_handlers() |
| |
| def _initialize_event_handlers(self): |
| """Initialize WebSocket event handlers""" |
| self.event_handlers = { |
| WebSocketEventType.WORKFLOW_UPDATE: self._handle_workflow_update, |
| WebSocketEventType.EXECUTION_STATUS: self._handle_execution_status, |
| WebSocketEventType.SERVICE_STATUS: self._handle_service_status, |
| WebSocketEventType.NOTIFICATION: self._handle_notification, |
| WebSocketEventType.COLLABORATION: self._handle_collaboration, |
| WebSocketEventType.SYSTEM_UPDATE: self._handle_system_update, |
| WebSocketEventType.USER_ACTIVITY: self._handle_user_activity, |
| WebSocketEventType.ERROR: self._handle_error, |
| WebSocketEventType.HEARTBEAT: self._handle_heartbeat |
| } |
| |
| logger.info(f"Initialized {len(self.event_handlers)} event handlers") |
| |
| def _initialize_connection_handlers(self): |
| """Initialize connection lifecycle handlers""" |
| self.connection_handlers = { |
| "on_connect": self._on_connect, |
| "on_disconnect": self._on_disconnect, |
| "on_message": self._on_message, |
| "on_error": self._on_error |
| } |
| |
| logger.info("Initialized connection handlers") |
| |
| async def start_server(self): |
| """Start the WebSocket server""" |
| try: |
| logger.info(f"Starting WebSocket server on {self.host}:{self.port}") |
| |
| self.running = True |
| self.metrics["start_time"] = datetime.now() |
| |
| |
| self.server = await websockets.serve( |
| self._handle_connection, |
| self.host, |
| self.port, |
| ping_interval=20, |
| ping_timeout=10, |
| close_timeout=10, |
| max_size=10_000_000, |
| max_queue=1000 |
| ) |
| |
| logger.info(f"WebSocket server started successfully on ws://{self.host}:{self.port}") |
| |
| |
| asyncio.create_task(self._heartbeat_monitor()) |
| asyncio.create_task(self._cleanup_connections()) |
| asyncio.create_task(self._performance_monitor()) |
| |
| except Exception as e: |
| logger.error(f"Failed to start WebSocket server: {str(e)}") |
| self.running = False |
| raise |
| |
| async def stop_server(self): |
| """Stop the WebSocket server""" |
| try: |
| logger.info("Stopping WebSocket server...") |
| |
| self.running = False |
| |
| |
| for connection_id, connection in list(self.connections.items()): |
| try: |
| await connection.websocket.close() |
| except: |
| pass |
| |
| |
| if self.server: |
| self.server.close() |
| await self.server.wait_closed() |
| |
| logger.info("WebSocket server stopped successfully") |
| |
| except Exception as e: |
| logger.error(f"Error stopping WebSocket server: {str(e)}") |
| |
| async def _handle_connection(self, websocket: WebSocketServerProtocol, path: str): |
| """Handle new WebSocket connection""" |
| connection_id = str(uuid.uuid4()) |
| connection = WebSocketConnection( |
| connection_id=connection_id, |
| websocket=websocket, |
| user_id="", |
| session_id=None, |
| state=ConnectionState.CONNECTING |
| ) |
| |
| try: |
| |
| self.connections[connection_id] = connection |
| self.metrics["total_connections"] += 1 |
| self.metrics["active_connections"] = len(self.connections) |
| |
| logger.info(f"New WebSocket connection: {connection_id} from {path}") |
| |
| |
| auth_message = await websocket.recv() |
| auth_data = json.loads(auth_message) |
| |
| |
| if auth_data.get("type") == "auth" and auth_data.get("user_id"): |
| connection.user_id = auth_data["user_id"] |
| connection.session_id = auth_data.get("session_id") |
| connection.metadata.update(auth_data.get("metadata", {})) |
| connection.state = ConnectionState.CONNECTED |
| |
| |
| self.user_connections[connection.user_id].add(connection_id) |
| if connection.session_id: |
| self.session_connections[connection.session_id].add(connection_id) |
| |
| |
| await self._send_to_connection(connection_id, { |
| "type": "auth_success", |
| "connection_id": connection_id, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| |
| await self.connection_handlers["on_connect"](connection) |
| |
| logger.info(f"WebSocket {connection_id} authenticated for user {connection.user_id}") |
| else: |
| |
| await websocket.close(1008, "Authentication failed") |
| logger.warning(f"WebSocket {connection_id} authentication failed") |
| return |
| |
| |
| connection.state = ConnectionState.CONNECTED |
| while connection.state == ConnectionState.CONNECTED and not websocket.closed: |
| try: |
| |
| message = await asyncio.wait_for(websocket.recv(), timeout=30.0) |
| |
| |
| connection.last_activity = datetime.now() |
| |
| |
| try: |
| data = json.loads(message) |
| await self.connection_handlers["on_message"](connection, data) |
| self.metrics["events_received"] += 1 |
| |
| except json.JSONDecodeError: |
| logger.error(f"Invalid JSON from connection {connection_id}") |
| await self._send_error(connection_id, "Invalid message format") |
| |
| except asyncio.TimeoutError: |
| |
| try: |
| await websocket.ping() |
| connection.last_activity = datetime.now() |
| except: |
| break |
| |
| except websockets.exceptions.ConnectionClosed: |
| break |
| |
| except Exception as e: |
| logger.error(f"Error handling message from {connection_id}: {str(e)}") |
| await self._send_error(connection_id, f"Message handling error: {str(e)}") |
| |
| except Exception as e: |
| logger.error(f"Error in WebSocket connection {connection_id}: {str(e)}") |
| await self.connection_handlers["on_error"](connection, e) |
| |
| finally: |
| |
| await self._cleanup_connection(connection_id) |
| logger.info(f"WebSocket connection {connection_id} closed") |
| |
| async def _cleanup_connection(self, connection_id: str): |
| """Clean up closed connection""" |
| try: |
| if connection_id not in self.connections: |
| return |
| |
| connection = self.connections[connection_id] |
| connection.state = ConnectionState.DISCONNECTED |
| |
| |
| if connection.user_id: |
| self.user_connections[connection.user_id].discard(connection_id) |
| if not self.user_connections[connection.user_id]: |
| del self.user_connections[connection.user_id] |
| |
| if connection.session_id: |
| self.session_connections[connection.session_id].discard(connection_id) |
| if not self.session_connections[connection.session_id]: |
| del self.session_connections[connection.session_id] |
| |
| |
| for subscription in connection.subscriptions: |
| self.subscriptions[subscription].discard(connection_id) |
| if not self.subscriptions[subscription]: |
| del self.subscriptions[subscription] |
| |
| |
| del self.connections[connection_id] |
| self.metrics["active_connections"] = len(self.connections) |
| |
| |
| await self.connection_handlers["on_disconnect"](connection) |
| |
| except Exception as e: |
| logger.error(f"Error cleaning up connection {connection_id}: {str(e)}") |
| |
| async def _send_to_connection(self, connection_id: str, data: Dict[str, Any]): |
| """Send message to specific connection""" |
| try: |
| if connection_id not in self.connections: |
| logger.warning(f"Attempted to send to unknown connection: {connection_id}") |
| return False |
| |
| connection = self.connections[connection_id] |
| if connection.websocket.closed: |
| return False |
| |
| message = json.dumps(data) |
| await connection.websocket.send(message) |
| self.metrics["events_sent"] += 1 |
| return True |
| |
| except Exception as e: |
| logger.error(f"Error sending to connection {connection_id}: {str(e)}") |
| return False |
| |
| async def _send_error(self, connection_id: str, error_message: str): |
| """Send error message to connection""" |
| error_data = { |
| "type": "error", |
| "error": error_message, |
| "timestamp": datetime.now().isoformat() |
| } |
| await self._send_to_connection(connection_id, error_data) |
| |
| |
| async def _on_connect(self, connection: WebSocketConnection): |
| """Handle new connection""" |
| try: |
| logger.info(f"Connection established: {connection.connection_id} for user {connection.user_id}") |
| |
| |
| await self._send_to_connection(connection.connection_id, { |
| "type": "connection_established", |
| "connection_id": connection.connection_id, |
| "timestamp": datetime.now().isoformat(), |
| "features": [ |
| "workflow_updates", |
| "execution_status", |
| "collaboration", |
| "notifications" |
| ] |
| }) |
| |
| |
| await self._subscribe_to_channel(connection.connection_id, f"user:{connection.user_id}") |
| if connection.session_id: |
| await self._subscribe_to_channel(connection.connection_id, f"session:{connection.session_id}") |
| |
| except Exception as e: |
| logger.error(f"Error in connect handler: {str(e)}") |
| |
| async def _on_disconnect(self, connection: WebSocketConnection): |
| """Handle connection disconnection""" |
| try: |
| logger.info(f"Connection disconnected: {connection.connection_id}") |
| |
| |
| await self._broadcast_event(WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.USER_ACTIVITY, |
| payload={ |
| "user_id": connection.user_id, |
| "activity": "disconnected", |
| "timestamp": datetime.now().isoformat() |
| }, |
| user_id=connection.user_id, |
| target_connections=list(self.user_connections.get(connection.user_id, [])), |
| broadcast=False |
| )) |
| |
| except Exception as e: |
| logger.error(f"Error in disconnect handler: {str(e)}") |
| |
| async def _on_message(self, connection: WebSocketConnection, data: Dict[str, Any]): |
| """Handle incoming message""" |
| try: |
| message_type = data.get("type") |
| |
| if message_type == "subscribe": |
| |
| channel = data.get("channel") |
| if channel: |
| await self._subscribe_to_channel(connection.connection_id, channel) |
| |
| elif message_type == "unsubscribe": |
| |
| channel = data.get("channel") |
| if channel: |
| await self._unsubscribe_from_channel(connection.connection_id, channel) |
| |
| elif message_type == "workflow_command": |
| |
| await self._handle_workflow_command(connection, data) |
| |
| elif message_type == "collaboration": |
| |
| await self._handle_collaboration_message(connection, data) |
| |
| elif message_type == "ping": |
| |
| await self._send_to_connection(connection.connection_id, { |
| "type": "pong", |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| else: |
| logger.warning(f"Unknown message type: {message_type}") |
| |
| except Exception as e: |
| logger.error(f"Error handling message: {str(e)}") |
| |
| async def _on_error(self, connection: WebSocketConnection, error: Exception): |
| """Handle connection error""" |
| try: |
| logger.error(f"Connection error for {connection.connection_id}: {str(error)}") |
| self.metrics["errors"] += 1 |
| |
| except Exception as e: |
| logger.error(f"Error in error handler: {str(e)}") |
| |
| |
| async def _handle_workflow_update(self, event: WebSocketEvent): |
| """Handle workflow update event""" |
| try: |
| |
| if event.user_id: |
| target_connections = list(self.user_connections.get(event.user_id, [])) |
| else: |
| target_connections = list(self.connections.keys()) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "workflow_update", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling workflow update: {str(e)}") |
| |
| async def _handle_execution_status(self, event: WebSocketEvent): |
| """Handle execution status event""" |
| try: |
| |
| user_id = event.payload.get("user_id") |
| if user_id: |
| target_connections = list(self.user_connections.get(user_id, [])) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "execution_status", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling execution status: {str(e)}") |
| |
| async def _handle_service_status(self, event: WebSocketEvent): |
| """Handle service status event""" |
| try: |
| |
| for connection_id in self.connections.keys(): |
| await self._send_to_connection(connection_id, { |
| "type": "service_status", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling service status: {str(e)}") |
| |
| async def _handle_notification(self, event: WebSocketEvent): |
| """Handle notification event""" |
| try: |
| |
| if event.user_id: |
| target_connections = list(self.user_connections.get(event.user_id, [])) |
| elif event.session_id: |
| target_connections = list(self.session_connections.get(event.session_id, [])) |
| else: |
| target_connections = list(self.connections.keys()) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "notification", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling notification: {str(e)}") |
| |
| async def _handle_collaboration(self, event: WebSocketEvent): |
| """Handle collaboration event""" |
| try: |
| |
| if event.session_id: |
| target_connections = list(self.session_connections.get(event.session_id, [])) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "collaboration", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling collaboration: {str(e)}") |
| |
| async def _handle_system_update(self, event: WebSocketEvent): |
| """Handle system update event""" |
| try: |
| |
| for connection_id in self.connections.keys(): |
| await self._send_to_connection(connection_id, { |
| "type": "system_update", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling system update: {str(e)}") |
| |
| async def _handle_user_activity(self, event: WebSocketEvent): |
| """Handle user activity event""" |
| try: |
| |
| if event.session_id: |
| target_connections = list(self.session_connections.get(event.session_id, [])) |
| |
| for connection_id in target_connections: |
| |
| connection = self.connections.get(connection_id) |
| if connection and connection.user_id != event.user_id: |
| await self._send_to_connection(connection_id, { |
| "type": "user_activity", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling user activity: {str(e)}") |
| |
| async def _handle_error(self, event: WebSocketEvent): |
| """Handle error event""" |
| try: |
| |
| if event.user_id: |
| target_connections = list(self.user_connections.get(event.user_id, [])) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "error", |
| "event_id": event.event_id, |
| "payload": event.payload, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling error event: {str(e)}") |
| |
| async def _handle_heartbeat(self, event: WebSocketEvent): |
| """Handle heartbeat event""" |
| try: |
| |
| if event.user_id: |
| target_connections = list(self.user_connections.get(event.user_id, [])) |
| |
| for connection_id in target_connections: |
| await self._send_to_connection(connection_id, { |
| "type": "heartbeat_response", |
| "event_id": event.event_id, |
| "timestamp": event.timestamp.isoformat() |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error handling heartbeat: {str(e)}") |
| |
| |
| async def _subscribe_to_channel(self, connection_id: str, channel: str): |
| """Subscribe connection to channel""" |
| try: |
| if connection_id in self.connections: |
| connection = self.connections[connection_id] |
| connection.subscriptions.add(channel) |
| self.subscriptions[channel].add(connection_id) |
| |
| await self._send_to_connection(connection_id, { |
| "type": "subscription_confirmed", |
| "channel": channel, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| logger.info(f"Connection {connection_id} subscribed to {channel}") |
| |
| except Exception as e: |
| logger.error(f"Error subscribing to channel: {str(e)}") |
| |
| async def _unsubscribe_from_channel(self, connection_id: str, channel: str): |
| """Unsubscribe connection from channel""" |
| try: |
| if connection_id in self.connections: |
| connection = self.connections[connection_id] |
| connection.subscriptions.discard(channel) |
| self.subscriptions[channel].discard(connection_id) |
| |
| if not self.subscriptions[channel]: |
| del self.subscriptions[channel] |
| |
| await self._send_to_connection(connection_id, { |
| "type": "unsubscription_confirmed", |
| "channel": channel, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| logger.info(f"Connection {connection_id} unsubscribed from {channel}") |
| |
| except Exception as e: |
| logger.error(f"Error unsubscribing from channel: {str(e)}") |
| |
| async def _handle_workflow_command(self, connection: WebSocketConnection, data: Dict[str, Any]): |
| """Handle workflow command from client""" |
| try: |
| command = data.get("command") |
| workflow_data = data.get("data", {}) |
| |
| if command == "create_workflow": |
| |
| result = working_enhanced_workflow_engine.create_workflow_from_template( |
| template_id=workflow_data.get("template_id"), |
| parameters=workflow_data.get("parameters", {}), |
| user_id=connection.user_id |
| ) |
| |
| await self._send_to_connection(connection.connection_id, { |
| "type": "workflow_command_response", |
| "command": command, |
| "success": result.get("success", False), |
| "data": result, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| elif command == "execute_workflow": |
| |
| result = working_enhanced_workflow_engine.execute_workflow( |
| workflow_id=workflow_data.get("workflow_id"), |
| input_data=workflow_data.get("input_data", {}) |
| ) |
| |
| if result.get("success"): |
| |
| execution_id = result.get("execution_id") |
| |
| |
| await self._broadcast_event(WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.EXECUTION_STATUS, |
| payload={ |
| "execution_id": execution_id, |
| "status": "started", |
| "workflow_id": workflow_data.get("workflow_id"), |
| "user_id": connection.user_id |
| }, |
| user_id=connection.user_id |
| )) |
| |
| await self._send_to_connection(connection.connection_id, { |
| "type": "workflow_command_response", |
| "command": command, |
| "success": result.get("success", False), |
| "data": result, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| elif command == "get_execution_status": |
| |
| execution_id = workflow_data.get("execution_id") |
| result = working_enhanced_workflow_engine.get_execution_status(execution_id) |
| |
| await self._send_to_connection(connection.connection_id, { |
| "type": "workflow_command_response", |
| "command": command, |
| "success": result.get("success", False), |
| "data": result, |
| "timestamp": datetime.now().isoformat() |
| }) |
| |
| else: |
| await self._send_error(connection.connection_id, f"Unknown command: {command}") |
| |
| except Exception as e: |
| logger.error(f"Error handling workflow command: {str(e)}") |
| await self._send_error(connection.connection_id, f"Command error: {str(e)}") |
| |
| async def _handle_collaboration_message(self, connection: WebSocketConnection, data: Dict[str, Any]): |
| """Handle collaboration message""" |
| try: |
| collaboration_type = data.get("collaboration_type") |
| message_data = data.get("data", {}) |
| |
| |
| event = WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.COLLABORATION, |
| payload={ |
| "collaboration_type": collaboration_type, |
| "data": message_data, |
| "user_id": connection.user_id, |
| "session_id": connection.session_id |
| }, |
| user_id=connection.user_id, |
| session_id=connection.session_id |
| ) |
| |
| await self.event_handlers[WebSocketEventType.COLLABORATION](event) |
| |
| except Exception as e: |
| logger.error(f"Error handling collaboration message: {str(e)}") |
| |
| async def _broadcast_event(self, event: WebSocketEvent): |
| """Broadcast event to appropriate connections""" |
| try: |
| handler = self.event_handlers.get(event.event_type) |
| if handler: |
| await handler(event) |
| else: |
| logger.warning(f"No handler for event type: {event.event_type}") |
| |
| except Exception as e: |
| logger.error(f"Error broadcasting event: {str(e)}") |
| |
| |
| async def _heartbeat_monitor(self): |
| """Monitor connection heartbeats""" |
| while self.running: |
| try: |
| await asyncio.sleep(60) |
| |
| current_time = datetime.now() |
| dead_connections = [] |
| |
| for connection_id, connection in self.connections.items(): |
| |
| if (current_time - connection.last_activity).total_seconds() > 300: |
| dead_connections.append(connection_id) |
| |
| |
| for connection_id in dead_connections: |
| logger.warning(f"Cleaning up dead connection: {connection_id}") |
| await self._cleanup_connection(connection_id) |
| |
| except Exception as e: |
| logger.error(f"Error in heartbeat monitor: {str(e)}") |
| |
| async def _cleanup_connections(self): |
| """Periodic cleanup of connections""" |
| while self.running: |
| try: |
| await asyncio.sleep(300) |
| |
| |
| empty_subscriptions = [ |
| sub for sub, conns in self.subscriptions.items() |
| if not conns |
| ] |
| |
| for sub in empty_subscriptions: |
| del self.subscriptions[sub] |
| |
| logger.debug(f"Cleaned up {len(empty_subscriptions)} empty subscriptions") |
| |
| except Exception as e: |
| logger.error(f"Error in cleanup task: {str(e)}") |
| |
| async def _performance_monitor(self): |
| """Monitor server performance""" |
| while self.running: |
| try: |
| await asyncio.sleep(300) |
| |
| if self.metrics["start_time"]: |
| uptime = datetime.now() - self.metrics["start_time"] |
| |
| performance_report = { |
| "uptime_seconds": uptime.total_seconds(), |
| "total_connections": self.metrics["total_connections"], |
| "active_connections": len(self.connections), |
| "events_sent": self.metrics["events_sent"], |
| "events_received": self.metrics["events_received"], |
| "errors": self.metrics["errors"], |
| "avg_events_per_minute": ( |
| self.metrics["events_sent"] / max(uptime.total_seconds() / 60, 1) |
| ) |
| } |
| |
| logger.info(f"WebSocket Server Performance: {performance_report}") |
| |
| |
| await self._broadcast_event(WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.SYSTEM_UPDATE, |
| payload={ |
| "type": "performance_report", |
| "data": performance_report |
| }, |
| broadcast=True |
| )) |
| |
| except Exception as e: |
| logger.error(f"Error in performance monitor: {str(e)}") |
| |
| |
| async def send_notification(self, user_id: str, message: str, notification_type: str = "info"): |
| """Send notification to specific user""" |
| try: |
| event = WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.NOTIFICATION, |
| payload={ |
| "message": message, |
| "type": notification_type, |
| "user_id": user_id |
| }, |
| user_id=user_id |
| ) |
| |
| await self._broadcast_event(event) |
| |
| except Exception as e: |
| logger.error(f"Error sending notification: {str(e)}") |
| |
| async def broadcast_workflow_update(self, user_id: str, workflow_id: str, update_data: Dict[str, Any]): |
| """Broadcast workflow update to user""" |
| try: |
| event = WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.WORKFLOW_UPDATE, |
| payload={ |
| "workflow_id": workflow_id, |
| "user_id": user_id, |
| "update": update_data |
| }, |
| user_id=user_id |
| ) |
| |
| await self._broadcast_event(event) |
| |
| except Exception as e: |
| logger.error(f"Error broadcasting workflow update: {str(e)}") |
| |
| async def broadcast_execution_status(self, user_id: str, execution_id: str, status: str, details: Dict[str, Any] = None): |
| """Broadcast execution status update""" |
| try: |
| event = WebSocketEvent( |
| event_id=str(uuid.uuid4()), |
| event_type=WebSocketEventType.EXECUTION_STATUS, |
| payload={ |
| "execution_id": execution_id, |
| "status": status, |
| "user_id": user_id, |
| "details": details or {} |
| }, |
| user_id=user_id |
| ) |
| |
| await self._broadcast_event(event) |
| |
| except Exception as e: |
| logger.error(f"Error broadcasting execution status: {str(e)}") |
| |
| def get_metrics(self) -> Dict[str, Any]: |
| """Get server performance metrics""" |
| uptime = None |
| if self.metrics["start_time"]: |
| uptime = datetime.now() - self.metrics["start_time"] |
| |
| return { |
| "server_running": self.running, |
| "uptime_seconds": uptime.total_seconds() if uptime else 0, |
| "total_connections": self.metrics["total_connections"], |
| "active_connections": len(self.connections), |
| "events_sent": self.metrics["events_sent"], |
| "events_received": self.metrics["events_received"], |
| "errors": self.metrics["errors"], |
| "subscriptions": len(self.subscriptions), |
| "avg_events_per_minute": ( |
| self.metrics["events_sent"] / max(uptime.total_seconds() / 60, 1) if uptime else 0 |
| ), |
| "memory_usage": len(self.connections) * 1024, |
| "host": self.host, |
| "port": self.port |
| } |
|
|
|
|
| |
| websocket_server = RealTimeWebSocketServer() |
|
|
| logger.info("Real-Time WebSocket Server initialized") |
|
|
|
|
| def start_websocket_server(): |
| """Start WebSocket server in background""" |
| try: |
| |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| |
| |
| loop.run_until_complete(websocket_server.start_server()) |
| loop.run_forever() |
| |
| except Exception as e: |
| logger.error(f"Error starting WebSocket server: {str(e)}") |
|
|
|
|
| |
| websocket_thread = threading.Thread(target=start_websocket_server, daemon=True) |
| websocket_thread.start() |
|
|
| logger.info("WebSocket server background thread started") |