• 카테고리

    질문 & 답변
  • 세부 분야

    풀스택

  • 해결 여부

    미해결

세션ID가 변합니다

22.06.08 00:13 작성 조회수 851

0

 

다른 네임스페이스에 접속하면 세션ID가 변합니다. 어떻게 해결해야 하나요?

axios나 cookie-signature같은 건 교과서보면서 그대로 따라 했습니다.

const signedCookie = cookie.sign( req.signedCookies['connect.sid'], process.env.COOKIE_SECRET );
    const connectSID = `${signedCookie}`;
    console.log('연결ID :', connectSID);
    axios.post(`http://localhost:8005/room/${roomId}/join`, {
      headers: {
        Cookie: `connect.sid=s%3A${connectSID}`
      }
  })
app.use((req, res, next) => {
    if (!req.session.color) {
      const colorHash = new ColorHash();
      req.session.color = colorHash.hex(req.sessionID);
    }
    console.log('세션ID: ', req.sessionID);
    console.log('유저ID: ', req.session.color);
    next();
});

이상한점은 채팅을 칠 때에는

처음 받은 세션으로 등록됩니다.

입장과 퇴장 시에만 세션이 변경됩니다

 

답변 1

답변을 작성해보세요.

0

세션 ID 위에 항상 연결ID가 뜨도록 코드를 수정해서 로깅해보세요.

app.use 부분에서 추가하시면 됩니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

app.use((req, res, next) => {
    if (!req.session.color) {
      const colorHash = new ColorHash();
      req.session.color = colorHash.hex(req.sessionID);
    }
    console.log('연결ID :', cookie.sign( req.signedCookies['connect.sid'], process.env.COOKIE_SECRET ));
    console.log('세션ID: ', req.sessionID);
    console.log('유저ID: ', req.session.color);
    next();
});

이렇게 수정해서 로깅해보니

이런 에러가 뜹니다.

cookie-signature에서 에러가 나는것 같아 빼고 해봤습니다.어차피 뒤에 있는 곳은 비밀키 부분이라 큰 상관은 없어 보였습니다.

app.use((req, res, next) => {
    if (!req.session.color) {
      const colorHash = new ColorHash();
      req.session.color = colorHash.hex(req.sessionID); // req.session.color에 색을 넣는다
    }
    console.log('연결IDapp :', req.signedCookies['connect.sid']);
    console.log('세션ID: ', req.sessionID);
    console.log('유저ID: ', req.session.color);
    next();
});

이렇게 코딩하고 다시 해보니

이런 결과가 나왔습니다.

정의할수 없다고 뜹니다

 

코드 순서가 헷갈리네요. 어딘가에서 cookie가 유실되었습니다. 또는 올바르지 않은 쿠키 형식이라 req.signedCookies가 생성되고 있지 않은 겁니다.

참고로 12장은 제 책에서 제가 최악의 실수를 저지른 장입니다. ㅎㅎ

앞으로 개정할 예정인데 그에 따른 코드는

https://github.com/ZeroCho/nodejs-book/tree/3pan/ch12/12.7/gif-chat

여기 있습니다. 지금 방 join하는 부분을 커스텀하게 만드시는 것 같은데 그렇다면 그냥 여기 따라하세요.

훨씬 간단하게 할 수 있습니다. cookie.sign 이런 것도 필요없고요. (브라우저 html 부분도 달라졌으니 주의하세요)

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

코드를 쭉 봤는데 궁금한점이 생겨 질문합니다

 

원래 코드에서 socket.js부분에 axios를 붙여 delete메서드를 사용했는데 새로운 코드에서는 axios를 사용하지 않더군요 

delete는 어떻게 처리된건가요?

그리고 제가 스스로 해보기에서 시스템 메시지를 db에 저장하기 위해 라우터를 새로 만들었습니다.

방ID를 받으면 그것을 axios를 통해 라우터에서 웹소켓 이벤트를 실행시켰는데 axios가 없으니 어떻게 해야 하나요?

새로운 chat.html에 join이벤트로 방ID를 보내던데 이것을 브라우저에서 바로 라우터로 가는 코드로 바꿔야 할까요?

애초에 axios 통해서 같은 서버로 요청을 보내는 게 이상한 것이었습니다. 그냥 socket.js에서 방 제거하는 로직을 호출하면 되었거든요.

