GuidesWebsockets

Code Samples

Sample Python Code

import json
import time
import queue
import threading
import signal
import sys
import logging
import websocket

logging.basicConfig(
    format='%(asctime)s.%(msecs)03d %(levelname)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S',
    level=logging.INFO,
    stream=sys.stdout
)
logger = logging.getLogger(__name__)

SITE_ID = 'your-site-id'
API_TOKEN = 'your-api-token'  # use secrets manager in production

message_queue = queue.Queue()

def on_open(ws):
    ws.send(json.dumps({'subscribe': f'/sites/{SITE_ID}/stats/clients'}))
    logger.info("Subscribed to channel")

def on_message(ws, message):
    message_queue.put(message)  # never block here

def on_error(ws, error):
    logger.error(f"WebSocket error: {error}")

def on_close(ws, code, msg):
    logger.info(f"WebSocket closed — code: {code}, msg: {msg}")
    if code == 1006:
        logger.warning("Abnormal closure — will reconnect")

def on_ping(ws, _):
    logger.debug("Ping received")

def on_pong(ws, _):
    logger.debug("Pong received")

def worker():
    count = 0
    while True:
        msg = message_queue.get(block=True)
        if msg is None:
            continue
        try:
            data = json.loads(msg)
            # TODO: send to your downstream sink
            count += 1
            if count % 100 == 0:
                logger.info(f"Processed {count} messages. Queue depth: {message_queue.qsize()}")
        except Exception as e:
            logger.error(f"Error processing message: {e}")

def establish_ws():
    ws = websocket.WebSocketApp(
        'wss://api-ws.mist.com/api-ws/v1/stream',
        header=[f'Authorization: Token {API_TOKEN}'],
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close,
        on_ping=on_ping,
        on_pong=on_pong,
    )
    ws.run_forever(ping_interval=60, ping_timeout=45)

if __name__ == '__main__':
    signal.signal(signal.SIGINT, lambda s, f: sys.exit(0))
    signal.signal(signal.SIGTERM, lambda s, f: sys.exit(0))
    threading.Thread(target=worker, daemon=True).start()
    while True:
        try:
            establish_ws()
        except Exception as e:
            logger.error(f"Connection error: {e}")
        logger.info("Reconnecting in 5 seconds...")
        time.sleep(5)

Sample JavaScript Code (Node.js)

// Requires: npm install ws
const WebSocket = require('ws');

const API_TOKEN = process.env.MIST_API_TOKEN;
const SITE_ID = process.env.MIST_SITE_ID;

if (!API_TOKEN || !SITE_ID) {
    throw new Error('Set MIST_API_TOKEN and MIST_SITE_ID before running this script.');
}

const WS_URL = 'wss://api-ws.mist.com/api-ws/v1/stream';
const SUBSCRIPTION = `/sites/${SITE_ID}/stats/clients`;
const RECONNECT_DELAY_MS = 5000;
const SUBSCRIBE_ACK_TIMEOUT_MS = 10000;
const LOG_EVERY_N_MESSAGES = 100;
const QUEUE_WARN_THRESHOLD = 5000;

const messageQueue = [];
let workerScheduled = false;
let processedCount = 0;
let reconnectTimer = null;
let ws = null;

function log(message, extra = '') {
    const suffix = extra ? ` ${extra}` : '';
    console.log(`${new Date().toISOString()} ${message}${suffix}`);
}

function scheduleWorker() {
    if (workerScheduled) return;
    workerScheduled = true;
    setImmediate(processQueue);
}

async function processQueue() {
    workerScheduled = false;

    while (messageQueue.length > 0) {
        const raw = messageQueue.shift();
        try {
            const event = JSON.parse(raw);
            void event;
            // TODO: send to downstream sink (Kafka/Kinesis/DB/etc.)
            processedCount += 1;
            if (processedCount % LOG_EVERY_N_MESSAGES === 0) {
                log('worker throughput', `processed=${processedCount} queue_depth=${messageQueue.length}`);
            }
        } catch (err) {
            log('worker processing error', err.message);
        }

        // Yield so socket callbacks keep running under load.
        await new Promise((resolve) => setImmediate(resolve));
    }
}

function connect() {
    let subscriptionConfirmed = false;
    let subscribeAckTimer = null;

    ws = new WebSocket(WS_URL, {
        headers: {
            Authorization: `Token ${API_TOKEN}`,
        },
    });

    ws.on('open', () => {
        log('websocket opened');
        ws.send(JSON.stringify({ subscribe: SUBSCRIPTION }));

        subscribeAckTimer = setTimeout(() => {
            if (!subscriptionConfirmed) {
                log('subscription ack timeout, reconnecting');
                ws.close();
            }
        }, SUBSCRIBE_ACK_TIMEOUT_MS);
    });

    ws.on('message', (buffer) => {
        const raw = buffer.toString();

        // Keep receive path fast: enqueue first, process elsewhere.
        messageQueue.push(raw);
        if (messageQueue.length === QUEUE_WARN_THRESHOLD || messageQueue.length % 10000 === 0) {
            log('queue depth warning', `queue_depth=${messageQueue.length}`);
        }
        scheduleWorker();

        // Lightweight control-plane handling in receive path.
        try {
            const msg = JSON.parse(raw);
            if (msg.event === 'channel_subscribed' && msg.channel === SUBSCRIPTION) {
                subscriptionConfirmed = true;
                clearTimeout(subscribeAckTimer);
                log('channel_subscribed', msg.channel);
            } else if (msg.event === 'subscribe_failed') {
                log('subscribe_failed', `${msg.channel || ''} ${msg.detail || ''}`.trim());
                ws.close();
            } else if (msg.event === 'data') {
                // TODO: send to your downstream sink
            }
        } catch (err) {
            log('control-plane parse error', err.message);
        }
    });

    ws.on('ping', () => {
        log('ping received');
    });

    ws.on('pong', () => {
        log('pong received');
    });

    ws.on('error', (err) => {
        log('websocket error', err.message);
    });

    ws.on('close', (code, reasonBuffer) => {
        clearTimeout(subscribeAckTimer);
        const reason = reasonBuffer ? reasonBuffer.toString() : '';
        log('websocket closed', `code=${code} reason=${reason}`.trim());

        if (!reconnectTimer) {
            reconnectTimer = setTimeout(() => {
                reconnectTimer = null;
                log('reconnecting');
                connect();
            }, RECONNECT_DELAY_MS);
        }
    });
}

function shutdown() {
    log('shutdown requested');
    if (reconnectTimer) {
        clearTimeout(reconnectTimer);
        reconnectTimer = null;
    }
    if (ws && ws.readyState === WebSocket.OPEN) {
        ws.close(1000, 'shutdown');
    }
    process.exit(0);
}

process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);

connect();

On this page