Bizarre behaviour with dash-websockets

I developed a dash app using dash-websockets.

This was working just fine for about a month, until an issue arose running on AWS. No changes to the code or the server were made.

Now I am seeing a bizarre behavior: It runs fine if both server and client are running on my local machine. However, now when I run it in my AWS EC2 instance, the dash-client is trying to make a websocket connection to my local machine: The dash client does not make a websocket connction with the server in the cloud, but I noticed when I run the server locally the client in the cloud connects to.

I don’t even understand how client knows about my local system??

I made a simple test client/server to demonstrate the websocket issue. I will post it below.

Can someone explain please how this is possible?

How can I debug this issue?

Here is the code:

server:

# main_app.py - Claude Version 5.1

import asyncio
import websockets
import json
import logging
import random
import argparse

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Parse command line arguments
parser = argparse.ArgumentParser(description="WebSocket Server")
parser.add_argument('--port', type=int, default=8765, help='WebSocket server port')
args = parser.parse_args()

WEBSOCKET_HOST = 'localhost'
WEBSOCKET_PORT = args.port

connected = set()

# Simulated data for the table
table_data = [
    {"Exchange": "Binance", "Symbol": "BTC/USDT", "Price": 50000, "Volume": 100},
    {"Exchange": "Coinbase", "Symbol": "ETH/USDT", "Price": 3000, "Volume": 200},
    {"Exchange": "Kraken", "Symbol": "ADA/USDT", "Price": 2, "Volume": 5000},
]

async def update_table_data():
    while True:
        for row in table_data:
            row["Price"] += random.uniform(-100, 100)
            row["Volume"] += random.uniform(-1000, 1000)
        
        if connected:  # Only send updates if there are connected clients
            message = json.dumps({"type": "table_data", "content": table_data})
            websockets.broadcast(connected, message)
        
        await asyncio.sleep(1)  # Update every second

async def handle_websocket(websocket):
    logger.info("New WebSocket connection")
    connected.add(websocket)
    try:
        async for message in websocket:
            data = json.loads(message)
            if data['type'] == 'message':
                response = {"type": "response", "content": f"Received: {data['content']}"}
                await websocket.send(json.dumps(response))
    except websockets.exceptions.ConnectionClosed:
        logger.info("WebSocket connection closed")
    finally:
        connected.remove(websocket)

async def main():
    server = await websockets.serve(
        handle_websocket, 
        WEBSOCKET_HOST, 
        WEBSOCKET_PORT,
        process_request=process_request
    )
    logger.info(f"WebSocket server started on {WEBSOCKET_HOST}:{WEBSOCKET_PORT}")
    
    update_task = asyncio.create_task(update_table_data())
    
    await server.wait_closed()
    update_task.cancel()
    try:
        await update_task
    except asyncio.CancelledError:
        pass

async def process_request(path, headers):
    if "Origin" in headers:
        headers["Access-Control-Allow-Origin"] = headers["Origin"]
    return None

if __name__ == "__main__":
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("Received exit signal (CTRL+C)")

client:

# dash_app.py

import json
import logging
import argparse

import dash
import pandas as pd
from dash import dash_table, dcc, html
from dash.dependencies import Input, Output, State
from dash_extensions import WebSocket

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

# Parse command line arguments
parser = argparse.ArgumentParser(description="Dash WebSocket Client")
parser.add_argument('--port', type=int, default=8765, help='WebSocket server port')
args = parser.parse_args()

WEBSOCKET_HOST = 'localhost'
WEBSOCKET_PORT = args.port

app = dash.Dash(__name__)

app.layout = html.Div([
    html.H1("Dash WebSocket Client with DataTable"),
    dcc.Input(id='input-box', type='text', placeholder='Enter message'),
    html.Button('Send', id='send-button'),
    html.Div(id='output-div'),
    dash_table.DataTable(
        id='data-table',
        columns=[{"name": i, "id": i} for i in ["Exchange", "Symbol", "Price", "Volume"]],
        data=[],
        style_table={'height': '300px', 'overflowY': 'auto'}
    ),
    WebSocket(url=f"ws://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}", id="ws")
])

@app.callback(
    Output("ws", "send"),
    Input('send-button', 'n_clicks'),
    State('input-box', 'value'),
    prevent_initial_call=False
)
def send_message(n_clicks, value):
    if n_clicks:
        logger.info(f"Sending message: {value}")
        return json.dumps({"type": "message", "content": value})

@app.callback(
    Output('output-div', 'children'),
    Input("ws", "message")
)
def update_output(message):
    if message is None:
        return "No messages received yet."
    data = json.loads(message["data"])
    if data["type"] == "response":
        return f"Received: {data['content']}"
    return dash.no_update

@app.callback(
    Output('data-table', 'data'),
    Input("ws", "message")
)
def update_table(message):
    if message is None:
        return []
    data = json.loads(message["data"])
    if data["type"] == "table_data":
        df = pd.DataFrame(data['content'])
        return df.to_dict('records')
    return dash.no_update

if __name__ == '__main__':
    logger.info(f"Connecting to WebSocket server at ws://{WEBSOCKET_HOST}:{WEBSOCKET_PORT}")
    app.run_server(debug=True, host='0.0.0.0', port=8050)