백엔드
패스트캠퍼스 챌린지 10일차 - 웹소켓을 통한 실시간 인터렉션 구현
꾸준이
2021. 9. 15. 23:49
0. 공부 시간
공부 시작 : 21 : 32
공부 종료 : 23 : 48
1. 요구사항과 프로젝트 설계
- 실시간 채팅
- 데이터베이스에 채팅 내역을 저장
프론트엔드 구성
-PUG
-TailwindCSS
백엔드 구성
-Koa
-koa-websocket
-MongoDB
2. 프론트엔드 UI 구성하기
초기 세팅
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
ctx.body = 'Hello World'
// express 와 달리 next에서 프로미스를 돌려줌
await next()
ctx.body = `[${ctx.body}]`
});
app.use(async ctx => {
ctx.body = `<${ctx.body}>`
})
app.listen(5000);
pug와 TailwindCSS 설치하기
const Pug = require('koa-pug')
const path = require('path')
~
const pug = new Pug({
viewPath: path.resolve(__dirname, './views'),
app
})
~
app.use(async ctx => {
await ctx.render('main')
})
pug 파일 (src/views/main.pug)
html
head
link(href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet")
body
h1.mx-4.my-4.bg-gray-200 hello, Pug
1차 - 채팅 웹 프론트 엔드
html
head
link(href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet")
body
h1.bg-gradient-to-r.bg.from-purple-100.to-gray-200.p-16.text-4xl.font-bold 나의 채팅 서비스
div.p-16.space-y-8
div.bg-gray-100.p-2 채팅 목록
form.space-x-4.flex
input.flex-1.border.border-gray-200.p-4.rounded(placeholder="채팅을 입력해보세요")
button.bg-blue-600.text-white.p-2.rounded 보내기
3. 웹소켓 미들웨어 사용해 프론트엔드와 연결하기
- 클라이언트 쪽의 JS 파일 만들기
main.pug
script(src="/public/client.js")
- 백엔드에서 정적파일 공급하기
const websockify = require('koa-websocket'),
route = require('koa-route'),
Koa = require('koa'),
// 웹 소켓으로 Koa 불러오기
app = websockify(new Koa()),
// 정적 파일
serve = require('koa-static'),
path = require('path'),
Pug = require('koa-pug');
// 특정 라우팅에서 미들웨어 실행하기 for 정적파일 url
mount = require('koa-mount')
// PUG 뷰 엔진으로 설정하기
const pug = new Pug({
viewPath: path.resolve(__dirname, './views'),
app
})
// 정적파일 제공
app.use(mount('/public', serve('src/public')))
app.use(async ctx => {
await ctx.render('main')
})
// Using routes
// 웹소켓 미들웨어는 따로 .ws. 으로 넣어주게 되어있음
app.ws.use(
// 아래 경로로 들어오면 웹소켓 처리를 하는 것.
route.all('/ws/:id', (ctx) => {
// `ctx` is the regular koa context created from the `ws` onConnection `socket.upgradeReq` object.
// the websocket is added to the context on `ctx.websocket`.
ctx.websocket.send('Hello World');
ctx.websocket.on('message', (message) => {
// do something with the message from client
console.log(message);
});
}));
app.listen(5000);
4. 웹 소켓 미들웨어를 통한 클라이언트와 서버 요청 왔다감...ㅎ
-서버에서 먼저 웹소켓 라우팅하기
app.ws.use(
// 아래 경로로 들어오면 웹소켓 처리를 하는 것.
route.all('/ws', (ctx) => {
// 클라이언트로 부터 데이터를 받을 경우
ctx.websocket.on('message', (message) => {
// do something with the message from client
console.log(message);
ctx.websocket.send("hello, client")
});
}));
- 클라이언트에서 웹소켓 주고 받기
코드의 은닉화(?)를 위하여 IIFE 를 형태를 사용
// 클라이언트 측 콘솔에서 작동을 불가능하게 하기. -> IIFE
;(() => {
const socket = new WebSocket(`ws://${window.location.host}/ws`)
// WS 통신이 시작되는 순간
socket.addEventListener('open', () => {
socket.send("hello, server")
})
// 서버로부터 응답받는 순간
socket.addEventListener('message', eve => {
alert(eve.data)
})
})()
- 클라이언트가 보낸 요청을 서버에서 받아서 다시 클라이언트에서 받고, 그것을 돔에 추가하기
client.js
// 클라이언트 측 콘솔에서 작동을 불가능하게 하기. -> IIFE
;(() => {
const socket = new WebSocket(`ws://${window.location.host}/ws`)
const formEl = document.getElementById('form')
const inputEl = document.getElementById('input')
const chatsEl = document.getElementById('chats')
if(!formEl || !inputEl || !chatsEl ) throw new Error("init fail")
const chats = []
formEl.addEventListener('submit', eve => {
eve.preventDefault()
socket.send(JSON.stringify({
nickname : "멋진사람",
message : inputEl.value
}))
inputEl.value = ""
})
socket.addEventListener('open', () => {
// socket.send("hello, server")
})
socket.addEventListener('message', eve => {
chats.push(JSON.parse(eve.data))
chatsEl.innerHTML = ''
chats.forEach( ({message, nickname}) => {
const div = document.createElement('div')
div.innerText = `${nickname} : ${message}`
chatsEl.appendChild(div)
})
})
})()
main.js
// Using routes
// 웹소켓 미들웨어는 따로 .ws. 으로 넣어주게 되어있음
app.ws.use(
// 아래 경로로 들어오면 웹소켓 처리를 하는 것.
route.all('/ws', (ctx) => {
// `ctx` is the regular koa context created from the `ws` onConnection `socket.upgradeReq` object.
// the websocket is added to the context on `ctx.websocket`.
// ctx.websocket.send('Hello World');
ctx.websocket.on('message', (data) => {
if(typeof data !== "string") return
const { message, nickname } = JSON.parse(data)
ctx.websocket.send(JSON.stringify({
message,
nickname
}))
});
}));
그런데 위코드로 하면, 보낸 사람한테만 다시 데이터를 보내는 것이기에 일반적인 채팅이 안됨.
그러면 브로드캐스팅을 이용해야해
// ctx.websocket.send(JSON.stringify({
// message,
// nickname
// }))
// 대신
const {server} = app.ws
if(!server) return
// 브로딩 캐스팅,
// 접속한 모든 클라이언트에게 전송
server.clients.forEach(client => {
client.send(JSON.stringify({
message,
nickname
}))
})
또한 닉네임의 다양하게 해보자
const adj = ["멋진", "이쁜"]
const ani = ["호랑이", "향슈"]
function pickRand(array){
// Math.random() 은 0~1 사이의 랜덤값
const rndIdx = Math.floor(Math.random() * array.length)
const result = array[rndIdx]
if(!result) throw new Error("no Err")
return result
}
const myNickname = pickRand(adj) + pickRand(ani)
formEl.addEventListener('submit', eve => {
eve.preventDefault()
socket.send(JSON.stringify({
nickname : myNickname,
message : inputEl.value
}))
inputEl.value = ""
})
으어 늦겠다.
빨리 수업 업로드 해야지....
환급 챌린지 (10/30)
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
패스트캠퍼스 [직장인 실무교육]
프로그래밍, 영상편집, UX/UI, 마케팅, 데이터 분석, 엑셀강의, The RED, 국비지원, 기업교육, 서비스 제공.
fastcampus.co.kr
#패스트캠퍼스 #패캠챌린지 #직장인인강 #직장인자기계발 #패스트캠퍼스후기 #한번에끝내는Node.js웹프로그래밍초격차패키지