Node.js의 socket.io 클라이언트 파일 경로가 /socket.io/socket.io.js 인 이유
Socket.io
Socket.io란 WebSocket을 기반으로 클라이언트와 서버의 양방향 통신을 가능하게 해주는 모듈입니다. Socket.io를 사용하기 위해선 서버와 클라이언트 모두 Socket.io 라이브러리가 필요합니다.
socket.io 문서를 보면 client에서 socket.io 라이브러리를 html script로 불러올 때 다음 코드를 작성하도록 하게 합니다.
<script src="/socket.io/socket.io.js"></script>
문제는 Node.js 서버 개발 시에는 저런 파일을 정적(static)으로 제공한 적도, 저 파일을 만든 적도 없는데 사용할 수 있다는 것입니다.
socket.io 서버
위에서 작성한 코드는 웹 클라이언트에서 상대 경로에 있는 socket.io.js 파일을 서버 측에 요청하는 코드입니다. 그렇다면 Node.js 서버에서는 static 파일을 제공하는 코드를 작성해야 하는데, 먼저 socket.io 문서에서 제공하는 서버 측 코드를 보시면 좋을 것 같습니다.
// CommonJS
const httpServer = require("http").createServer();
const io = require("socket.io")(httpServer, {
// ...
});
io.on("connection", (socket) => {
// ...
});
httpServer.listen(3000);
위의 코드는 express.js나 nest.js 웹 프레임워크를 사용하지 않는 코드입니다.
위의 코드를 보면 알 수 있듯이, 기존 http 서버 인스턴스를 socket.io 라이브러리 인스턴스의 프로퍼티로 넣는 것을 확인할 수 있습니다.
이번엔 express.js 서버에서 사용하는 코드입니다.
const app = require("express")();
const httpServer = require("http").createServer(app);
const options = { /* ... */ };
const io = require("socket.io")(httpServer, options);
io.on("connection", socket => { /* ... */ });
httpServer.listen(3000);
마찬가지로 기존 express http 서버 인스턴스를 socket.io 라이브러리 인스턴스의 프로퍼티로 넣습니다.
이로써 우리는 http 서버 인스턴스를 사용해 socket.io를 사용할 수 있도록 하는 것을 추측할 수 있습니다.
socket.io.js는 어디에 있을까?
그렇다면 socket.io 내부에서는 기존 http 서버 인스턴스를 어떻게 활용하길래, socket.io.js 파일을 제공할 수 있었던 건 지 알아봐야 할 것 같습니다.
일단 node_modules
디렉토리에 있을 것 같아 socket.io의 디렉토리를 확인해보니 서브 디렉토리 client-dist
에 socket.io.js 파일이 있는 것을 확인했습니다.
그렇다면 socket.io 라이브러리 내에 client-dist 경로의 파일을 static 파일로 제공할 것 같아 socket.io github에 "client-dist" 를 검색해보니 다음과 같은 코드가 검색됐습니다.
private serve(req: http.IncomingMessage, res: http.ServerResponse): void {
const filename = req.url!.replace(this._path, "");
const isMap = dotMapRegex.test(filename);
const type = isMap ? "map" : "source";
// Per the standard, ETags must be quoted:
// https://tools.ietf.org/html/rfc7232#section-2.3
const expectedEtag = '"' + clientVersion + '"';
const weakEtag = "W/" + expectedEtag;
const etag = req.headers["if-none-match"];
if (etag) {
if (expectedEtag === etag || weakEtag === etag) {
debug("serve client %s 304", type);
res.writeHead(304);
res.end();
return;
}
}
debug("serve client %s", type);
res.setHeader("Cache-Control", "public, max-age=0");
res.setHeader(
"Content-Type",
"application/" + (isMap ? "json" : "javascript")
);
res.setHeader("ETag", expectedEtag);
if (!isMap) {
res.setHeader("X-SourceMap", filename.substring(1) + ".map");
}
Server.sendFile(filename, req, res);
}
위의 코드를 보면 알 수 있듯이, socket.io 서버 측 라이브러리에는 기존 http 서버를 활용해서, node_modules/socket.io/client-dist 경로에 있는 socket.io.js를 제공하는 함수 serve
가 있다는 것을 알 수 있습니다.
socket.io를 create 할 시에 node.js의 http 서버를 생성자로 받기에 이러한 코드가 있을 것 같은 예상을 하긴 했었지만 코드를 보니 확신할 수 있었습니다.
물론 socket.io.js 파일의 경로와 클라이언트에서 요청하는 socket.io.js파일의 경로가 다르지만, replace
을 이용해 요청받은 url의 경로를 socket.io.js파일의 경로로 변경하는 것을 알 수 있습니다.
결론
따라서 마무리 요약하자면 다음으로 정리할 수 있을 것 같습니다.
- socket.io 라이브러리 내부에 클라이언트에서 사용하는 socket.io.js 파일이 존재한다.
- 서버 측 socket.io는 기존 http 서버 인스턴스를 프로퍼티로 받아, 업그레이드 한다.
- 업그레이드 된 socket.io 서버는 socket.io.js 파일을 serve 할 수 있게 된다.