시스템 메시지를 db에 저장하는것도 라우터가 아니라 그냥 socket.js에서 호출하면 됩니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

socket.js에서 db를 호출하지 않기 위해 따로 index.js를 만든 것이 아닌가요?

 

removeRoom모듈만 따로 services폴더에 담아서 호출했는데 이유가 있나요?

controllers폴더에서 db를 Room만 불러오던데 이것과 관련이 있는 것인가요?

스프링 구조를 보시면 라우터 역할인 http 컨트롤러(req, res를 받음)와 서비스(비즈니스 로직, req, res를 모름)으로 보통 분리해서 구현합니다.

controllers(익스프레스 라우터)는 req, res와 긴밀하게 연결되어있습니다. 그런데 웹소켓은 req, res가 없으므로 컨트롤러를 사용하면 안 됩니다. 따라서 req, res와 무관한 서비스로 비즈니스로직을 분리한 뒤 웹소켓에서는 서비스를 사용합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

시스템 메시지를 db에 저장하는 로직은 req, res가 필요한 로직이기에 컨트롤러에 넣었습니다.

그렇다면 socket.js에서 require로 두 모듈을 호출하면 되는 것인가요?

시스템 메시지를 db에 저장하는 게 왜 req, res가 필요한가요? 서비스로도 충분히 됩니다.

await join(req.session.color);
await exit(req.session.color);
이런 식으로 하면 join, exit 서비스는 req를 몰라도 됩니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

이렇게 코드를 짜봤습니다.

exports.join = async (data, user) => {
  try {
  await Chat.save({
      room: data,
      user: 'system',
      chat: `${user}님이 입장하셨습니다.`,
    });
  } catch (error) {
    throw error;
  }
};

exports.exit = async (roomId, user) => {
  try {
    await Chat.save({
        room: roomId,
        user: 'system',
        chat: `${user}님이 퇴장하셨습니다.`,
      });
    await sys.save();
  } catch (error) {
    throw error;
  }
};

서비스폴더 안에 이렇게 join과 exit를 만들었습니다

