Skip to Content
WebSocket API

WebSocket API

Eventara provides real-time metric updates through WebSocket connections using the STOMP protocol over SockJS.

Connection Details

Endpoint

ws://localhost:8080/ws

For production, use:

wss://your-domain.com/ws

Protocol

  • Transport: SockJS fallback (WebSocket, XHR streaming, etc.)
  • Sub-protocol: STOMP (Simple Text Oriented Messaging Protocol)
  • Message Format: JSON

Establishing Connection

Using JavaScript (SockJS + STOMP.js)

import SockJS from 'sockjs-client'; import { Client } from '@stomp/stompjs'; const socket = new SockJS('http://localhost:8080/ws'); const stompClient = new Client({ webSocketFactory: () => socket, debug: (str) => console.log('[STOMP]', str), reconnectDelay: 3000, // Reconnect after 3 seconds heartbeatIncoming: 4000, heartbeatOutgoing: 4000, }); stompClient.onConnect = () => { console.log('Connected to Eventara WebSocket'); // Subscribe to metrics topic stompClient.subscribe('/topic/metrics', (message) => { const metrics = JSON.parse(message.body); console.log('Received metrics:', metrics); // Update your UI }); // Request initial metrics (optional) stompClient.publish({ destination: '/app/subscribe', body: JSON.stringify({ action: 'subscribe' }) }); }; stompClient.onStompError = (frame) => { console.error('STOMP error:', frame); }; stompClient.onWebSocketError = (event) => { console.error('WebSocket error:', event); }; stompClient.onWebSocketClose = () => { console.log('WebSocket connection closed'); }; // Activate connection stompClient.activate();

Using Python (websocket-client + stomper)

import json import websocket from sockjs_client import SockJSClient def on_message(ws, message): data = json.loads(message) print(f"Received metrics: {data['summary']['totalEvents']}") def on_error(ws, error): print(f"Error: {error}") def on_close(ws): print("Connection closed") def on_open(ws): print("Connected") # Subscribe to metrics ws.send(json.dumps({ "command": "SUBSCRIBE", "destination": "/topic/metrics", "id": "sub-0" })) ws = websocket.WebSocketApp( "ws://localhost:8080/ws", on_open=on_open, on_message=on_message, on_error=on_error, on_close=on_close ) ws.run_forever()

Topics

/topic/metrics

Broadcasts comprehensive metrics every second.

Subscription:

stompClient.subscribe('/topic/metrics', (message) => { const metrics = JSON.parse(message.body); // Handle metrics update });

Broadcast Frequency: Every 1 second

Message Structure: See Metrics API for complete structure.


Message Format

Metrics Broadcast

{ "summary": { "totalEvents": 15000, "eventsPerSecond": 25.3, "avgLatency": 12.5, "errorRate": 2.1 }, "eventsByType": { ... }, "eventsBySource": { ... }, "eventsBySeverity": { ... }, "latencyMetrics": { ... }, "errorMetrics": { ... }, "throughput": { ... }, "timeWindows": { ... }, "topUsers": [ ... ] }

The metrics object is identical to the response from GET /api/v1/metrics.


Connection Management

Heartbeats

STOMP heartbeats keep the connection alive:

const stompClient = new Client({ heartbeatIncoming: 4000, // Server sends heartbeat every 4s heartbeatOutgoing: 4000, // Client sends heartbeat every 4s });

Automatic Reconnection

const stompClient = new Client({ reconnectDelay: 3000, // Wait 3 seconds before reconnecting onWebSocketClose: () => { console.log('Connection lost, will attempt to reconnect...'); } });

Manual Disconnection

// Gracefully disconnect stompClient.deactivate();

React Hook Example

Complete React hook for WebSocket integration:

import { useEffect, useState, useCallback, useRef } from 'react'; import SockJS from 'sockjs-client'; import { Client, IMessage } from '@stomp/stompjs'; interface MetricsData { summary: { totalEvents: number; eventsPerSecond: number; avgLatency: number; errorRate: number; }; // ... other fields } enum ConnectionState { CONNECTING = 'CONNECTING', CONNECTED = 'CONNECTED', DISCONNECTED = 'DISCONNECTED', RECONNECTING = 'RECONNECTING', ERROR = 'ERROR', } export function useWebSocketMetrics(url: string = 'http://localhost:8080/ws') { const [metrics, setMetrics] = useState<MetricsData | null>(null); const [connectionState, setConnectionState] = useState<ConnectionState>( ConnectionState.CONNECTING ); const [error, setError] = useState<Error | null>(null); const clientRef = useRef<Client | null>(null); const reconnectAttemptsRef = useRef(0); const MAX_RECONNECT_ATTEMPTS = 10; const RECONNECT_INTERVAL = 3000; const connect = useCallback(() => { setConnectionState(ConnectionState.CONNECTING); setError(null); const socket = new SockJS(url); const stompClient = new Client({ webSocketFactory: () => socket, reconnectDelay: RECONNECT_INTERVAL, heartbeatIncoming: 4000, heartbeatOutgoing: 4000, }); stompClient.onConnect = () => { console.log('WebSocket Connected'); setConnectionState(ConnectionState.CONNECTED); reconnectAttemptsRef.current = 0; stompClient.subscribe('/topic/metrics', (message: IMessage) => { try { const metricsData = JSON.parse(message.body); setMetrics(metricsData); } catch (err) { console.error('Error parsing metrics:', err); } }); }; stompClient.onStompError = (frame) => { console.error('STOMP error:', frame); setConnectionState(ConnectionState.ERROR); setError(new Error(frame.headers['message'] || 'STOMP error')); }; stompClient.onWebSocketClose = () => { console.log('WebSocket connection closed'); setConnectionState(ConnectionState.DISCONNECTED); if (reconnectAttemptsRef.current < MAX_RECONNECT_ATTEMPTS) { reconnectAttemptsRef.current++; console.log(`Reconnecting... (${reconnectAttemptsRef.current}/${MAX_RECONNECT_ATTEMPTS})`); setConnectionState(ConnectionState.RECONNECTING); setTimeout(connect, RECONNECT_INTERVAL); } else { setError(new Error('Failed to reconnect after maximum attempts')); } }; stompClient.activate(); clientRef.current = stompClient; }, [url]); const disconnect = useCallback(() => { clientRef.current?.deactivate(); }, []); useEffect(() => { connect(); return () => disconnect(); }, [connect, disconnect]); return { metrics, connectionState, error, reconnect: connect }; }

Usage:

function Dashboard() { const { metrics, connectionState, reconnect } = useWebSocketMetrics(); if (connectionState === 'CONNECTING') { return <div>Connecting to metrics stream...</div>; } if (!metrics) { return <div>Waiting for data...</div>; } return ( <div> <h1>Total Events: {metrics.summary.totalEvents}</h1> <p>Throughput: {metrics.summary.eventsPerSecond} events/sec</p> {connectionState === 'RECONNECTING' && ( <button onClick={reconnect}>Retry Connection</button> )} </div> ); }

Connection States

StateDescription
CONNECTINGInitial connection attempt in progress
CONNECTEDSuccessfully connected and subscribed
DISCONNECTEDConnection closed (manual or error)
RECONNECTINGAttempting to reconnect after disconnection
ERRORConnection error occurred

Error Handling

Common Errors

ErrorCauseSolution
Connection refusedBackend not runningStart Eventara backend
STOMP errorInvalid subscriptionCheck topic name
Heartbeat timeoutNetwork issueCheck network connectivity
Max reconnect attemptsServer unreachableVerify server status

Implementing Error Recovery

stompClient.onStompError = (frame) => { console.error('STOMP Error:', frame); // Notify user showNotification('Connection error', 'error'); // Attempt to reconnect after delay setTimeout(() => { stompClient.activate(); }, 5000); };

Performance Considerations

Message Rate

  • Frequency: 1 message per second
  • Size: ~10-50KB per message (varies with metrics)
  • Bandwidth: ~10-50KB/s per client

Scaling

Current Setup:

  • Single WebSocket endpoint
  • Broadcasts to all connected clients
  • Suitable for ~100 concurrent connections

For Higher Scale:

  • Use message broker (RabbitMQ, Redis Pub/Sub)
  • Implement WebSocket clustering
  • Add load balancer with sticky sessions

Client Optimization

// Throttle updates to reduce UI re-renders import { debounce } from 'lodash'; const debouncedUpdate = debounce((metrics) => { updateUI(metrics); }, 100); // Update UI max once per 100ms stompClient.subscribe('/topic/metrics', (message) => { const metrics = JSON.parse(message.body); debouncedUpdate(metrics); });

Security Considerations

Current State

  • No authentication required
  • No encryption (ws://)
  • CORS enabled

Production Recommendations

  1. Use WSS (WebSocket Secure):

    wss://your-domain.com/ws
  2. Add Authentication:

    stompClient.onConnect = () => { // Include auth token in subscription stompClient.subscribe('/topic/metrics', callback, { 'Authorization': 'Bearer ' + authToken }); };
  3. Implement STOMP Authentication:

    const stompClient = new Client({ connectHeaders: { 'Authorization': 'Bearer ' + authToken } });
  4. Rate Limiting:

    • Limit connections per IP
    • Implement subscription limits

Testing WebSocket Connection

Using wscat

npm install -g wscat # Connect to WebSocket wscat -c ws://localhost:8080/ws

Using Browser DevTools

  1. Open browser DevTools (F12)
  2. Go to Network tab
  3. Filter by WS (WebSocket)
  4. Load page with WebSocket connection
  5. Click on WebSocket connection to see frames

Troubleshooting

Connection Not Establishing

  1. Check Backend Status:

    curl http://localhost:8080/api/v1/events/health
  2. Verify WebSocket Endpoint:

    curl -I http://localhost:8080/ws/info
  3. Check Firewall/Network:

    • Ensure port 8080 is accessible
    • Check for proxy/firewall blocking WebSocket

Not Receiving Messages

  1. Verify Subscription:

    • Correct topic: /topic/metrics
    • Subscription confirmed in logs
  2. Check Message Broadcast:

    • Backend should log broadcasts
    • Verify scheduler is running
  3. Test with REST API:

    curl http://localhost:8080/api/v1/metrics

    If REST works but WebSocket doesn’t, issue is with WebSocket layer.

High Latency

  1. Check Message Size:

    • Monitor network tab for payload size
    • Consider filtering metrics on server
  2. Network Issues:

    • Use browser DevTools to check latency
    • Test from different network
  3. Client Performance:

    • Profile JavaScript execution
    • Optimize render cycle
Last updated on