인프런 커뮤니티 질문&답변

cumulonimbus69님의 프로필 이미지
cumulonimbus69

작성한 질문수

[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지

실시간으로 텍스트, GIF 보내기

실시간 채팅방에서 GIF uploads 하면 GIF가 바로 화면에 보이지 않고 새로 고침을 해야 보이는데 어떻게 해야 할까요?

작성

·

110

0

실시간 채팅방 강좌 코드를 작성하여 작동 시켜 본 결과 메시지 전송 까지는 잘 되는 것을 확인 하였는데 GIF 업로드 시 다음 그림과 같은 현상이 발생하고 있습니다

그림 하단에 표시한 부분 처럼 처음에 GIF 올리기를 하면 그림이 보이지 않다가 새로 고침을 하면 위의 다른 GIF 처럼 잘 보이긴 하는데 무슨 문제 일까요?

참고로 관련 코드를 같이 올립니다

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="https://unpkg.com/axios/dist/axios.min.js"></script>
  <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');
      div.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');
      div.textContent = data.chat;
      div.appendChild(chat);
      document.querySelector('#chat-list').appendChild(div);
    });
    socket.on('chat', function (data) {
      const div = document.createElement('div');
      if (data.user === '{{user}}') {
        div.classList.add('mine');
      } else {
        div.classList.add('other');
      }
      const name = document.createElement('div');
      name.textContent = data.user;
      div.appendChild(name);
      if (data.chat){
        const chat = document.createElement('div');
        chat.textContent = data.chat;
        div.appendChild(chat);
      } else {
        const gif = document.createElement('img');
        gif.sr = '/gif/' + data.gif;
        div.appendChild(gif);
      }
      div.style.color = data.user;
      document.querySelector('#chat-list').appendChild(div);
    });
    
    document.querySelector('#chat-form').addEventListener('submit', function (e) {
      e.preventDefault();
      if (e.target.chat.value) {
        axios.post('/room/{{room._id}}/chat', {
          chat: this.chat.value, 
        })
            .then( () => {
              e.target.chat.value = '';
            })
            .catch( (err) => {
              console.error(err);
            });
      }
    });

    document.querySelector('#gif').addEventListener('change', function (e) {
      console.log('******',e.target.files);
      const formData = new FormData();
        formData.append('gif', e.target.files[0]);
        axios.post('/room/{{room._id}}/gif', formData)
           .then( () => {
              e.target.file = null;
            })
            .catch( (err) => {
              console.error(err);
            });   
    });
  </script>
{% endblock %}

routes/index.js

const express = require('express');
const { renderMain, renderRoom, createRoom, enterRoom, removeRoom, sendChat, sendGif } = require('../controllers');
const multer = require('multer');
const fs = require('fs');
const path = require('path');

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);
router.post('/room/:id/chat', sendChat);
try {fs.readdirSync('uploads');
} catch (err) {
  console.error('uploads 폴더가 없어 uploads 폴더를 생성합니다.');
  fs.mkdirSync('uploads');
}
const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, 'uploads/');
    },
    filename(req, file, done ) {
      const ext = path.extname(file.originalname);
      done(null, path.basename(file.originalname, ext) + Date.now() + ext);
    },
  }),
  limits: {fileSize: 5 * 1024 *1024 },
})
router.post('/room/:id/gif', upload.single('gif'), sendGif);

module.exports = router;

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, next ) => {
  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, //session data 에서 옮
      });
      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;
    if (room.max <= rooms.get(req.params.id)?.size) {
      return res.redirect('/?error=허용 인원을 초과하였습니다.');
    }
    const chats = await Chat.find({room: room._id }).sort('createdAt');
    res.render('chat', { title: 'GIF 채팅방 생성', chats , room, user: req.session.color });
  } catch (error) {
    console.error(error);
    next(error);
  }
};
exports.removeRoom = async ( req, res, next ) => {
  try { 
    await removeRoomService(req.params.id );
    res.send('ok');
    setTimeout(() => {
      req.app.get('io').of('/room').emit('removeRoom', req.params.id);
    }, 2000) 
  } catch (error) {
    console.error(error);
    next(error);
  }
};

