백엔드

패스트캠퍼스 챌린지 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웹프로그래밍초격차패키지