채팅기능을 만들어보자
채팅메세지도 결국 게시물 발행기능과 똑같다.
새로운 점이 있다면 채팅메세지는 채팅방 게시물에 종속이 되어야한다.
댓글 만드는 법
댓글은 댓글 collection 을 하나 새로 만들어서 모든 댓글을 저장하면 된다.
하지만 그러면 그 댓글들 하나하나가 어떤 글에 달린 댓글인지 확인할 수 없게될 것이다.
때문에 모든 댓글은 부모 게시물이 무엇인지 를 명시해야한다.
따라서 게시물 collection 과 댓글 collection 을 만들고
댓글 1의 부모게시물은 게시물 2고,
댓글 2의 부모 게시물은 게시물 4이다... 이런식으로 collection 끼리 관계를 맺어야한다.
채팅방 만드는 POST api 만들기
채팅 버튼을 누르면 chatroom 이라는 collection 에 document 를 새로 생성하도록 만들어보자
chat 버튼을 누르면 /chatroom 이라는 경로로 post 요청을 한다.
이 post 요청은 그 글의 작성자(채팅 당한 사람)의 아이디를 e.target.dataset.id 로 얻어 같이 보낸다.
server.js 는 req.body 에 담긴 채팅당한 사람의 id 와 현재 로그인한 유저의 아이디(req.user._id 에 담겨있다) 를 포함한 데이터를 chatroom 이라는 collection 에 insertOne 한다.
현재 날짜는 new Date() 로 저장한다.
잘 insert 된 것을 볼 수 있다.
그럼 이번에는 채팅방 화면을 만들어보자.
/chat 경로로 get 요청을 하면 채팅방 화면을 렌더링 해주도록 했다.
채팅방화면을 구성해준 모습이다.
내가 속한 채팅방들을 <li> 로 보여주는데 이때 반복문을 사용한다.
왼쪽에 내가 속한(현재 로그인한 유저가 속한) 채팅방이 보인다.
메세지 전송기능 만들기
지금 구현하려는 메세지 전송은 기능구현자체는 게시물을 발행하는 것과 비슷하다.
대신 게시물 안에 댓글이 달리는 것처럼,
채팅방안에 메세지가 올라온다고 생각하자.
일단 채팅방 리스트가 있다.
이따 채팅방을 클릭하고 -> 메세지를 보낼 것이기 때문에 (메세지를 전송할 때에 채팅방의 정보도 보내기 때문)
list-group-item 에 data-id 값을 준다.
전송 버튼을 누르면 폼안에 입력한 메세지와 채팅방의 _id 를 서버로 전송하도록 코드를 짰다.
그 다음은 server.js 를 작업해줬다
/message 경로로 post 요청이오면
message 라는 collection에 데이터를 저장하도록 했다.
하다가 오류가 나서 확인해보니 jQuery slim 버전이면 $.ajax()를 사용할 수 없다고 한다.
<script src="https://code.jquery.com/jquery-3.5.1.slim.js"></script>
를
<script src="https://code.jquery.com/jquery-3.5.1.js"></script>
로 바꿔주었다.
그랬더니 오류가 해결되었다.
메세지 폼에 메세지를 작성하고 전송버튼을 눌러 테스트를 해보면 db에 잘 들어가있는 것을 확인할 수 있다.
서버와 실시간으로 통신하는 법(SSE)
방법은 2가지가 있다.
1. get 요청을 계속 날리기(예: 1초마다)
대신 이런경우엔 유저가 만명이렇게 되면 서버가 많이 힘들어질 것이다.
아니면 DDos 공격같은 경우도...
2. 서버와 유저간 실시간 소통채널을 열기(SSE)(Server Sent Events)
이 방법은 위의 방법보다 서버에 부담이 덜하다
나는 SSE 방법을 사용할거다.
원래 GET 이나 POST 같은 http 요청은 1회요청, 1회 응답이다.
하지만 1회 응답이 아니라 지속적으로 서버에서 응답을 하고싶은 경우가 있다(채팅같은 경우)
응답을 여러번 지속적으로 하는 방법은 다음과 같아
1. Header 라는 정보의 connection 항목을 keep-alive 로 설정
2. res.write(~~) -> 유저에게 지속적으로 응답 가능
Header란?
서버와 유저가 get, post http 요청으로 정보를 주고 받을때 부가정보로 몰래 전달된다.
유저의 브라우저, OS, 쓰는 언어, 보유 쿠키 등의 정보를 get 요청시에 서버로 몰래 전달한다.
반대로 서버도 유저에게 몰래 서버정보를 전달하는데,
이 정보를 보관하는 곳을 Header 라고 함.
유저 -> 서버 로 전달되는 header 는 request header,
서버 -> 유저 로 전달되는 header 는 response header 라고함.
유저가 /message/~~ 로 get 요청을 했을 때의 실시간 소통 채널을 열어보자
1. 서버는 res.writeHead(~~) 라고 쓰면 지속적 실시간 소통채널이 개설된다.
res.writeHead(200, {
"Connection": "keep-alive",
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
});
2. 유저에게 계속 메세지를 보내고 싶을 때마다 res.write() 를 한다.
3. event: 보낼 데이터의 이름, data : 보낼 데이터 를 쓴다. (\n 를 적절히 넣는다)
1. 유저는 new EventSource('/message/' + 지금누른채팅방id); 처럼 쓰면 서버에서 만들어놓은 실시간채널에 입장가능하다.
2. event_source.addEventListener('서버에서작명한데이터이름') 처럼 쓰면 서버가 보낸 데이터를 수신할 수 있다.
그럼 서버가 res.write() 할때마다 내부 코드를 실행한다.
3. e.data 안에는 서버가 보낸 데이터(data : 보낼데이터) 들어있다.
그럼 채널을 개설할 때 DB 의 메세지들을 가져와서 보여주어야 한다.
서버는 누군가가 /message/~~ 로 실시간 채널에 접속하면 DB dptj { parent : 채팅방id(여기선 parentid) } 를 가진 게시물을 다 찾아서 보내준다.
실시간 채널이니 res.write 를 이용해서 보낸다
찾은 자료는 [{}, {} ... ] 형태가 이런식이어서 JSON.stringify() 에 넣어 보낸다
(실시간 채널 이용할땐 문자만 전송가능하기 때문)
현재는 채팅방을 누를 때마다 소통채널에 계속 입장된다.
따라서 소통 채널 나가기를 구현해보자.
event_source 가 undefined 가 아닐 경우 close 되도록 코드를 추가했다.
채널을 클릭했을 때 불러온 메세지 데이터를 li 태그로 출력되도록한다.
대신 이렇게 하면 채널을 클릭할 때마다 이전에 append 된 li 까지 쌓여서 보여지게 된다.
따라서 채널을 클릭했을 때 이전의 채팅방 내용을 초기화 해주어야 한다.
93번째 줄에 해당한다.
html(' ') 을 통해 초기화 해주었다.
MongoDB Change Stream
DB는 수동적이라서 서버가 요청할 때만 데이터를 내준다.
MongoDB의 Change Stream 을 통해 DB 를 능동적으로 만들어보자.
DB 변동이 생기면 서버에게 바로 알려줄 수 있다(실시간 서비스를 구현할 수 있음!)
change stream 을 이용해서 message 라는 collection에 변동이 생길 때마다 실행할 코드들을 작성해주었다.
변동 사항은 result.fullDocument 안에 저장되어있다.(궁금하면 console.log 로 출력해보자)