exports.sendChat = async (req, res, next ) =>{
  try {
    const chat = await Chat.create({
      room: req.params.id,
      user: req.session.color,
      chat: req.body.chat,
    });
    req.app.get('io').of('/chat').to(req.params.id).emit('chat', chat);
    res.send('ok');
  } catch( error ){
    console.error(error);
    next(error);
  }
}
  
exports.sendGif = async (req, res, next ) => {
  try {
    const chat = await Chat.create({
      room : req.params.id,
      user: req.session.color,
      gif: req.file.filename,
    })
    setTimeout(() => {
      req.app.get('io').of('/chat').to(req.params.id).emit('chat',chat);
    }, 1000);
    res.send('ok');
  } catch (error) {
    console.error(error);
    next(error);
  }
}

[제로초 강좌 질문 필독 사항입니다]
질문에는 여러분에게 도움이 되는 질문과 도움이 되지 않는 질문이 있습니다.
도움이 되는 질문을 하는 방법을 알려드립니다.

https://www.youtube.com/watch?v=PUKOWrOuC0c

0. 숫자 0부터 시작한 이유는 1보다 더 중요한 것이기 때문입니다. 에러가 났을 때 해결을 하는 게 중요한 게 아닙니다. 왜 여러분은 해결을 못 하고 저는 해결을 하는지, 어디서 힌트를 얻은 것이고 어떻게 해결한 건지 그걸 알아가셔야 합니다. 그렇지 못한 질문은 무의미한 질문입니다.
1. 에러 메시지를 올리기 전에 반드시 스스로 번역을 해야 합니다. 번역기 요즘 잘 되어 있습니다. 에러 메시지가 에러 해결 단서의 90%를 차지합니다. 한글로 번역만 해도 대부분 풀립니다. 그냥 에러메시지를 올리고(심지어 안 올리는 분도 있습니다. 저는 독심술사가 아닙니다) 해결해달라고 하시면 아무런 도움이 안 됩니다.
2. 에러 메시지를 잘라서 올리지 않아야 합니다. 입문자일수록 에러메시지에서 어떤 부분이 가장 중요한 부분인지 모르실 겁니다. 그러니 통째로 올리셔야 합니다.
3. 코드도 같이 올려주세요. 다만 코드 전체를 다 올리거나, 깃헙 주소만 띡 던지지는 마세요. 여러분이 "가장" 의심스럽다고 생각하는 코드를 올려주세요.
4. 이 강좌를 바탕으로 여러분이 응용을 해보다가 막히는 부분, 여러 개의 선택지 중에서 조언이 필요한 부분, 제 경험이 궁금한 부분에 대한 질문은 대환영입니다. 다만 여러분의 회사 일은 질문하지 마세요.
5. 강좌 하나 끝날 때마다 남의 질문들을 읽어보세요. 여러분이 곧 만나게 될 에러들입니다.
6. 위에 적은 내용을 명심하지 않으시면 백날 강좌를 봐도(제 강좌가 아니더라도) 실력이 늘지 않고 그냥 코딩쇼 관람 및 한컴타자연습을 한 셈이 될 겁니다.

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

저거 네트워크 탭 열어서 websocket 요청 클릭해서 메시지 확인해봐야 할 것 같습니다.

/gif/ + data.gif 경로가 올바른 경로가 맞는지(express.static이 가리키는) 확인이 필요합니다.

문제 해결 되었습니다

강사님 말씀하신 부분 참고해서 찾아보니 chat.html 부분에 "gif.src" 를 "gif.sr"로 오타가 있었습니다

수정하니 올바로 작동 하였습니다

 그런데 질문이 하나 있습니다

gif.src에 데이터가 잘못 들어가 GIF가 바로 안 보인 것인데 새로 고침을 하면 보이는 것은 어떤 이유 일까요?

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

아아 거기였군요. 실시간으로 gif 표시하는 건 자바스크립트(오타) 부분이고, 새로고침 시 초기 화면 그리는 건 nunjucks가 하고 있어서 그렇습니다.

cumulonimbus69님의 프로필 이미지
cumulonimbus69

작성한 질문수

질문하기