์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

[๋…ธ๋“œ 2] 12์žฅ. ์›น ์†Œ์ผ“์œผ๋กœ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†กํ•˜๊ธฐ

23-24/Node.js 2

by _๋„๋‹ด 2024. 1. 5. 10:00

๋ณธ๋ฌธ

728x90

 

๐ŸŒŸ12์žฅ ํ‚ค์›Œ๋“œ๐ŸŒŸ

์›น ์†Œ์ผ“

socket.io

ws

์–‘๋ฐฉํ–ฅ

๋‹จ๋ฐฉํ–ฅ

 

 

 

12.1 ์›น ์†Œ์ผ“ ์ดํ•ดํ•˜๊ธฐ

 

์›น ์†Œ์ผ“:

  • HTML5์— ์ƒˆ๋กœ ์ถ”๊ฐ€๋œ ์ŠคํŽ™
  • ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ€๋Šฅ
  • WS ํ”„๋กœํ† ์ฝœ ์‚ฌ์šฉ
  • ์‚ฌ์šฉ์กฐ๊ฑด์€ ์„œ๋ฒ„์™€ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ WSํ”„๋กœํ† ์ฝœ์„ ์ง€์›ํ•  ๊ฒƒ(๋…ธ๋“œ ๊ฐ™์€ ๊ฒƒ๊ณผ ์ตœ์‹ ์˜ ๋ธŒ๋ผ์šฐ์ €๋Š” wsํ˜น์€ Socket.IO ๊ฐ™์€ ํŒจํ‚ค์ง€๋กœ ์›น ์†Œ์ผ“ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)
  • ์ตœ์ดˆ ์—ฐ๊ฒฐ ์ดํ›„ ์ญ‰ ์—ฐ๊ฒฐ๋œ ์ƒํƒœ์ด๋ฏ€๋กœ ์—…๋ฐ์ดํŠธ ์œ ๋ฌด ํ™•์ธ ์š”์ฒญ ๋ณด๋‚ผ ํ•„์š” ์—†์Œ(ํด๋ง์˜ ๋‹จ์  ํ•ด๊ฒฐ)
  • HTTP ํ”„๋กœํ† ์ฝœ๊ณผ ํฌํŠธ ๊ณต์œ  ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ ๋‹ค๋ฅธ ํฌํŠธ์— ์—ฐ๊ฒฐํ•  ํ•„์š” ์—†์Œ
  • ์„œ๋ฒ„์„ผํŠธ์ด๋ฒคํŠธ์˜ ์ž‘์—… ๊ฐ€๋Šฅ

 

 

ํด๋ง:

  • ์›น ์†Œ์ผ“์ด ๋‚˜์˜ค๊ธฐ ์ด์ „์˜ ๊ธฐ์ˆ 
  • ์ฃผ๊ธฐ์ ์œผ๋กœ ์„œ๋ฒ„์— ์ƒˆ๋กœ์šด ์—…๋ฐ์ดํŠธ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ด์•ผ ํ•˜๋Š” ๋‹จ์ 
  • ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ 
  • ํด๋ผ์ด์–ธํŠธ -> ์„œ๋ฒ„ O
  • ์„œ๋ฒ„ -> ํด๋ผ์ด์–ธํŠธ X

 

์„œ๋ฒ„์„ผํŠธ ์ด๋ฒคํŠธ SSE:

  • EventSource ๋ผ๋Š” ๊ฐ์ฒด ์ด์šฉ
  • ์ตœ์ดˆ ์—ฐ๊ฒฐ ์ดํ›„ ์ง€์†์ ์œผ๋กœ ์—ฐ๊ฒฐ
  • ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ํ•„์š” ์—†๋Š” ์ž‘์—…์— ์‚ฌ์šฉ(์ฐจํŠธ ๊ฐ€์ ธ์˜ค๊ธฐ, ์ƒˆ ๊ฒŒ์‹œ๋ฌผ ๊ฐ€์ ธ์˜ค๊ธฐ ๋“ฑ)
  • ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ 
  • ํด๋ผ์ด์–ธํŠธ -> ์„œ๋ฒ„ X
  • ์„œ๋ฒ„ -> ํด๋ผ์ด์–ธํŠธ O

 

 

Socket.IO

  • ์›น ์†Œ์ผ“ ๊ด€๋ฆฌ๋ฅผ ๋•๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • ์›น ์†Œ์ผ“์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” IE9 ๊ฐ™์€ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์•Œ์•„์„œ ์›น ์†Œ์ผ“ ๋Œ€์‹  ํด๋ง ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๊ฟ”์คŒ

 

 

12.2 ws๋ชจ๋“ˆ๋กœ ์›น ์†Œ์ผ“ ์‚ฌ์šฉํ•˜๊ธฐ

 

gif-chat์ด๋ผ๋Š” ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ, ์•„๋ž˜์˜ jsonํŒŒ์ผ ์ƒ์„ฑ ํ›„

npm i ๋กœ ํŒจํ‚ค์ง€ ์„ค์น˜, env, app.js, index.js ํŒŒ์ผ ์ƒ์„ฑ

 