chat.on('connection', (socket) => {
    console.log('chat 네임스페이스에 접속');

    socket.on('join', (data) => {
      socket.join(data);
      join(data, 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 = io.of('/chat').adapter.rooms.get(roomId);
      const userCount = currentRoom?.size || 0;
      if (userCount === 0) { // 유저가 0명이면 방 삭제
        await removeRoom(roomId); // 컨트롤러 대신 서비스를 사용
        room.emit('removeRoom', roomId);
        console.log('방 제거 요청 성공');
      } else {
        await exit(roomId, socket.request.session.color)
      }
    });
});

브라우저에서 이벤트가 오면 join과 exit에 방ID와 req.session.color를 인수로 넣어 보냈습니다

exports.joinRoom = async (req, res, next) => {
    try {
      await join(req.params.id, req.session.color);
      req.app.get('io').of('/chat').to(req.params.id).emit('join', {
        user: 'system',
        chat: `${req.session.color}님이 입장하셨습니다.`,
        count: req.app.get('io').of('/chat').to(req.params.id).adapter.rooms[req.params.id].length,
      })
      res.send('ok');
    } catch (error) {
      console.error(error);
      next(error);
    }
};

exports.exitRoom = async (req, res, next) => {
    try {
      await exit(req.params.id, req.session.color);
      req.app.get('io').of('/chat').to(req.params.id).emit('exit', {
        user: 'system',
        chat: `${req.session.color}님이 입장하셨습니다.`,
        count: req.app.get('io').of('/chat').to(req.params.id).adapter.rooms[req.params.id].length,
      })
      res.send('ok');
    } catch (error) {
      console.error(error);
      next(error);
    }
};

컨트롤러에는 이렇게 짜봤습니다.

 

인수로 req.session.color만 넣기엔 roomID도 필요해서 인수를 2개 넣었습니다.

그리도 테스트해보니

이런 에러가 발생합니다.

저 부분은 좀 의외네요. rooms에는 항상 get메서드가 있을텐데..

chat.adapter.rooms.get(...으로 바꿔보세요.

아, socket.io 옛날버전이시군요. 그러면 rooms[방아이디] 하셔야합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

알려주신대로 수정후 다시 해보니

이러한 에러가 발생했습니다.

exports.join = async (data, user) => {
  try {
  const sys = new Chat({
      room: data,
      user: 'system',
      chat: `${user}님이 입장하셨습니다.`,
    });
  await sys.save();
  } catch (error) {
    throw error;
  }
};

exports.exit = async (roomId, user) => {
  try {
    const sys = new Chat({
        room: roomId,
        user: 'system',
        chat: `${user}님이 퇴장하셨습니다.`,
      });
    await sys.save();
  } catch (error) {
    throw error;
  }
};

이렇게 고쳐서 다시 해봤습니다.

문제점이 2가지 생겼습니다.

첫번째는 방 나가기를 누르면

해당 에러가 발생합니다

또 한가지는 시스템 메세지가 다르게 올라가는 것입니다.

 

 

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.08

이건 아래 코드의 결과입니다.

console.log('어뎁터:',io.of('/chat').adapter);

rooms[roomId]입니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

감사합니다. 그러나 다른 문제가 있습니다.

socket.on('disconnect', async () => {
      console.log('chat 네임스페이스 접속 해제');
      const { referer } = socket.request.headers; // 브라우저 주소가 들어있음
      const roomId = new URL(referer).pathname.split('/').at(-1);
      const currentRoom = io.of('/chat').adapter.rooms[roomId];
      const userCount = currentRoom?.size || 0;

      if (userCount === 0) { // 유저가 0명이면 방 삭제
        await removeRoom(roomId); // 컨트롤러 대신 서비스를 사용
        room.emit('removeRoom', roomId);
        console.log('방 제거 요청 성공');
      } else {
        await exit(roomId, socket.request.session.color)
      }
  });

이 부분에서 문제가 생긴것 같으나 어떻게 해결해야할지 모르겠습니다.

문제가 여려개 생겼습니다

가장 큰 문제는 유저가 남아있음에도 방을 나가면 if문 안의 내용이 실행됩니다.

네임스페이스를 확인해봐도 왜 이런 문제가 생겼는지 감이 안잡힙니다.

 

 

지금 socket.io 2버전과 4버전의 소스코드를 혼용하셔서 그렇습니다. 이 강좌는 2버전인데 깃헙 코드는 4버전입니다. 2버전에서는 currentRoom.size가 아니라 currentRoom.length입니다. 

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

저도 length로 바꿔서 해봤지만 비슷한 오류가 뜹니다

한가지 달라진 점은 이번엔 인원이 0명일 때 이러한 오류가 발생합니다

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

다음은 chat네임스페이스의 내용입니다

처음 방을 생성했을 때의 데이터입니다

다음은 다른 사용자가 방에 들어갔을 때의 데이터입니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

이제 로직은 구현이 됩니다.

그러나 웹소켓쪽에서 반응이 조금 느립니다.

실시간으로 반응해야 하는데 새로고침을 눌러야 그제서야 반응합니다.

또 새로고침을 누르면 방에서 퇴장과 입장이 실행됩니다.

이런 에러도 뜹니다

저렇게 에러메시지가 뜨면 36줄22칸이 뭔지를 봐보세요.

반응이 느린것이랑 새로고침해야 뜨는 것은 차원이 다른 문제입니다.

새로고침을 하면 소켓연결이 끊어져서 방에서 입장과 퇴장이 됩니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

저 에러 메세지와 반응이 느린것은 별개의 문제인것 같습니다.

const socket = io.connect('http://localhost:8005/room', {
    path: '/socket.io',
});

이 코드에 

transports: ['websocket']을 붙이면 해당 에러가 뜹니다.

현재는 지워서 뜨지 않습니다.

참고호 해당 코드는

const { referer } = socket.request.headers; // 브라우저 주소가 들어있음
const roomId = new URL(referer).pathname.split('/').at(-1);

이 코드 입니다.

 

다만 반응이 한박자 느린것은 어떻게 해결해야 할지 모르겠습니다.

채팅과 로그는 정상적으로 뜨지만 system메세지와 인원수만 새로고침을 해야 뜹니다

 

 

 

저 부분은 콘솔로그 referer로 로깅해보세요. 주소가 invalid한 주소같습니다. system메시지가 안 뜨는 것은 웹소켓으로 방에 제대로 emit되지 않아서 그런 것이라 그 부분 다시 확인하셔야 합니다. 근데 저 에러때문에 다음 줄이 실행 안되서 발생하는 게 맞는 것 같습니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

컨트롤러 실행이 어떻게 되는지 궁금합니다

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

router.post('/room/:id', joinRoom);

router.post('/room/:id', exitRoom);

이 3개의 라우터는 어느 코드에서 실행되는지 모르겠습니다.

(밑에 2개는 제가 커스텀한것 입니다...)

이 쪽 웹소켓 부분이 제대로 emit되지 않는다는 것을 알았지만 어떻게 호출해야 할지 고민입니다.

브라우저에 axios를 활용해야 할까요?

아니면 따로 라우터를 만들지 않고

socket.on('join', (data) => {
      socket.join(data);
      join(data, socket.request.session.color)
      socket.to(data).emit('join', {
        user: 'system',
        chat: `${socket.request.session.color}님이 입장하셨습니다.`,
      });
  });

여기서 emit를 보내도 될까요?

 

 

네 저기서 이밋하는 겁니다. 웹소켓은 http 컨트롤러를 호출 못합니다.

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.09

감사합니다 해결했습니다. 아예 위에 저 3개의 라우터를 지워버리고

chat.on('connection', (socket) => {
    console.log('chat 네임스페이스에 접속');
   

    socket.on('join', (data) => {
      socket.join(data);
      join(data, socket.request.session.color)
      socket.to(data).emit('join', {
        user: 'system',
        chat: `${socket.request.session.color}님이 입장하셨습니다.`,
        count: socket.adapter.rooms[data].length,
      });
    });

    console.log('어뎁터1:',io.of('/chat').adapter);
   

    socket.on('disconnect', async () => {
      console.log('chat 네임스페이스 접속 해제');
      const { referer } = socket.request.headers; // 브라우저 주소가 들어있음
      console.log('리퍼러',referer);
      const roomId = new URL(referer).pathname.split('/').at(-1);
      const currentRoom = io.of('/chat').adapter.rooms[roomId];
      const userCount = currentRoom ? currentRoom.length : 0;
     
      console.log('어뎁터2:',io.of('/chat').adapter);

      if (userCount === 0) { // 유저가 0명이면 방 삭제
        await removeRoom(roomId); // 컨트롤러 대신 서비스를 사용
        setTimeout(() => {
          io.of('/room').emit('removeRoom', roomId);// room네임스페이스에 emit
        }, 1000);
        console.log('방 제거 요청 성공');
      } else {
        await exit(roomId, socket.request.session.color)
        socket.to(roomId).emit('exit', {
          user: 'system',
          chat: `${socket.request.session.color}님이 퇴장하셨습니다.`,
          count: socket.adapter.rooms[roomId].length,
        });
      }
     
    });
});

소켓에서 emit하니 제대로 작동합니다. 감사합니다

 

 

 

wdhgood123님의 프로필

wdhgood123

질문자

2022.06.10

문제랄것도 없는 사소한 것이긴 한데 찝찝해서 물어봅니다.

방에 처음 입장했을 때의 시스템 메시지가 뜨지 않습니다.

웹소켓 연결은

socket.on('join', (data) => {
      socket.join(data);
      join(data, socket.request.session.color);
      socket.to(data).emit('join', {
        user: 'system',
        chat: `${socket.request.session.color}님이 입장하셨습니다.`,
        count: socket.adapter.rooms[data].length,
      });
  });

socket.emit('join', new URL(location).pathname.split('/').at(-1));
    socket.on('join', function (data) {
      document.querySelector('#count').textContent = data.count;
      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.join이 비동기여서 그럴 겁니다.

socket.join(data, () => {
join(data, socket.request.session.color)
socket.to(data).emit('join', {
user: 'system',
  chat: `${socket.request.session.color}님이 입장하셨습니다.`,
  count: socket.adapter.rooms[data].length,
});
});

   
wdhgood123님의 프로필

wdhgood123

질문자

2022.06.10

그렇다면 join 앞에 sysn를 붙여야 하나요?

알려주신대로 socket.join의 콜백함수로 emit을 넣었지만 변화는 없습니다.