I'm working on a React client app for a chat app using rabbitmq
, ws
, docker
, docker compose
, react-use-websocket
.
repository here
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)
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 #
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)
The full error :
localhost/:1 Error while trying to use the following icon from the Manifest: http://localhost:3050/logo192.png (Download error or resource isn't a valid image)
create-or-join.ts:97 WebSocket connection to 'ws://m1pex-server:10101/' failed:
exports.createOrJoinSocket @ create-or-join.ts:97
(anonymous) @ use-websocket.ts:106
step @ use-socket-io.ts:73
(anonymous) @ use-socket-io.ts:73
fulfilled @ use-socket-io.ts:73
Show 5 more frames
Show less
App.js:75 WebSocket error: Event {isTrusted: true, type: 'error', target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}isTrusted: truebubbles: falsecancelBubble: falsecancelable: falsecomposed: falsecurrentTarget: WebSocket {url: 'ws://m1pex-server:10101/', readyState: 3, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}defaultPrevented: falseeventPhase: 0returnValue: truesrcElement: WebSocket {url: 'ws://m1pex-server:10101/', readyState: 3, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}target: WebSocket {url: 'ws://m1pex-server:10101/', readyState: 3, bufferedAmount: 0, onopen: ƒ, onerror: ƒ, …}timeStamp: 542.3999999761581type: "error"[[Prototype]]: Event
onError @ App.js:75
webSocketInstance.onerror @ attach-listener.ts:87
error (async)
bindErrorHandler @ attach-listener.ts:86
__webpack_modules__../node_modules/react-use-websocket/dist/lib/attach-listener.js.exports.attachListeners @ attach-listener.ts:155
exports.createOrJoinSocket @ create-or-join.ts:105
(anonymous) @ use-websocket.ts:106
step @ use-socket-io.ts:73
(anonymous) @ use-socket-io.ts:73
fulfilled @ use-socket-io.ts:73
Promise.then (async)
step @ use-socket-io.ts:73
(anonymous) @ use-socket-io.ts:73
__webpack_modules__../node_modules/react-use-websocket/dist/lib/use-websocket.js.__awaiter @ use-socket-io.ts:73
start_1 @ use-websocket.ts:88
(anonymous) @ use-websocket.ts:126
invokePassiveEffectCreate @ react-dom.development.js:23487
callCallback @ react-dom.development.js:3945
invokeGuardedCallbackDev @ react-dom.development.js:3994
invokeGuardedCallback @ react-dom.development.js:4056
flushPassiveEffectsImpl @ react-dom.development.js:23574
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
flushPassiveEffects @ react-dom.development.js:23447
performSyncWorkOnRoot @ react-dom.development.js:22269
(anonymous) @ react-dom.development.js:11327
unstable_runWithPriority @ scheduler.development.js:468
runWithPriority$1 @ react-dom.development.js:11276
flushSyncCallbackQueueImpl @ react-dom.development.js:11322
flushSyncCallbackQueue @ react-dom.development.js:11309
unbatchedUpdates @ react-dom.development.js:22438
legacyRenderSubtreeIntoContainer @ react-dom.development.js:26020
render @ react-dom.development.js:26103
./src/index.js @ index.js:6
options.factory @ react refresh:6
__webpack_require__ @ bootstrap:24
(anonymous) @ startup:7
(anonymous) @ startup:7
Show 30 more frames
Show less
App.js:72 WebSocket connection closed
I suspect it might be a CORS issue, but I can't pinpoint the exact problem. Here are more 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
You can only use the service name as a host name from containers on the docker bridge network.
Your browser runs outside the docker network, so from there you have to use the host's IP address and the mapped port. That means that your URL should be ws://localhost:10101/
if you're running your application in a browser on the host machine.
Unfortunately, you've cut off the error message you get in your screenshot. If you show the full error message, it should say that it can't resolve the m1pex-server
host name.