// .env

COOKIE_SECRET=gifchat
 
// app.js

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');

dotenv.config();
const indexRouter = require('./routes');

const app = express();
app.set('port', process.env.PORT || 8005);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});

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(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));

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');
});

app.listen(app.get('port'), () => {
  console.log(app.get('port'), '๋ฒˆ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ์ค‘');
});
 
 
// routes.index.js

const express = require('express');

const router = express.Router();

router.get('/', (req, res) => {
  res.render('index');
});

module.exports = router;
 

 

npm i ws@8 ๋กœ ws๋ชจ๋“ˆ ์„ค์น˜, app.js ํŒŒ์ผ ์ˆ˜์ •ํ•ด์„œ ์›น ์†Œ์ผ“์„ ์ต์Šคํ”„๋ ˆ์Šค ์„œ๋ฒ„์— ์—ฐ๊ฒฐ

 

// app.js

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');

dotenv.config();
const webSocket = require('./socket');
const indexRouter = require('./routes');		// ์ถ”๊ฐ€

const app = express();
app.set('port', process.env.PORT || 8005);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});

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(session({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  cookie: {
    httpOnly: true,
    secure: false,
  },
}));

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);		// ์ถ”๊ฐ€
 

 

 

์›น ์†Œ์ผ“ ๋กœ์ง์„ socket.jsํŒŒ์ผ์— ์ž‘์„ฑ

// socket.js

const WebSocket = require('ws');

module.exports = (server) => {
  const wss = new WebSocket.Server({ server });

  wss.on('connection', (ws, req) => { // ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์‹œ
    const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
    console.log('์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ ์ ‘์†', ip);
    ws.on('message', (message) => { // ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€
      console.log(message.toString());
    });
    ws.on('error', (error) => { // ์—๋Ÿฌ ์‹œ
      console.error(error);
    });
    ws.on('close', () => { // ์—ฐ๊ฒฐ ์ข…๋ฃŒ ์‹œ
      console.log('ํด๋ผ์ด์–ธํŠธ ์ ‘์† ํ•ด์ œ', ip);
      clearInterval(ws.interval);
    });

    ws.interval = setInterval(() => { // 3์ดˆ๋งˆ๋‹ค ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก
      if (ws.readyState === ws.OPEN) {
        ws.send('์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.');
      }
    }, 3000);
  });
};
 
 

ws์— ์—ฐ๊ฒฐ๋œ ์ด๋ฒคํŠธ๋ฆฌ์Šค๋„ˆ 3๊ฐœ

  • messing
  • error
  • close: ํด๋ผ์ด์–ธํŠธ์™€ ์—ฐ๊ฒฐ ๋Š์–ด์ง

setIneterval์„ clearInterval๋กœ ๋ฐ”๊ฟ”์•ผ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ง‰์Œ

 

 

 

์›น ์†Œ์ผ“์˜ ์ƒํƒœ:

  • connecting
  • open: ์—๋Ÿฌ ์—†์ด ๋ฉ”์‹œ์ง€ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Œ
  • closing
  • closed

 

 

์ดํ›„ index.html๊ณผ error.html ์ƒ์„ฑ

// index.html


<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>GIF ์ฑ„ํŒ…๋ฐฉ</title>
</head>
<body>
<div>F12๋ฅผ ๋ˆŒ๋Ÿฌ console ํƒญ๊ณผ network ํƒญ์„ ํ™•์ธํ•˜์„ธ์š”.</div>
<script>
  const webSocket = new WebSocket("ws://localhost:8005");
  webSocket.onopen = function () {
    console.log('์„œ๋ฒ„์™€ ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์„ฑ๊ณต!');
  };
  webSocket.onmessage = function (event) {
    console.log(event.data);
    webSocket.send('ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„๋กœ ๋‹ต์žฅ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค');
  };
</script>
</body>
</html>

 

// error.html

<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>

 

 

npm start ๋กœ ์‹คํ–‰ํ•˜๊ณ  http://localhoset:8005 ์ ‘์†

 

 

 

ํ„ฐ๋ฏธ๋„์„ ํ†ตํ•ด์„œ 3์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ๋‚ด์šฉ์ด ๋‚˜์˜ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 

12.3 Socket.IO ์‚ฌ์šฉํ•˜๊ธฐ

 

WS๋ชจ๋“ˆ์ด Socket.IO๊ฐ€ ํ•˜๋Š” ์ผ์„ ๋ชปํ•˜๋Š” ์ผ์„ ๋ชปํ•˜์ง„ ์•Š์ง€๋งŒ, 

๋ณด๋‹ค ๋ณต์žกํ•œ ์ผ์„ ์ž‘์—…์„ ํ•˜๊ธฐ์— Socket.IO๊ฐ€ ํŽธ์˜๊ธฐ๋Šฅ์ด ๋งŽ์ด ์ถ”๊ฐ€๋˜์–ด ๋” ์ ํ•ฉํ•˜๋‹ค.

 

npm i socket.io@4 ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ ์†Œ์ผ“์„ ์„ค์น˜ํ•œ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  socket.jsํŒŒ์ผ์„ ์—ฐ๊ฒฐํ•œ๋‹ค.

