프로그래밍 언어/NODE JS

미들웨어와 소켓 연결하기

· 코딩마이데이

방에 입장할 때와 퇴장할 때 채팅방의 다른 사람에게 '#12C6B8님이 입장하였습니다' 같은 시스템 메시지를 초대하려고 합니다. 그런데 사용자의 이름은 세션(req.session.color)에 들어있습니다. socket.IO에서 세션에 접근하려면 추가 작업이 필요합니다.

Socket.IO도 미들웨어를 사용할 수 있으므로 express-session을 공유하면 됩니다. 추가로 채팅방 접속자가 0명일 때 제거하는 코드도 같이 넣어보겠습니다.

const express = require("express");
const path = require("path");
const cookieParser = require("cookie-parser");
const session = require("express-session");
const nunjucks = require("nunjucks");
const dotenv = require("dotenv");
const ColorHash = require("color-hash");

dotenv.config();
const webSocket = require("./socket");
const indexRouter = require("./routes");
const morgan = require("morgan");
const connect = require("./schemas");
const ColorHash = require("color-hash");

const app = express();
app.set("port", process.env.PORT || 8005);
app.set("view engine", "html");

nunjucks.configure("views", {
  express: app,
  watch: true,
});

connect();

const sessionMiddleware = session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
});

app.use(morgan("dev"));
app.use(express.static(path.join(__dirname, "public")));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(sessionMiddleware);
app.use(
  session({
    resave: false,
    saveUninitialized: false,
    secret: process.env.COOKIE_SECRET,
    cookie: {
      httpOnly: true,
      secure: false,
    },
  }),
);

app.use((req, res, next) => {
  if (!req.session.color) {
    const colorHash = new ColorHash();
    req.session.color = colorHash.hex(req.sessionID);
  }
  next();
});

app.use("/", indexRouter);

app.use((req, res, next) => {
  const error = new Error(`${req.method} ${req.url} 라우터가 없습니다.`);
  error.status = 404;
  next(error);
});

app.use((err, req, res, next) => {
  res.locals.message = err.message;
  res.locals.error = process.env.NODE_ENV !== "production" ? err : {};
  res.status(err.status || 500);
  res.render("error");
});

const server = app.listen(app.get("port"), () => {
  console.log(app.get("port"), "번 포트에서 대기 중");
});

webSocket(server, app, sessionMiddleware);

 

app.js와 socket.js 간에 express-session 미들웨어를 공유하기 위해 변수로 분리했습니다.

socket.js와 다음과 같이 수정합니다.

const SocketIO = require("socket.io");
const axios = require("axios");

module.exports = (server, app, sessionMiddleware) => {
  const io = SocketIO(server, { path: "/socket.io" });
  app.set("io", io);
  const room = io.of("/room");
  const chat = io.of("/chat");

  io.use((socket, next) => {    // ❶
    sessionMiddleware(socket.request, socket.request.res, next);
  });

  room.on("connection", (socket) => {
    console.log("room 네임스페이스에 접속");
    socket.on("disconnect", () => {
      console.log("room 네임스페이스 접속 해제");
    });
  });

  chat.on("connection", (socket) => {
    console.log("chat 네임스페이스 접속");
    const req = socket.request;
    const {
      headers: { referer },
    } = req;
    const roomId = referer
      .split("/")
      [referer.split("/").length - 1].replace(/\?./, "");
    socket.join(roomId);
    socket.to(roomId).emit("join", {    // ❷
      user: "system",
      chat: `${req.session.color}님이 입장하셨습니다.`,
    });

    socket.on("disconnect", () => {
      console.log("chat 네임스페이스 접속 해제");
      socket.leave(roomId);
      const currentRoom = socket.adapter.rooms[roomId]; 	// ❸
      const userCount = currentRoom ? currentRoom.length : 0;
      if (userCount === 0) {
        // 접속사가 0명이면 방 삭제
        axios
          .delete("http://localhost:8005/room/${roomId}")
          .then(() => {
            console.log("방 제거 요청 성공");
          })
          .catch((error) => {
            console.error(error);
          });
      } else {
        socket.to(roomId).emit("exit", {
          user: "system",
          chat: `${req.session.color}님이 퇴장하셨습니다.`,
        });
      }
    });
  });
};

 

io.use 메서드에 미들웨어를 장착할 수 있습니다. 이 부분은 모든 웹 소켓 연결 시마다 실행됩니다. 세션 미들웨어에 요청 객체(socket.request), 응답 객체(socket.request.res), next 함수를 인수로 넣으면 됩니다. 이제 socket.request 객체 안에 socket.request.session 객체가 생성됩니다.

socket.to(방 아이디) 메서드로 특정 방에 데이터를 보낼 수 있습니다. 조금 전에 세션 미들웨어와 socket.IO를 연결했으므로 웹 소켓에서 세션을 사용할 수 있습니다. 방에 참여할 때 방에 누군가가 입장했다는 시스템 메시지를 보냅니다.

접속 해제 시에는 현재 방의 사람 수를 구해서 참여자 수가 0명이면 방에 제거하는 HTTP 요청을 보냅니다. socket.adapter.rooms[방 아이디]에 참여 중인 소켓 정보가 들어 있습니다. 참여자 수가 0명이 아니면 방에 남아 있는 참여자에게 퇴장했다는 데이터를 보냅니다.