0

I have a Python backend (built with FastAPI) and a JavaScript frontend (built with ReactJS). I created a websocket route in the backend, and a websocket component in the frontend. When both servers are run locally, the websocket works as expected. However, when I deploy to Azure Web App, the websocket cannot open the connection to the backend. Both my Azure Web App instances are using Linux and Python 3.9/NodeJS 18 LTS in their respective enviroments. Everything else in the application works as exppected.

Here is my frontend websocket code (I replaced the real domain address with MYDOMAIN):

import React, { useEffect } from "react";

const WebSocketExample = () => {
    const socket = new WebSocket("wss://MYDOMAIN/ws/");

    useEffect(() => {
        socket.onopen = function (event) {
            console.log("WebSocket connected");
        };

        socket.onmessage = function (event) {
            console.log("Received message:", event.data);
        };

        socket.onclose = function (event) {
            console.log("WebSocket closed");
        };

        return () => {
            socket.close();
        };
    }, [socket]);

    function sendEvent() {
        const eventData = "Event data";
        socket.send(eventData);
        console.log("Sent event:", eventData);
    }

    return (
        <div>
            <h1>WebSocket Example</h1>
            <button onClick={sendEvent}>Send Event</button>
        </div>
    );
};

export default WebSocketExample;

And my backend route:

from app.routes.crud import crud_router
from app.routes.login import login_router
from app.routes.security import security_router
from app.routes.configs import configs_router
from setup import app, root_logger

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi import  WebSocket
from fastapi.responses import JSONResponse, HTMLResponse
import uvicorn

app.include_router(login_router)
app.include_router(crud_router)
app.include_router(security_router)
app.include_router(configs_router)



app = FastAPI()
app.add_middleware( # necessary to allow requests from local services
    CORSMiddleware,
    allow_methods=["*"],
    allow_headers=["*"],
    allow_origins=[
        'http://localhost:3000',
        'https://MYDOMAIN',
    ],
    allow_credentials=True)


connected_clients = set()


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    # Accept WebSocket connection
    await websocket.accept()

    # Add client to the connected clients set
    connected_clients.add(websocket)

    try:
        while True:
            # Receive event data from the client
            event_data = await websocket.receive_text() + 'wow'
            root_logger.info(f"WEBSOCKET received event data: {event_data}")


            # Process the event data (e.g., validate, update state)

            # Broadcast the event data to all connected clients
            for client in connected_clients:
                await client.send_text(event_data)
                root_logger.info(f"WEBSOCKET has sent data: {event_data}")
    except Exception as e:
        root_logger.error(f"WEBSOCKET has error: {e}")
    finally:
        # Remove client from the connected clients set
        connected_clients.remove(websocket)
        root_logger.info(f"WEBSOCKET has removed client: {websocket}")

if __name__ == '__main__':
    uvicorn.run('main:app', reload=True, port=8000)

I get the following printed on the browser's console:

webs.js:7 WebSocket connection to 'wss://MYDOMAIN/ws/' failed: WebSocketExample @ webs.js:7 renderWithHooks @ react-dom.development.js:16305 mountIndeterminateComponent @ react-dom.development.js:20074 beginWork @ react-dom.development.js:21587 beginWork$1 @ react-dom.development.js:27426 performUnitOfWork @ react-dom.development.js:26557 workLoopSync @ react-dom.development.js:26466 renderRootSync @ react-dom.development.js:26434 performConcurrentWorkOnRoot @ react-dom.development.js:25738 workLoop @ scheduler.development.js:266 flushWork @ scheduler.development.js:239 performWorkUntilDeadline

I cannot for the life of me know what is wrong. I have scoured the internet and the AI's for answers, but there are surprisingly few mentions of this problem using this stack.

Thanks in advance for any answers.

EDIT: upon further testing I was able to setup a dummy Backend which succesfully tested for websocket connection. I used no CORS, so I am thinking that somehow FastAPI's CORS Middleware is interfering with the websocket connection.

I will now attempt removing CORS from my code, and seeing if it performs as expected. If that works, I will then attempt to setup Azure's CORS.

1 Answers1

0

I have found that the problem was related to how the websocket was implemented in the frontend. I changed the code to the following:

import React, { useEffect, useState } from "react";

const WebSocketExample = () => {
    const [socket, setSocket] = useState(null);
    
    
    useEffect(() => {
        // const newSocket = new WebSocket("ws://localhost:8000/ws");
        const newSocket = new WebSocket("wss://industriaapi2.azurewebsites.net/ws");
        setSocket(newSocket);
        return () => newSocket.close();
    }, []);

    useEffect(() => {
        if (!socket) return;

        socket.onopen = function (event) {
            console.log("WebSocket connected");
        };

        socket.onmessage = function (event) {
            console.log("Received message:", event.data);
        };

        socket.onclose = function (event) {
            console.log("WebSocket closed");
        };

        return () => {
            socket.close();
        };
    }, [socket]);

    async function sendEvent() {
        const eventData = "Event data";
        socket.send(eventData);
    }

    return (
        <div>
            <h1>WebSocket Example</h1>
            <button onClick={sendEvent}>Send Event</button>
        </div>
    );
};

export default WebSocketExample;

This allowed the connection to be established succesfully. Im an now figuring out how to broadcast message to all connected clients, and will edit this answer when I figure that out.