Context
I’m working on a React client app for a chat app using rabbitmq
, ws
, docker
, docker compose
, react-use-websocket
.
repository here
Problem
The Websocket connection fails from the browser of the react app, but works perfectly fine inside the container when using wscat and also when running the react app locally (not using docker)
Demo
what works
The WebSocket connection from the client
to the server
works perfectly when I try it from within the client docker container using :
> make up -d
> docker compose exec m1pex-client sh
/app # npm install -g wscat
added 9 packages in 3s
/app # wscat -c ws://my-server:10101
Connected (press CTRL+C to quit)
> exit
Disconnected (code: 1006, reason: "")
/app #
and also we can see the server is listening on 10101:
docker-compose exec m1pex-server sh
/app # netstat -tuln
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.11:38569 0.0.0.0:* LISTEN
tcp 0 0 :::10101 :::* LISTEN
udp 0 0 127.0.0.11:49401 0.0.0.0:*
/app #
what fails
But it fails when the React app within the browser tries to connect to it :
(I’ve console.log
many times my .env REACT_APP_WS_URL
and it is right)
Solution (?)
I suspect it might be a CORS issue, but I can’t pinpoint the exact problem. Here are more details…
Details
docker-compose.yml:
networks:
m1pex-network:
driver: bridge
services:
m1pex-server:
build:
context: ./server
working_dir: /app
environment:
AMQP_URL: amqp://m1pex-rabbitmq:5672
PORT: 10101
volumes:
- ./server:/app:cached
- /app/node_modules
ports:
- "10101:10101"
depends_on:
- m1pex-rabbitmq
restart: on-failure
command: npm run start
networks:
- m1pex-network
m1pex-client:
build:
context: ./client
working_dir: /app
environment:
PORT: 3050
REACT_APP_WS_PORT: 10101
REACT_APP_WS_URL: ws://m1pex-server:10101
volumes:
- ./client:/app:cached
- /app/node_modules
ports:
- "3050:3050"
depends_on:
- m1pex-server
restart: on-failure
command: npm run start
networks:
- m1pex-network
m1pex-rabbitmq:
image: rabbitmq:3-management
ports:
- "8080:15672"
- "5672:5672"
restart: always
networks:
- m1pex-network
...
(client) app.js:
function App() {
return (
<ChakraProvider>
<Content />
</ChakraProvider>
);
}
export default App;
function Content() {
const [message, setMessage] = React.useState('');
const handleMessageChange = (event) => {
setMessage(event.target.value);
};
const [name, setName] = React.useState(username);
const handleNameChange = (event) => {
username = event.target.value;
setName(event.target.value);
};
const [messageLog, setMessageLog] = React.useState([]);
const { sendMessage, readyState } = useWebSocket(process.env.REACT_APP_WS_URL, {
onOpen: () => {
console.log('WebSocket connection opened');
},
onClose: () => {
console.log('WebSocket connection closed');
},
onError: (error) => {
console.error('WebSocket error:', error);
},
onMessage: (message) => {
const msg = JSON.parse(message.data);
if (msg.message !== null && msg.message !== '') {
setMessageLog((prevMessageLog) => [...prevMessageLog, msg]);
}
},
});
const sendMessageToEveryone = () => {
console.log('WebSocket URL:', process.env.REACT_APP_WS_URL);
console.log('WebSocket readyState:', readyState);
sendMessage(JSON.stringify({ username: name, message }));
};
return (.....)
(server) index.js :
...
wss.on('connection', (ws) => {
ws.id = Math.random() * 100000000;
ws.on('headers', (headers) => {
headers['Access-Control-Allow-Origin'] = '*';
});
ws.on('message', (message) => {
console.log('received: %s (%i)n', message, ws.id)
channel.publish(exchangeName, '', message)
})
ws.on('close', () => {
console.log(`Client disconnected`)
})
if (channel !== null) {
const queueName = `chat-client-${ws.id}`
channel
.assertQueue(queueName, {
autoDelete: true,
durable: false,
})
.then((ok) => {
channel.bindQueue(queueName, exchangeName, '')
})
.then((ok) => {
channel.consume(queueName, (message) => {
ws.send(JSON.stringify(JSON.parse(message.content)))
})
})
}
console.log(`connection started with ID ${ws.id}`)
})
...
network configuration:
[
{
"Name": "m1pex_tp_rabbitmq_m1pex-network",
"Id": "77c2126b6a6f319d26a14f0c8db19fd5c2b04d7932290c029a389a5af812785e",
"Created": "2024-06-07T11:45:09.111659472+02:00",
"Scope": "local",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "192.168.186.0/24",
"Gateway": "192.168.186.1"
}
]
},
"Internal": false,
"Attachable": false,
"Ingress": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {
"4637c18b4392941ada7832b5e4656585e1e50ceddf23fe524e445eca6076a7f5": {
"Name": "m1pex_tp_rabbitmq-m1pex-client-1",
"EndpointID": "e40ce9820eaa7890c362a919e09f3168278f79aae71950d2dd7b587a9f70bf66",
"MacAddress": "02:42:c0:a8:ba:04",
"IPv4Address": "192.168.186.4/24",
"IPv6Address": ""
},
"723afde52e1bdefab9184dccf420d3784d2a88604eb58c75296cd813cc7a45c8": {
"Name": "some-rabbit",
"EndpointID": "ef8a678a0c93db75f6f6d6ad085c6fcdd0e90642b9afd13a80e593955bb1caa5",
"MacAddress": "02:42:c0:a8:ba:02",
"IPv4Address": "192.168.186.2/24",
"IPv6Address": ""
},
"cb1c29c14e70fb24d99e4efbf41c9e2ad76f1b504fca53a02079ad0cf63d2bef": {
"Name": "m1pex_tp_rabbitmq-m1pex-server-1",
"EndpointID": "00f0d0106e8ab0be20b9018c51ca5b6f036f8408b20794f35c89f1fe4a6c123a",
"MacAddress": "02:42:c0:a8:ba:03",
"IPv4Address": "192.168.186.3/24",
"IPv6Address": ""
}
},
"Options": {},
"Labels": {
"com.docker.compose.network": "m1pex-network",
"com.docker.compose.project": "m
hopefully I didn’t forget anything, if you need any more informations, the Github repository is right here