//socket.js

const SocketIO = require('socket.io');	// ์ฃผ์˜

module.exports = (server) => {
  const io = SocketIO(server, { path: '/socket.io' });

  io.on('connection', (socket) => { // ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์‹œ
    const req = socket.request;
    const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
    console.log('์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ ์ ‘์†!', ip, socket.id, req.ip);
    socket.on('disconnect', () => { // ์—ฐ๊ฒฐ ์ข…๋ฃŒ ์‹œ
      console.log('ํด๋ผ์ด์–ธํŠธ ์ ‘์† ํ•ด์ œ', ip, socket.id);
      clearInterval(socket.interval);
    });
    socket.on('error', (error) => { // ์—๋Ÿฌ ์‹œ
      console.error(error);
    });
    socket.on('reply', (data) => { // ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€
      console.log(data);
    });
    socket.interval = setInterval(() => { // 3์ดˆ๋งˆ๋‹ค ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฉ”์‹œ์ง€ ์ „์†ก
      socket.emit('news', 'Hello Socket.IO');
    }, 3000);
  });
};

 

emit ๋ฉ”์„œ๋“œ:

3์ดˆ๋งˆ๋‹ค ํด๋ผ์ด์–ธํŠธ ํ•œ ๋ช…์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ด๋Š” ๋ถ€๋ถ„. ์ด๋ฒคํŠธ ์ด๋ฆ„, ๋ฐ์ดํ„ฐ ๋‘ ๊ฐ€์ง€ ์ธ์ˆ˜๋ฅผ ๋ณด๋‚ธ๋‹ค.

 

์•„๋ž˜๋Š” index.html ํŒŒ์ผ์€ ์•„๋ž˜๋‹ค.

// index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>GIF ์ฑ„ํŒ…๋ฐฉ</title>
</head>
<body>
<div>F12๋ฅผ ๋ˆŒ๋Ÿฌ console ํƒญ๊ณผ network ํƒญ์„ ํ™•์ธํ•˜์„ธ์š”.</div>
<script src="/socket.io/socket.io.js"></script>
<script>
  const socket = io.connect('http://localhost:8005', {
    path: '/socket.io',
    transports: ['websocket'],
  });
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('reply', 'Hello Node.JS');
  });
</script>
</body>
</html>

 

 

 

 

12.4 ์‹ค์‹œ๊ฐ„ GIF ์ฑ„ํŒ…๋ฐฉ ๋งŒ๋“ค๊ธฐ

 

๋ชฝ๊ณ ๋””๋น„๋ž‘ ๋ชฝ๊ณ  ๋””๋น„ ODM์ธ ๋ชฝ๊ตฌ์Šค๋ฅผ ์ด์šฉํ•ด gif๋ฅผ ํฌํ•จํ•˜๋„๋ก ์ถ”๊ฐ€ํ•ด ์ˆ˜์ • ๋ฐ ๋ณด์™„ํ•œ๋‹ค.

 

 

npm i mongoose multer color-hash@2  ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ

์ด๋ฏธ์ง€ ์—…๋กœ๋“œ์— ํ•„์š”ํ•œ multer ์™€ ๋žœ๋ค ์ƒ‰์ƒ์„ ๊ตฌํ˜„ํ•  color-hash ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•œ๋‹ค.

 

 ์ฑ„ํŒ…๋ฐฉ ์Šคํ‚ค๋งˆ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

// room.js

const mongoose = require('mongoose');

