WebSocket API
Eventara provides real-time metric updates through WebSocket connections using the STOMP protocol over SockJS.
Connection Details
Endpoint
ws://localhost:8080/wsFor production, use:
wss://your-domain.com/wsProtocol
- 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
| State | Description |
|---|---|
| CONNECTING | Initial connection attempt in progress |
| CONNECTED | Successfully connected and subscribed |
| DISCONNECTED | Connection closed (manual or error) |
| RECONNECTING | Attempting to reconnect after disconnection |
| ERROR | Connection error occurred |
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Connection refused | Backend not running | Start Eventara backend |
| STOMP error | Invalid subscription | Check topic name |
| Heartbeat timeout | Network issue | Check network connectivity |
| Max reconnect attempts | Server unreachable | Verify 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
-
Use WSS (WebSocket Secure):
wss://your-domain.com/ws -
Add Authentication:
stompClient.onConnect = () => { // Include auth token in subscription stompClient.subscribe('/topic/metrics', callback, { 'Authorization': 'Bearer ' + authToken }); }; -
Implement STOMP Authentication:
const stompClient = new Client({ connectHeaders: { 'Authorization': 'Bearer ' + authToken } }); -
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/wsUsing Browser DevTools
- Open browser DevTools (F12)
- Go to Network tab
- Filter by WS (WebSocket)
- Load page with WebSocket connection
- Click on WebSocket connection to see frames
Troubleshooting
Connection Not Establishing
-
Check Backend Status:
curl http://localhost:8080/api/v1/events/health -
Verify WebSocket Endpoint:
curl -I http://localhost:8080/ws/info -
Check Firewall/Network:
- Ensure port 8080 is accessible
- Check for proxy/firewall blocking WebSocket
Not Receiving Messages
-
Verify Subscription:
- Correct topic:
/topic/metrics - Subscription confirmed in logs
- Correct topic:
-
Check Message Broadcast:
- Backend should log broadcasts
- Verify scheduler is running
-
Test with REST API:
curl http://localhost:8080/api/v1/metricsIf REST works but WebSocket doesn’t, issue is with WebSocket layer.
High Latency
-
Check Message Size:
- Monitor network tab for payload size
- Consider filtering metrics on server
-
Network Issues:
- Use browser DevTools to check latency
- Test from different network
-
Client Performance:
- Profile JavaScript execution
- Optimize render cycle