TL;DR
node.js에서 하나의 http 서버를 공유하는 여러 웹소켓 서버를 생성하여 발생한 문제 (ws 모듈 사용)
문제상황
브라우저에서 웹소켓 연결 시 아래와 같이 Invalid frame header 에러 발생
개발자도구의 네트워크 탭을 확인해보니 아래와 같이 websocket으로 업그레이드는 잘 되었다고 나옴
원인
기존에 node.js에서 ws모듈을 사용해서 여러 웹소켓 서버를 각각 포트를 할당해서 띄우고 있었는데, 하나의 http 서버를 공유하도록 수정했더니 문제 발생
너무 안일하게 당연히 될 거라고 생각했다.
해결방법
아래의 ws 모듈 문서를 확인해보니 대략적으로 이런 내용이 있었다.
- 서버 인스턴스 생성시 포트를 지정하면 http 서버가 자동으로 생성되고 사용됨
- 외부 http서버를 사용하려면 서버를 지정하거나 noServer 옵션을 사용
- noServer 옵션은 웹소켓 서버와 http서버를 분리하기 때문에, 하나의 http서버에 여러 웹소켓 서버를 공유할 수 있음
즉, 하나의 http서버에 여러 웹소켓 서버를 공유하려면 noServer옵션으로 웹소켓 서버 인스턴스를 생성해야 함
https://github.com/websockets/ws/blob/master/doc/ws.md
Create a new server instance. One and only one of port, server or noServer must be provided or an error is thrown. An HTTP server is automatically created, started, and used if port is set. To use an external HTTP/S server instead, specify only server or noServer. In this case, the HTTP/S server must be started manually. The "noServer" mode allows the WebSocket server to be completely detached from the HTTP/S server. This makes it possible, for example, to share a single HTTP/S server between multiple WebSocket servers.
코드
❌ 틀린 코드
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
//웹소켓 서버 하나만 열거면 이렇게 사용해도 됨
const wsServerMain = new WebSocket.Server({ server, "/ws" });
const wsServerCam = new WebSocket.Server({ server, "/ws/camera" });
아래와 같이 수정
const http = require('http');
const WebSocket = require('ws');
const server = http.createServer();
//noServer 옵션으로 웹소켓 서버 인스턴스 생성
const wsServerMain = new WebSocket.Server({ noServer: true });
const wsServerCam = new WebSocket.Server({ noServer: true });
//http 요청이 upgrade인 경우
server.on('upgrade', (req, socket, head) => {
//요청 URL에 따라 적절한 웹소켓 서버로 업그레이드 처리
if (req.url === '/ws') {
wsServerMain.handleUpgrade(req, socket, head, (ws) => {
//업그레이드 완료 후 'connection' 이벤트 발생 시킴
wsServerMain.emit('connection', ws, req);
});
} else if (req.url === '/ws/camera') {
wsServerCam.handleUpgrade(req, socket, head, (ws) => {
wsServerCam.emit('connection', ws, req);
});
} else {
socket.destroy();
}
});
------- 이런식으로 사용하면 될듯 --------
//Core.ts
public start(server: http.Server): void {
this.websocketServer.init(this);
this.websocketServerForCamera.init(this);
server.on("upgrade", (req, socket, head) => {
const url = req.url || "";
if (url.startsWith("/ws/camera")) {
this.websocketServerForCamera.handleUpgrade(req, socket, head);
} else if (url.startsWith("/ws")) {
this.websocketServer.handleUpgrade(req, socket, head);
} else {
socket.destroy();
}
});
...
abstract class BaseWebsocketServer<T extends WsClient = WsClient> {
protected core: Core;
protected wss: WebSocket.Server;
protected clients: T[] = [];
constructor() {}
init(core: Core) {
this.core = core;
this.wss = new WebSocket.Server({ noServer: true });
this.wss.on("connection", (ws: WebSocket, req: IncomingMessage) => {
this.logWsClientConnnection(req, "connected");
const client = this.createClient(ws);
ws.on("message", (message) => {
this.onMessage(client, message);
});
ws.on("close", () => {
this.onClose(client);
this.clients = this.clients.filter((c) => c.ws !== ws);
this.logWsClientConnnection(req, "disconnected");
});
ws.on("error", (err) => {
logger.error(`[ws] error on ws: ${err}`);
});
});
}
handleUpgrade(req: IncomingMessage, socket: internal.Duplex, head: Buffer) {
this.wss.handleUpgrade(req, socket, head, (ws) => {
this.wss.emit("connection", ws, req);
});
}
...
여담
node 서버를 https -> http로 변경하면서 웹소켓도 wss에서 ws 프로토콜로 변경하게 되었다.
그리고 nginx를 도입하였고
웹소켓 서버를 여러 개 띄워야 하는 요구사항이 생겨서 추상화까지 해버렸다.
당연히 잘 돌겠지~ 하면서 프론트엔드 수정하다보니 웹소켓 연결이 안돼서 원인을 찾는데 시간이 조금 걸렸다....
너무 안일하게 생각했다....
'Web > Node.js' 카테고리의 다른 글
[Docker] node:alpine에서 bcrypt 사용 시 앱이 죽는 문제 해결 (0) | 2025.05.21 |
---|---|
Nest.js vs Spring에 대한 생각 (0) | 2025.03.25 |