const { Schema } = mongoose;
const roomSchema = new Schema({
  title: {		// ๋ฐฉ ์ œ๋ชฉ
    type: String,
    required: true,
  },
  max: {		// ์ธ์› ์ˆ˜
    type: Number,
    required: true,
    default: 10,	// ๊ธฐ๋ณธ ์ธ์›
    min: 2,			// ์ตœ๋Œ€ ์ธ์›
  },
  owner: {		// ๋ฐฉ์žฅ	
    type: String,
    required: true,
  },
  password: String,		// ๋ฌธ์ž์—ด
  createdAt: {		// ์ƒ์„ฑ ์‹œ๊ฐ„
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Room', roomSchema);

 

๊ทธ๋ฆฌ๊ณ  ์ฑ„ํŒ… ์Šคํ‚ค๋งˆ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

// chart.js

const mongoose = require('mongoose');

const { Schema } = mongoose;
const { Types: { ObjectId } } = Schema;
const chatSchema = new Schema({
  room: {
    type: ObjectId,
    required: true,
    ref: 'Room',
  },
  user: {
    type: String,
    required: true,
  },
  chat: String,
  gif: String,
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('Chat', chatSchema);

 

 

์•„๋ž˜๋Š” ๋ชฝ๊ณ ๋””๋น„๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ์ฝ”๋“œ์ด๋‹ค.

 

// schemas/index.js

const mongoose = require('mongoose');

const { MONGO_ID, MONGO_PASSWORD, NODE_ENV } = process.env;
const MONGO_URL = `mongodb://${MONGO_ID}:${MONGO_PASSWORD}@localhost:27017/admin`;

const connect = () => {
  if (NODE_ENV !== 'production') {
    mongoose.set('debug', true);
  }
  mongoose.connect(MONGO_URL, {
    dbName: 'gifchat',
    useNewUrlParser: true,
  }).then(() => {
    console.log("๋ชฝ๊ณ ๋””๋น„ ์—ฐ๊ฒฐ ์„ฑ๊ณต");
  }).catch((err) => {
    console.error("๋ชฝ๊ณ ๋””๋น„ ์—ฐ๊ฒฐ ์—๋Ÿฌ", err);
  });
};

mongoose.connection.on('error', (error) => {
  console.error('๋ชฝ๊ณ ๋””๋น„ ์—ฐ๊ฒฐ ์—๋Ÿฌ', error);
});
mongoose.connection.on('disconnected', () => {
  console.error('๋ชฝ๊ณ ๋””๋น„ ์—ฐ๊ฒฐ์ด ๋Š๊ฒผ์Šต๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.');
  connect();
});

module.exports = connect;

 

๊ทธ๋ฆฌ๊ณ  ํ™˜๊ฒฝ๋ณ€์ˆ˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์€๋ฐ, ๊ฐ์ž ๋งž์ถฐ์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์„ค์ •ํ•˜๋ฉด ๋œ๋‹ค

// .env

COOKIE_SECRET=gifchat
MONGO_ID=root
MONGO_PASSWORD=iamhungry

 

 

์„œ๋ฒ„์™€ ๋ชฝ๊ตฌ์Šค๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š”  app.js์ด๋‹ค. 

 

// app.js

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const ColorHash = require('color-hash').default;

dotenv.config();
const webSocket = require('./socket');
const indexRouter = require('./routes');
const connect = require('./schemas');

const app = express();
app.set('port', process.env.PORT || 8005);
app.set('view engine', 'html');
nunjucks.configure('views', {
  express: app,
  watch: true,
});
connect();

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(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);
    console.log(req.session.color, 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);

 

 

ํ™”๋ฉด์˜ ๋ ˆ์ด์•„์›ƒ์— ๋Œ€ํ•œ ํŒŒ์ผ๊ณผ ์—๋Ÿฌ๊ฐ€ ๋‚˜์˜ฌ ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•œ ํŒŒ์ผ, css ํŒŒ์ผ ๋“ฑ์— ๋Œ€ํ•œ ๋‚ด์šฉ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

// views.layout

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{{title}}</title>
  <link rel="stylesheet" href="/main.css">
</head>
<body>
{% block content %}
{% endblock %}
{% block script %}
{% endblock %}
</body>
</html>

 

// views/error.html

{% extends 'layout.html' %}

{% block content %}
  <h1>{{message}}</h1>
  <h2>{{error.status}}</h2>
  <pre>{{error.stack}}</pre>
{% endblock %}

 

// public/ main.css

* { box-sizing: border-box; }
.mine { text-align: right; }
.system { text-align: center; }
.mine img, .other img {
  max-width: 300px;
  display: inline-block;
  border: 1px solid silver;
  border-radius: 5px;
  padding: 2px 5px;
}
.mine div:first-child, .other div:first-child { font-size: 12px; }
.mine div:last-child, .other div:last-child {
  display: inline-block;
  border: 1px solid silver;
  border-radius: 5px;
  padding: 2px 5px;
  max-width: 300px;
}
#exit-btn { position: absolute; top: 20px; right: 20px; }
#chat-list { height: 500px; overflow: auto; padding: 5px; }
#chat-form { text-align: right; }
label[for='gif'], #chat, #chat-form [type='submit'] {
  display: inline-block;
  height: 30px;
  vertical-align: top;
}
label[for='gif'] { cursor: pointer; padding: 5px; }
#gif { display: none; }
table, table th, table td {
  text-align: center;
  border: 1px solid silver;
  border-collapse: collapse;
}

 

 

// views/main.html

{% extends 'layout.html' %}

{% block content %}
<h1>GIF ์ฑ„ํŒ…๋ฐฉ</h1>
<fieldset>
  <legend>์ฑ„ํŒ…๋ฐฉ ๋ชฉ๋ก</legend>
  <table>
    <thead>
    <tr>
      <th>๋ฐฉ ์ œ๋ชฉ</th>
      <th>์ข…๋ฅ˜</th>
      <th>ํ—ˆ์šฉ ์ธ์›</th>
      <th>๋ฐฉ์žฅ</th>
    </tr>
    </thead>
    <tbody>
    {% for room in rooms %}
      <tr data-id="{{room._id}}">
        <td>{{room.title}}</td>
        <td>{{'๋น„๋ฐ€๋ฐฉ' if room.password else '๊ณต๊ฐœ๋ฐฉ'}}</td>
        <td>{{room.max}}</td>
        <td style="color: {{room.owner}}">{{room.owner}}</td>
        <td>
          <button
            data-password="{{'true' if room.password else 'false'}}"
            data-id="{{room._id}}"
            class="join-btn"
          >์ž…์žฅ
          </button>
        </td>
      </tr>
    {% endfor %}
    </tbody>
  </table>
  <div class="error-message">{{error}}</div>
  <a href="/room">์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ</a>
</fieldset>
<script src="/socket.io/socket.io.js"></script>
<script>
  const socket = io.connect('http://localhost:8005/room', { // ๋„ค์ž„์ŠคํŽ˜์ด์Šค
    path: '/socket.io',
  });

  socket.on('newRoom', function (data) { // ์ƒˆ ๋ฐฉ ์ด๋ฒคํŠธ ์‹œ ์ƒˆ ๋ฐฉ ์ƒ์„ฑ
    const tr = document.createElement('tr');
    let td = document.createElement('td');
    td.textContent = data.title;
    tr.appendChild(td);
    td = document.createElement('td');
    td.textContent = data.password ? '๋น„๋ฐ€๋ฐฉ' : '๊ณต๊ฐœ๋ฐฉ';
    tr.appendChild(td);
    td = document.createElement('td');
    td.textContent = data.max;
    tr.appendChild(td);
    td = document.createElement('td');
    td.style.color = data.owner;
    td.textContent = data.owner;
    tr.appendChild(td);
    td = document.createElement('td');
    const button = document.createElement('button');
    button.textContent = '์ž…์žฅ';
    button.dataset.password = data.password ? 'true' : 'false';
    button.dataset.id = data._id; // ๋ฒ„ํŠผ์— ๋ฐฉ ์•„์ด๋”” ์ €์žฅ
    button.addEventListener('click', addBtnEvent);
    td.appendChild(button);
    tr.appendChild(td);
    tr.dataset.id = data._id; // tr์— ๋ฐฉ ์•„์ด๋”” ์ €์žฅ
    document.querySelector('table tbody').appendChild(tr); // ํ™”๋ฉด์— ์ถ”๊ฐ€
  });

  socket.on('removeRoom', function (data) { // ๋ฐฉ ์ œ๊ฑฐ ์ด๋ฒคํŠธ ์‹œ id๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๋ฐฉ ์ œ๊ฑฐ
    document.querySelectorAll('tbody tr').forEach(function (tr) {
      if (tr.dataset.id === data) {
        tr.parentNode.removeChild(tr);
      }
    });
  });

  function addBtnEvent(e) { // ๋ฐฉ ์ž…์žฅ ํด๋ฆญ ์‹œ
    if (e.target.dataset.password === 'true') { // ๋น„๋ฐ€๋ฐฉ์ด๋ฉด
      const password = prompt('๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”');
      location.href = '/room/' + e.target.dataset.id + '?password=' + password;
    } else {
      location.href = '/room/' + e.target.dataset.id;
    }
  }

  document.querySelectorAll('.join-btn').forEach(function (btn) {
    btn.addEventListener('click', addBtnEvent);
  });
</script>
{% endblock %}

{% block script %}
<script>
  window.onload = () => {
    if (new URL(location.href).searchParams.get('error')) {
      alert(new URL(location.href).searchParams.get('error'));
    }
  };
</script>
{% endblock %}

 

// views/room.html

{% extends 'layout.html' %}

{% block content %}
  <fieldset>
    <legend>์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ</legend>
    <form action="/room" method="post">
      <div>
        <input type="text" name="title" placeholder="๋ฐฉ ์ œ๋ชฉ">
      </div>
      <div>
        <input type="number" name="max" placeholder="์ˆ˜์šฉ ์ธ์›(์ตœ์†Œ 2๋ช…)" min="2" value="10">
      </div>
      <div>
        <input type="password" name="password" placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ(์—†์œผ๋ฉด ๊ณต๊ฐœ๋ฐฉ)">
      </div>
      <div>
        <button type="submit">์ƒ์„ฑ</button>
      </div>
    </form>
  </fieldset>
{% endblock %}

 

 

// views/chat.html

{% extends 'layout.html' %}

{% block content %}
  <h1>{{title}}</h1>
  <a href="/" id="exit-btn">๋ฐฉ ๋‚˜๊ฐ€๊ธฐ</a>
  <fieldset>
    <legend>์ฑ„ํŒ… ๋‚ด์šฉ</legend>
    <div id="chat-list">
      {% for chat in chats %}
        {% if chat.user === user %}
          <div class="mine" style="color: {{chat.user}}">
            <div>{{chat.user}}</div>
            {% if chat.gif %}}
              <img src="/gif/{{chat.gif}}">
            {% else %}
              <div>{{chat.chat}}</div>
            {% endif %}
          </div>
        {% elif chat.user === 'system' %}
          <div class="system">
            <div>{{chat.chat}}</div>
          </div>
        {% else %}
          <div class="other" style="color: {{chat.user}}">
            <div>{{chat.user}}</div>
            {% if chat.gif %}
              <img src="/gif/{{chat.gif}}">
            {% else %}
              <div>{{chat.chat}}</div>
            {% endif %}
          </div>
        {% endif %}
      {% endfor %}
    </div>
  </fieldset>
  <form action="/chat" id="chat-form" method="post" enctype="multipart/form-data">
    <label for="gif">GIF ์˜ฌ๋ฆฌ๊ธฐ</label>
    <input type="file" id="gif" name="gif" accept="image/gif">
    <input type="text" id="chat" name="chat">
    <button type="submit">์ „์†ก</button>
  </form>
  <script src="/socket.io/socket.io.js"></script>
  <script>
    const socket = io.connect('http://localhost:8005/chat', {
      path: '/socket.io',
    });
    socket.emit('join', new URL(location).pathname.split('/').at(-1));
    socket.on('join', function (data) {
      const div = document.createElement('div');
      div.classList.add('system');
      const chat = document.createElement('div');
      chat.textContent = data.chat;
      div.appendChild(chat);
      document.querySelector('#chat-list').appendChild(div);
    });
    socket.on('exit', function (data) {
      const div = document.createElement('div');
      div.classList.add('system');
      const chat = document.createElement('div');
      chat.textContent = data.chat;
      div.appendChild(chat);
      document.querySelector('#chat-list').appendChild(div);
    });
  </script>
{% endblock %}

 

 

์ดํ›„ ์†Œ์ผ“์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

app.set('io', io)๋กœ ๋ผ์šฐํ„ฐ์—์„œ io ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ €์žฅํ•ด ๋‘”๋‹ค.

์ดํ›„ rep.app.get('io')๋กœ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.

 

of๋ฉ”์„œ๋“œ๋Š” Socket.IO์— ๋„ค์ž„ ์ŠคํŽ˜์ด์Šค๋ฅผ ๋ถ€์—ฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋กœ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ‘๊ทผํ•˜๋Š” ๊ณณ์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ๋งŒ๋“ค์–ด ์ ‘์†ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ ๋‹ค. 

๋„ค์ž„์ŠคํŽ˜์ด์Šค๋Š” ์„œ๋กœ ๊ฐ™์€ ๊ณณ๋ผ๋ฆฌ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.

ํ˜„์žฌ๋Š” /room, /chat ๋‘ ๊ฐ€์ง€์ด๋‹ค.

 

/room ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๋ฅผ ๋ถ™์ธ ์ฝ”๋“œ๊ฐ€ ์œ„์™€ ๊ฐ™๋‹ค. 

connection ์ด๋ฒคํŠธ๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์—ฐ๊ฒฐ ์‹œ ๋ฐœ์ƒํ•œ๋‹ค. 

disconnect ์ด๋ฒคํŠธ๋Š” ์—ฐ๊ฒฐ ํ•ด์ œ ์‹œ ๋ฐœ์ƒํ•œ๋‹ค.

 

/chat ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๋ถ™์ธ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ณ  connection๊ณผ disconnect๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋งŒ๋“  ์ด๋ฒคํŠธ์ด๋‹ค.

 

room ๋ฐฉ:

socket.io์— ์ด๋Š” ๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ณด๋‹ค ๋” ์„ธ๋ถ€์ ์ธ ๊ฐœ๋…์ด๋‹ค. 

๊ฐ™์€ ๋„ค์ž„์ŠคํŽ˜์Šค ์•ˆ์—์„œ๋„ ๊ฐ™์€ ๋ฐฉ์— ๋“ค์–ด ์žˆ๋Š” ์†Œ์ผ“๋ผ๋ฆฌ๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

socket.emit('join', ๋ฐฉ์•„์ด๋””)  ์ด๋ฅผ ํ†ตํ•ด์„œ join์ด๋ฒคํŠธ์—์„œ data ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ๋ฐฉ ์•„์ด๋””๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ๋ฐฉ์— ์ ‘์†ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

์•„๋ž˜๋Š” index.jsํŒŒ์ผ๋กœ, ๋ผ์šฐํ„ฐ์™€ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ ๋‹ค.

// rountes/index.js

const express = require('express');
const { renderMain, renderRoom, createRoom, enterRoom, removeRoom } = require('../controllers');

const router = express.Router();

router.get('/', renderMain);

router.get('/room', renderRoom);

router.post('/room', createRoom);

router.get('/room/:id', enterRoom);

router.delete('/room/:id', removeRoom);

module.exports = router;

 

 

๊ทธ๋ฆฌ๊ณ  ๋ชฝ๊ณ ๋””๋น„์™€ ์›น ์†Œ์ผ“ ๋ชจ๋‘ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

createToom ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ฑ„ํŒ…๋ฐฉ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๊ณ , GET / ๋ผ์šฐํ„ฐ์— ์ ‘์†ํ•œ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ์ฑ„ํŒ…๋ฐฉ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค. 

 

enterToom ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ฑ„ํŒ…๋ฐฉ์— ์ ‘์†ํ•ด ์ฑ„ํŒ…๋ฐฉ ํ™”๋ฉด์„ ๋ Œ๋”๋ง ํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ด๋‹ค.  

if ๋ฌธ์œผ๋กœ ๋น„๋ฐ€๋ฐฉ์˜ ์—ฌ๋ถ€, ๋žœ๋”๋ง ์ด์ „์— ๋ฐฉ์˜ ์กด์žฌ ์—ฌ๋ถ€, ํ—ˆ์šฉ ์ธ์› ์ดˆ๊ณผ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•œ๋‹ค. 

 

removeRoom ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์ฑ„ํŒ…๋ฐฉ์„ ์‚ญ์ œํ•˜๋Š” ๊ฒƒ์œผ๋กœ, ๋ฐฉ๊ณผ ๋‚ด์—ญ์„ ๋ชจ๋‘ ์‚ญ์ œํ•œ๋‹ค. 

 

// controllers/index.js

const Room = require('../schemas/room');
const Chat = require('../schemas/chat');

exports.renderMain = async (req, res, next) => {
  try {
    const rooms = await Room.find({});
    res.render('main', { rooms, title: 'GIF ์ฑ„ํŒ…๋ฐฉ' });
  } catch (error) {
    console.error(error);
    next(error);
  }
};

exports.renderRoom = (req, res) => {
  res.render('room', { title: 'GIF ์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ' });
};

exports.createRoom = async (req, res, next) => {
  try {
    const newRoom = await Room.create({
      title: req.body.title,
      max: req.body.max,
      owner: req.session.color,
      password: req.body.password,
    });
    const io = req.app.get('io');
    io.of('/room').emit('newRoom', newRoom);
    if (req.body.password) { // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ๋Š” ๋ฐฉ์ด๋ฉด
      res.redirect(`/room/${newRoom._id}?password=${req.body.password}`);
    } else {
      res.redirect(`/room/${newRoom._id}`);
    }
  } catch (error) {
    console.error(error);
    next(error);
  }
};

exports.enterRoom = async (req, res, next) => {
  try {
    const room = await Room.findOne({ _id: req.params.id });
    if (!room) {
      return res.redirect('/?error=์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์ž…๋‹ˆ๋‹ค.');
    }
    if (room.password && room.password !== req.query.password) {
      return res.redirect('/?error=๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.');
    }
    const io = req.app.get('io');
    const { rooms } = io.of('/chat').adapter;
    console.log(rooms, rooms.get(req.params.id), rooms.get(req.params.id));
    if (room.max <= rooms.get(req.params.id)?.size) {
      return res.redirect('/?error=ํ—ˆ์šฉ ์ธ์›์ด ์ดˆ๊ณผํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
    }
    return res.render('chat', {
      room,
      title: room.title,
      chats: [],
      user: req.session.color,
    });
  } catch (error) {
    console.error(error);
    return next(error);
  }
};

exports.removeRoom = async (req, res, next) => {
  try {
    await Room.deleteOne({ _id: req.params.id });
    await Chat.deleteMany({ room: req.params.id });
    res.send('ok');
  } catch (error) {
    console.error(error);
    next(error);
  }
};

 

 

ํ”„๋กœ์ ํŠธ ์‹คํ–‰์— ์•ž์„œ์„œ ๋ชฝ๊ณ ๋””๋น„๋ฅผ ๋จผ์ € ์‹คํ–‰ํ•ด์•ผ ํ•œ๋‹ค. 

์ดํ›„ ์„œ๋ฒ„ ์‹คํ–‰ ํ›„ ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‘ ๊ฐœ ๋„์šฐ๋ฉด ๋‘ ๋ช…์ด ์ ‘์†ํ•œ ๋“ฏํ•œ ๋ชจ์Šต์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

 

 

 

12.5 ๋ฏธ๋“ค์›จ์–ด์™€ ์†Œ์ผ“ ์—ฐ๊ฒฐํ•˜๊ธฐ

 

๋ฐฉ์—์„œ ์ž…์žฅํ•  ๋•Œ์™€ ํ‡ด์žฅํ•  ๋•Œ ๋ˆ„๊ฐ€ ์ž…์žฅํ•˜๊ณ  ๋ˆ„๊ฐ€ ํ‡ด์žฅํ–ˆ๋Š”์ง€ ์•Œ๋ฆฌ๋Š” ์‹œ์Šคํ…œ ์•Œ๋ฆผ์„ ๋งŒ๋“ ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ฑ„ํŒ…๋ฐฉ ์ ‘์†์ž ์ˆ˜๊ฐ€ 0๋ช…์ผ ๋•Œ ๋ฐฉ์„ ์ œ๊ฑฐํ•˜๋Š” ์ฝ”๋“œ๋„ ๊ฐ™์ด ๋„ฃ๋Š”๋‹ค.

 

์ด์— ๋Œ€ํ•œ app.js์™€ socket.js๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

 

// app.js

const express = require('express');
const path = require('path');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');
const ColorHash = require('color-hash').default;

dotenv.config();
const webSocket = require('./socket');
const indexRouter = require('./routes');
const connect = require('./schemas');

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((req, res, next) => {
  if (!req.session.color) {
    const colorHash = new ColorHash();
    req.session.color = colorHash.hex(req.sessionID);
    console.log(req.session.color, 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);

 

 

// socket.io

const SocketIO = require('socket.io');
const { removeRoom } = require('./services');

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');

  const wrap = middleware => (socket, next) => middleware(socket.request, {}, next);
  chat.use(wrap(sessionMiddleware));

  room.on('connection', (socket) => {
    console.log('room ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ์ ‘์†');
    socket.on('disconnect', () => {
      console.log('room ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ ‘์† ํ•ด์ œ');
    });
  });

  chat.on('connection', (socket) => {
    console.log('chat ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ์ ‘์†');

    socket.on('join', (data) => {
      socket.join(data);
      socket.to(data).emit('join', {
        user: 'system',
        chat: `${socket.request.session.color}๋‹˜์ด ์ž…์žฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.`,
      });
    });

    socket.on('disconnect', async () => {
      console.log('chat ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ ‘์† ํ•ด์ œ');
      const { referer } = socket.request.headers; // ๋ธŒ๋ผ์šฐ์ € ์ฃผ์†Œ๊ฐ€ ๋“ค์–ด์žˆ์Œ
      const roomId = new URL(referer).pathname.split('/').at(-1);
      const currentRoom = chat.adapter.rooms.get(roomId);
      const userCount = currentRoom?.size || 0;
      if (userCount === 0) { // ์œ ์ €๊ฐ€ 0๋ช…์ด๋ฉด ๋ฐฉ ์‚ญ์ œ
        await removeRoom(roomId); // ์ปจํŠธ๋กค๋Ÿฌ ๋Œ€์‹  ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉ
        room.emit('removeRoom', roomId);
        console.log('๋ฐฉ ์ œ๊ฑฐ ์š”์ฒญ ์„ฑ๊ณต');
      } else {
        socket.to(roomId).emit('exit', {
          user: 'system',
          chat: `${socket.request.session.color}๋‹˜์ด ํ‡ด์žฅํ•˜์…จ์Šต๋‹ˆ๋‹ค.`,
        });
      }
    });
  });
};

 

 

 

remove ์„œ๋น„์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๋ฐ์— ์žˆ์–ด์„œ ์•„๋ž˜์˜ index์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๊ณ , removeRoom ์„œ๋น„์Šค๋ฅผ ์ปจํŠธ๋กคํ•˜๋Š” index๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

 

// service/index.js

const Room = require('../schemas/room');
const Chat = require('../schemas/chat');

exports.removeRoom = async (roomId) => {
  try {
    await Room.deleteOne({ _id: roomId });
    await Chat.deleteMany({ room: roomId });
  } catch (error) {
    throw error;
  }
};

 

// controllers/index.js

const Room = require('../schemas/room');
const Chat = require('../schemas/chat');
const { removeRoom: removeRoomService } = require('../services'); 

exports.renderMain = async (req, res, next) => {
  try {
    const rooms = await Room.find({});
    res.render('main', { rooms, title: 'GIF ์ฑ„ํŒ…๋ฐฉ' });
  } catch (error) {
    console.error(error);
    next(error);
  }
};

exports.renderRoom = (req, res) => {
  res.render('room', { title: 'GIF ์ฑ„ํŒ…๋ฐฉ ์ƒ์„ฑ' });
};

exports.createRoom = async (req, res, next) => {
  try {
    const newRoom = await Room.create({
      title: req.body.title,
      max: req.body.max,
      owner: req.session.color,
      password: req.body.password,
    });
    const io = req.app.get('io');
    io.of('/room').emit('newRoom', newRoom);
    if (req.body.password) { // ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ๋Š” ๋ฐฉ์ด๋ฉด
      res.redirect(`/room/${newRoom._id}?password=${req.body.password}`);
    } else {
      res.redirect(`/room/${newRoom._id}`);
    }
  } catch (error) {
    console.error(error);
    next(error);
  }
};

exports.enterRoom = async (req, res, next) => {
  try {
    const room = await Room.findOne({ _id: req.params.id });
    if (!room) {
      return res.redirect('/?error=์กด์žฌํ•˜์ง€ ์•Š๋Š” ๋ฐฉ์ž…๋‹ˆ๋‹ค.');
    }
    if (room.password && room.password !== req.query.password) {
      return res.redirect('/?error=๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ ธ์Šต๋‹ˆ๋‹ค.');
    }
    const io = req.app.get('io');
    const { rooms } = io.of('/chat').adapter;
    console.log(rooms, rooms.get(req.params.id), rooms.get(req.params.id));
    if (room.max <= rooms.get(req.params.id)?.size) {
      return res.redirect('/?error=ํ—ˆ์šฉ ์ธ์›์ด ์ดˆ๊ณผํ•˜์˜€์Šต๋‹ˆ๋‹ค.');
    }
    return res.render('chat', {
      room,
      title: room.title,
      chats: [],
      user: req.session.color,
    });
  } catch (error) {
    console.error(error);
    return next(error);
  }
};

exports.removeRoom = async (req, res, next) => {
  try {
    await removeRoomService(req.params.id);
    res.send('ok');
  } catch (error) {
    console.error(error);
    next(error);
  }
};

 

 

 

์ดํ›„ ๊ต์žฌ๋ฅผ ํ†ตํ•ด์„œ ์ฑ„ํŒ…์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

 


Node.js #2 

Editor : ๋ผ๋งˆ

728x90

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