Front/Vue.js

[Vue.js] 소켓(Socket) 연결 방법(예제 포함) 및 CORS에러 해결 방법

코딩일꾼 2022. 9. 21. 13:53
SMALL

소켓(Socket)이란?

소켓(Socket)은 네트워크 상에서 돌아가는 두 개의 프로그램 간 양방향 통신의 개념이라고 생각하시면 됩니다.
즉, 프로세스가 소켓을 열고, 소켓에 데이터를 넣어 보내거나 데이터를 읽어드리는 것을 의미한다.

 

구현

1.설치
npm install socket.io-client
npm install express socket.io

 

2. 서버 구축
server.js

* 이렇게만 하면 cors 문제가 생기니 아래 cors 문제 해결 방안도 같이 봐야합니다

const express = require('express')
const http = require('http')
const { Server } = require('socket.io')

const app = express()
const server = http.createServer(app)
const io = new Server(server);

io.on('connection', (socket) => {
    console.log(`user ${socket.id} is connected`)
    socket.on('message', data =>{
        socket.broadcast.emit('message:received', data)
    })
    socket.on('disconnect', () => {
        console.log(`user ${socket.id} left`)
    })
})

server.listen(3000, () => {
    console.log('3000 서버 오픈')
})

 

3. 화면 구축
App.vue
<template>
  <div v-if="!joined" class="parent-container">
    <div class="name-container">
      <input type="text" class="user-name" v-model="currentUser"/>
      <button class="join-button" @click="join()">Join</button>
    </div>
  </div>
  <div v-if="joined">
    <div class="list-container">
      <div v-for="message in messages" :key="message.id">
        <b>
          {{ message.user }}
        </b>
         : {{ message.text }}
      </div>
    </div>
    <div class="text-input-container">
      <textarea
        v-model="text"
        class="text-message"
        v-on:keyup.enter="sendMessage"
      ></textarea>
    </div>
  </div>
</template>

<script>
import io from 'socket.io-client'
export default {
  data(){
    return {
      joined:false,
      currentUser:'',
      text:'',
      messages:[],
    }
  },
  methods:{
    join(){
      this.joined = true
      this.socketInstance = io('http://localhost:3000')
      this.socketInstance.on(
        "message:received", (data) => {
          this.messages = this.messages.concat(data)
        }
      )
    },
    sendMessage(){
      this.addMessage()
      this.text = ''
    },
    addMessage(){
      const message = {
        id : new Date().getTime(),
        text : this.text,
        user: this.currentUser
      };
      this.messages = this.messages.concat(message);
      this.socketInstance .emit('message', message)
    }
  },
  name: 'ChatApp',
}
</script>

<style scoped>
.parent-container{
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  position: fixed;
  padding-top: 150px;
}

.name-container{
  display: flex;
  flex-direction: column;
  width: 200px;
}

.user-name {
  height: 30px;
  font-size: 20px;
  padding: 5px;
  margin-bottom: 5px;
  text-align: center;
  font-weight: bold;
}

.join-button{
  height: 30px;
  font-size: 20px;
}

.text-input-container{
  height: 100vh;

}

.text-message{
  width: 100%;
  position: absolute;
  bottom: 0px;
  height: 70px;
  padding: 10px;
  box-sizing: border-box;
}
</style>

 

실행
새 터미널 띄운 후 'node server.js' 입력

 

 

node 서버 터미널은 그대로 두고 새 터미널 띄운 후 'npm run serve' 입력

 

결과 화면

 

 

SMALL

 

에러 (cors)

실행을 시켜보니 프론트 개발자라면 피해갈 수 없는 숙명의 cors 문제가 발생하였다.

'has been blocked by cors policy no 'access-control-allow-origin' header is present on the requested'

 

cors 에러

 

해결 방법 1 : cors origin 전체 허용

아까 작성한 server.js 에서 특정 코드를 아래와 같이 origin이 전체 허용하도록 수정하였더니 정상적으로 작동 하였다.

 

* 수정 전

const io = new Server(server);

* 수정 후

const io = new Server(server, {
    cors: {
        origin: "*"
    }
});

* 하지만 출처가 명확하지 않은 곳에서도 연결을 시도할 수 있으므로 실무에서는 사용하면 위험하다

 

해결 방법 2 : transports 속성 명시

아까 작성한 App.vue에서 소켓 연동 시켜주는 부분에 transports의 속성을 'websocket'으로 명시해주면 정상 동작한다.

 

* 수정 전

this.socketInstance = io('http://localhost:3000')

* 수정 후

this.socketInstance = io('http://localhost:3000',{
  transports: ["websocket"]
})

 

 

최종 결과 화면

 

양방향 통신 화면

이미지와 같이 창을 두개 띄워서 다른 user로 접속 후 한쪽씩 채팅하며 정상동작하는 것을 확인할 수 있다.

 

 

 

* 최종 코드 - server.js

const express = require('express')
const http = require('http')
const { Server } = require('socket.io')

const app = express()
const server = http.createServer(app)
const io = new Server(server, {
    cors: {
        origin: "*"
    }
});

io.on('connection', (socket) => {
    console.log(`user ${socket.id} is connected`)
    socket.on('message', data =>{
        socket.broadcast.emit('message:received', data)
    })
    socket.on('disconnect', () => {
        console.log(`user ${socket.id} left`)
    })
})

server.listen(3000, () => {
    console.log('3000 서버 오픈')
})

* 최종 코드 - App.vue

<template>
  <div v-if="!joined" class="parent-container">
    <div class="name-container">
      <input type="text" class="user-name" v-model="currentUser"/>
      <button class="join-button" @click="join()">Join</button>
    </div>
  </div>
  <div v-if="joined">
    <div class="list-container">
      <div v-for="message in messages" :key="message.id">
        <b>
          {{ message.user }}
        </b>
         : {{ message.text }}
      </div>
    </div>
    <div class="text-input-container">
      <textarea
        v-model="text"
        class="text-message"
        v-on:keyup.enter="sendMessage"
      ></textarea>
    </div>
  </div>
</template>

<script>
import io from 'socket.io-client'
export default {
  data(){
    return {
      joined:false,
      currentUser:'',
      text:'',
      messages:[],
    }
  },
  methods:{
    join(){
      this.joined = true
      this.socketInstance = io('http://localhost:3000')
      this.socketInstance.on(
        "message:received", (data) => {
          this.messages = this.messages.concat(data)
        }
      )
    },
    sendMessage(){
      this.addMessage()
      this.text = ''
    },
    addMessage(){
      const message = {
        id : new Date().getTime(),
        text : this.text,
        user: this.currentUser
      };
      this.messages = this.messages.concat(message);
      this.socketInstance .emit('message', message)
    }
  },
  name: 'ChatApp',
}
</script>

<style scoped>
.parent-container{
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  position: fixed;
  padding-top: 150px;
}

.name-container{
  display: flex;
  flex-direction: column;
  width: 200px;
}

.user-name {
  height: 30px;
  font-size: 20px;
  padding: 5px;
  margin-bottom: 5px;
  text-align: center;
  font-weight: bold;
}

.join-button{
  height: 30px;
  font-size: 20px;
}

.text-input-container{
  height: 100vh;

}

.text-message{
  width: 100%;
  position: absolute;
  bottom: 0px;
  height: 70px;
  padding: 10px;
  box-sizing: border-box;
}
</style>

 

참조 : (12) Building a Chat App using VueJS, Socket.IO and NodeJS | Manoj Singh Negi | Recraft Relic - YouTube

LIST