라봉이의 개발 블로그

Node.js의 socket.io 클라이언트 파일 경로가 /socket.io/socket.io.js 인 이유 본문

Node.js

Node.js의 socket.io 클라이언트 파일 경로가 /socket.io/socket.io.js 인 이유

Labhong 2021. 8. 22. 16:19
반응형

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 파일이 있는 것을 확인했습니다.

node_modules/에 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파일의 경로로 변경하는 것을 알 수 있습니다.

결론

따라서 마무리 요약하자면 다음으로 정리할 수 있을 것 같습니다.

  1. socket.io 라이브러리 내부에 클라이언트에서 사용하는 socket.io.js 파일이 존재한다.
  2. 서버 측 socket.io는 기존 http 서버 인스턴스를 프로퍼티로 받아, 업그레이드 한다.
  3. 업그레이드 된 socket.io 서버는 socket.io.js 파일을 serve 할 수 있게 된다.
반응형
Comments