프로젝트를 진행하다 최근에 사용하게 된 Next.js를 이용해서 웹소켓 통신을 하는 client와 server를 동시에 구현하려고 했었다.
먼저 websocket connection이 맺어지는것을 확인하기 위해 다음과 같이 코드를 작성했다.
- pages/api/monitoring/socket.ts
import { Server } from "socket.io";
import { NextApiResponseServerIO } from '@/types/monitoring'
const io = new Server({ path: "/api/monitoring/socket" });
io.on("connection", (socket) => {
console.log('WebSocket connected successfully');
socket.on("disconnect", () => {
console.log("Client disconnected");
changeStream.close();
});
});
export default (req: NextApiResponseServerIO, res: NextApiResponseServerIO) => {
if (!res.socket.server.io) {
res.socket.server.io = io;
io.attach(res.socket!.server);
}
res.send({});
};
- pages/index.tsx
import { useEffect, useState } from 'react';
import { io } from "socket.io-client";
export default function Home() {
const [data, setData] = useState<any>("?");
useEffect(() => {
const socket = io("/", { path: "/api/monitoring/socket" });
console.log('Attempting to connect WebSocket');
socket.on('connect', () => {
console.log('connected', socket);
})
socket.on("documentChange", (change) => {
console.log("Document changed:", change);
setData(change)
});
return () => {
socket.disconnect();
};
}, []);
return (
<div>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
이렇게 간단히 프로젝트를 구성하고 프로젝트를 실행시켰다.
localhost:3000번에 들어가고 개발자 도구를 켰을때 내 생각은
Attempting to connect Websocket
connect <socket 번호>
이런식으로 값이 뜰 것으로 예상했다.
하지만 개발자도구의 console에 뜬 것을 확인해 본 결과
Attempting to connect Websocket
Attempting to connect Websocket
만 지속적으로 뜨는 것을 확인할 수 있었다.
그렇게 나는 네트워크 패킷을 열어보았고 client가 server에게 다음과 같이 네트워크 패킷을 날리는 것을 확인할 수 있었다.
GET /api/monitoring/socket/?EIO=4&trasport=polling...
나는 socket.io라는 라이브러리를 사용하고 있다. 해당 라이브러리는 사용자의 브라우저의 스펙과 서버의 네트워크 성능에 따라 http polling, websocket, webtransport 중에 자동적으로 선택해서 통신을 진행한다. 그럼 다음 2가지 의문점이 생기게 된다.
1. 왜 ws통신이 아닌 http polling을 사용하게 되었는가?
2. http polling이라도 connection이 맺어지지 않는 이유는 무엇일까?
먼저 1번에 대해서 확인하기 위해 강제적으로 socket.io를 이용한 옵션에 transports : ['websocket']이라는 설정을 넣었다.
그러자 다음과 같은 오류가 Console에 뜨게 되었다.
Websocket connection to 'ws://localhost/api/monitoring/socket/?EIO=4&transport=websocket' failed
그리고 네트워크 패킷을 보니 EIO=4&transport=websocket의 status는 finished가 뜨고 지속적으로 패킷이 날라오는 것을 확인할 수 있었다.
이를 통해 추측할 수 있는 점은 HTTP handshake이후 bidirectional connection이 성립이 안되고 있다는 것이다.
여기서 우리는 next.js의 api route에 대해서 좀 더 생각해볼 필요가 있다.
Next.js에서는 api의 엔드포인트가 js파일이 된다. 이를 통해 특정 api를 호출하게 되면 해당 js파일의 event handler가 호출되며 이를 통해 RestAPI의 역할을 할 수 있게 된다.
그렇기 때문에 우리가 websocket으로 통신을 하려고 하면 첫 http handshake가 호출이 되고 이에 따라 pages/api/monitoring/socket.ts의 이벤트 핸들러가 호출이 되는 것을 알 수 있다.
하지만 이후 client가 ws://localhost/api/monitoring/socket 을 호출하게 되면 서버는 이에 응답을 못하게 된다. 왜냐하면 아까 말한 socket.ts는 http://localhost/api/monitoring/socket.ts의 엔드포인트인거지 ws://localhost/api/monitoring/socket.ts의 엔드포인트가 아니기 때문이다.
그렇게 생각하면 wss프로토콜로 통신이 안된걸 이해할 수 있다. 또한 socket.io는 서버의 특성이 http만 지원하는 것을 알고 http polling으로 웹 소켓 연결을 시도한 것으로 이해할 수 있다.
하지만 이것도 api route 특성을 생각하면 현재 코드로는 불가한 것을 알 수 있다.
client가 request를 날리지만 결국 호출되는 것은 socket.ts의 이벤트 핸들러이기 때문에 해당 코드에서 server의 event가 발생해야 response를 날리는 로직을 추가하지 않는 한 http polling으로 실시간 통신이 되지 않는 것이다.
출처
긴 글 읽어주셔서 감사합니다.
틀린 부분이 있으면 댓글을 달아주시면 감사하겠습니다.
📧 : realhwan1202@gmail.com