Hello everyone, I have a routing problem between my docker which contains a websocket service and my docker traefik.
Objective: I want my websocket service to be accessible from my docker traefik in WSS via my domain name monsupertestdocker.com.
How add websocket(WSS) in my prod with traefik ?
Thank you
Here's the initialization of my nodeJS containing two services:
- API REST which is on the '/api' prefix via port 3000
- Websocket via port 3000
My server.ts file :
import express, { Express } from 'express';
import { AddressInfo } from 'net';
import { Config } from './config';
import { ContainerMiddleware } from './container-ioc';
import { Router } from './app/router';
import { Server as HttpServer } from 'http';
import WebSocket, { WebSocketServer } from 'ws';
export class Server {
private config: Config;
private express: Express;
httpServer?: HttpServer;
wss?: WebSocketServer;
constructor({
config,
router,
containerMiddleware,
}: {
config: Config;
router: Router;
containerMiddleware: ContainerMiddleware;
}) {
this.config = config;
this.express = express();
this.express.disable('x-powered-by');
this.express.use(containerMiddleware);
this.express.use(router);
}
start() {
return new Promise<HttpServer>((resolve) => {
const http = this.express.listen(this.config.port, () => {
const { port } = http.address() as AddressInfo;
console.log(`App listening on the port: ${port}`);
this.setupWebSocket();
resolve(http);
});
this.httpServer = http;
});
}
setupWebSocket() {
this.wss = new WebSocketServer({ server: this.httpServer });
this.wss.on('connection', (ws: WebSocket) => {
console.log('New client connected');
ws.on('message', (message: string) => {
console.log(`Received message => ${message}`);
});
ws.on('close', () => {
console.log('Client disconnected');
});
});
}
broadcast(message: string) {
this.wss?.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
}
With docker compose I create a container called my-backend that contains the API REST service and the websocket service, and another docker that contains the traefik that will do the routing between the 2 services.
My docker-compose.yml :
################################################
# NETWORKS
################################################
networks:
my-network:
external: true
################################################
# SERVICES
################################################
services:
my-backend:
container_name: my-backend
image: registry.gitlab.com/dev9865655/my/my-backend:latest
ports:
- '3000:3000'
networks:
- my-network
labels:
# API REST
- 'traefik.http.routers.my-backend-api.rule=Host(`monsupertestdocker.com`) && PathPrefix(`/api`)'
- 'traefik.http.routers.my-backend-api.service=my-backend-api'
- 'traefik.http.services.my-backend-api.loadbalancer.server.port=3000'
- 'traefik.http.routers.my-backend-api.tls=true'
- 'traefik.http.routers.my-backend-api.tls.certresolver=myresolver'
# WebSocket
- 'traefik.http.routers.my-backend-ws.rule=Host(`monsupertestdocker.com`) && Headers(`Upgrade`, `websocket`)'
- 'traefik.http.routers.my-backend-ws.service=my-backend-ws'
- 'traefik.http.services.my-backend-ws.loadbalancer.server.port=3000'
- 'traefik.http.routers.my-backend-ws.tls=true'
- 'traefik.http.routers.my-backend-ws.tls.certresolver=myresolver'
- 'traefik.http.middlewares.websocket.headers.customrequestheaders.Upgrade=websocket'
- 'traefik.http.middlewares.websocket.headers.customrequestheaders.Connection=Upgrade'
- 'traefik.http.routers.my-backend-ws.middlewares=websocket'
environment:
- PGSQL_DATABASE_USER=${PGSQL_DATABASE_USER}
- PGSQL_DATABASE_PASSWORD=${PGSQL_DATABASE_PASSWORD}
- PGSQL_DATABASE_NAME=${PGSQL_DATABASE_NAME}
- PGSQL_DATABASE_PORT=${PGSQL_DATABASE_PORT}
- PGSQL_DATABASE_HOST=${PGSQL_DATABASE_HOST}
traefik:
image: traefik:v2.5
container_name: traefik
restart: unless-stopped
command:
- '--log.level=DEBUG'
- '--api.insecure=true'
- '--providers.docker=true'
- '--entrypoints.web.address=:80'
- '--entrypoints.websecure.address=:443'
- '--certificatesresolvers.myresolver.acme.httpchallenge=true'
- '--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web'
- '--certificatesresolvers.myresolver.acme.email=contact@monsupertestdocker.com'
- '--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json'
ports:
- '80:80'
- '443:443'
- '8080:8080'
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
- './letsencrypt:/letsencrypt'
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.api.rule=Host(`traefik.monsupertestdocker.com`)'
- 'traefik.http.services.api.loadbalancer.server.port=8080'
- 'traefik.http.routers.api.entrypoints=traefik'
- 'traefik.http.routers.api.service=api@internal'
- 'traefik.http.routers.api.middlewares=auth'
- 'traefik.http.middlewares.auth.basicauth.users=${TRAEFIK_USER}:${TRAEFIK_PASSWORD_HASH}'
- 'traefik.http.routers.api.tls=true'
- 'traefik.http.routers.api.tls.certresolver=myresolver'
networks:
- my-network
depends_on:
- my-backend
Result:
-
With Postman at 'http://localhost:3000' everything works. I can authenticate via apiRest and in 'ws://localhost:3000' connect via websocket.
-
In prod via 'https://monsupertestdocker.com/api/login' I can authenticate. But in 'wss://monsupertestdocker.com' I get a 502 error when I want to connect to the websocket.
Here is error by Postman when i try to connect with wss :
Error: Unexpected server response: 502
Handshake Details
Request Method: GET
Status Code: 502 Bad Gateway
Request Headers
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: TqTac3kITFk5Llf0+efqAQ==
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: monsupertestdocker.com
Response Headers
Date: Wed, 07 Aug 2024 17:26:17 GMT
Content-Length: 11
Content-Type: text/plain; charset=utf-8
here is traefik logs :
time="2024-08-07T15:55:36Z" level=debug msg="vulcand/oxy/roundrobin/rr: begin ServeHttp on request" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\",\"RawFragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Connection\":[\"Upgrade\"],\"Sec-Websocket-Extensions\":[\"permessage-deflate; client_max_window_bits\"],\"Sec-Websocket-Key\":[\"4d9l4L3nytw52lblsQzWsQ==\"],\"Sec-Websocket-Version\":[\"13\"],\"Upgrade\":[\"websocket\"],\"X-Forwarded-Host\":[\"monsupertestdocker.com\"],\"X-Forwarded-Port\":[\"443\"],\"X-Forwarded-Proto\":[\"wss\"],\"X-Forwarded-Server\":[\"fd008220b30c\"],\"X-Real-Ip\":[\"93.176.11.97\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"monsupertestdocker.com\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"93.176.11.97:55834\",\"RequestURI\":\"/\",\"TLS\":null}"
time="2024-08-07T15:55:36Z" level=debug msg="vulcand/oxy/roundrobin/rr: Forwarding this request to URL" ForwardURL="http://172.18.0.4:4200" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\",\"RawFragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Connection\":[\"Upgrade\"],\"Sec-Websocket-Extensions\":[\"permessage-deflate; client_max_window_bits\"],\"Sec-Websocket-Key\":[\"4d9l4L3nytw52lblsQzWsQ==\"],\"Sec-Websocket-Version\":[\"13\"],\"Upgrade\":[\"websocket\"],\"X-Forwarded-Host\":[\"monsupertestdocker.com\"],\"X-Forwarded-Port\":[\"443\"],\"X-Forwarded-Proto\":[\"wss\"],\"X-Forwarded-Server\":[\"fd008220b30c\"],\"X-Real-Ip\":[\"93.176.11.97\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"monsupertestdocker.com\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"93.176.11.97:55834\",\"RequestURI\":\"/\",\"TLS\":null}"
time="2024-08-07T15:55:36Z" level=debug msg="'502 Bad Gateway' caused by: EOF"
time="2024-08-07T15:55:36Z" level=debug msg="vulcand/oxy/roundrobin/rr: completed ServeHttp on request" Request="{\"Method\":\"GET\",\"URL\":{\"Scheme\":\"\",\"Opaque\":\"\",\"User\":null,\"Host\":\"\",\"Path\":\"/\",\"RawPath\":\"\",\"ForceQuery\":false,\"RawQuery\":\"\",\"Fragment\":\"\",\"RawFragment\":\"\"},\"Proto\":\"HTTP/1.1\",\"ProtoMajor\":1,\"ProtoMinor\":1,\"Header\":{\"Connection\":[\"Upgrade\"],\"Sec-Websocket-Extensions\":[\"permessage-deflate; client_max_window_bits\"],\"Sec-Websocket-Key\":[\"4d9l4L3nytw52lblsQzWsQ==\"],\"Sec-Websocket-Version\":[\"13\"],\"Upgrade\":[\"websocket\"],\"X-Forwarded-Host\":[\"monsupertestdocker.com\"],\"X-Forwarded-Port\":[\"443\"],\"X-Forwarded-Proto\":[\"wss\"],\"X-Forwarded-Server\":[\"fd008220b30c\"],\"X-Real-Ip\":[\"93.176.11.97\"]},\"ContentLength\":0,\"TransferEncoding\":null,\"Host\":\"monsupertestdocker.com\",\"Form\":null,\"PostForm\":null,\"MultipartForm\":null,\"Trailer\":null,\"RemoteAddr\":\"93.176.11.97:55834\",\"RequestURI\":\"/\",\"TLS\":null}"