본문 바로가기
Javascript

NestJS + Websocket으로 채팅만들기 #2 (feat. Socket.io)

by for2gles 2021. 7. 25.
반응형

이전까지 페이지에 접속하면 채팅방에 접속하여 채팅방으로서의 역할만을 하는 기능을 제작했다.

 

 


이번에는

  1. 채팅방을 생성할 수 있다.
  2. 채팅방 목록 페이지에서 본인의 닉네임을 설정하고, 변경은 목록페이지에서만 가능하다.
  3. 채팅방에 접속하면 이전 채팅글을 볼 수 있고, 100개단위로 이전 채팅글을 불러올 수 있다.
  4. 글자수 혹은 json size의 제한을 통해 바이너리형태의 직접적인 데이터 전송, xss 등의 보안부분도 추가해준다.
  5. 닉네임등록시 금지어를 설정하여 비속어 등을 제한한다.
  6. 사이트를 껐다 킬 경우에도 채팅방에 닉네임은 유지된다.

1번 6번에 해당하는 작업을 진행해보려고 한다.

 

1번 6번 작업하기에 앞서 현재 NestJS 서버의 Websocket이 Socket.io 가 아닌 ws 이다.

따라서 Websocket 에서 Socket.io로 변경하는 작업이 필요하다.

 


Websocket to Socket.io

가장 첫번째로 할 일은 Websocket 으로 서버가 생성되던 부분을 Socket.io 서버로 변경해주어야한다.

방법은 간단하다. 단순히 main.ts 에 들어가는 WsAdapter 를 SocketIoAdapter로 변경해주면 된다.

Websocket 은 단순히 

import { WsAdapter } from '@nestjs/platform-ws';

위 import 를 통해

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { WsAdapter } from '@nestjs/platform-ws';
import { join } from 'path';
import { AppModule } from './app.module';

async function bootstrap() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    app.useWebSocketAdapter(new WsAdapter(app));
    app.useStaticAssets(join(__dirname, '..', 'public'));
    app.setBaseViewsDir(join(__dirname, '..', 'views'));
    app.setViewEngine('ejs');

    await app.listen(3000);
}
bootstrap();

이렇게 구현했으나

 

Socket.io는 Custom Adapter 를 구성해주어야 한다.

adapters/socket-io.adapters.ts

import { IoAdapter } from '@nestjs/platform-socket.io';

export class SocketIoAdapter extends IoAdapter {
    createIOServer(port: number, options?: any): any {
        const server = super.createIOServer(port, options);

        return server;
    }
}

https://github.com/for2gles/realtime-chat/blob/27c3640538a36e7b58477dc7120f7c1e47f4ac41/src/adapters/socket-io.adapters.ts

 

위와 같이 설정해주고,

 

main.ts.를

import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { WsAdapter } from '@nestjs/platform-ws';
import { join } from 'path';
import { AppModule } from './app.module';
import { SocketIoAdapter } from './adapters/socket-io.adapters';

async function bootstrap() {
    const app = await NestFactory.create<NestExpressApplication>(AppModule);
    app.useWebSocketAdapter(new SocketIoAdapter(app));
    app.useStaticAssets(join(__dirname, '..', 'public'));
    app.setBaseViewsDir(join(__dirname, '..', 'views'));
    app.setViewEngine('ejs');

    await app.listen(3000);
}
bootstrap();

이렇게 SocketIOAdapter로 변경해준다. 그러면 백엔드서버는 Websocket => Socket.io로 변경이 완료되었다.

 

추가로 수정해줘야하는 부분이 있는데, 바로 프론트이다.

기존에는 백엔드서버가 Websocket 이었기 때문에 브라우져에 내장되어있는 Websocket 을 활용하면 작동이 되었지만, Socket.io는 별도 js파일을 로드해주어야한다.

 

<script src="https://cdn.socket.io/3.1.3/socket.io.min.js" integrity="sha384-cPwlPLvBTa3sKAgddT6krw0cJat7egBga3DJepJyrLl4Q9/5WLra3rrnMcyTyOnh" crossorigin="anonymous"></script>

이렇게 script를 불러와주고

const socket = new WebSocket('ws://localhost:5000');

이렇게 서버를 연결해주던 부분을

const socket = io('http://localhost:5000');

이렇게 socket.io로 변경해주어야한다.

 

Websocket 에서 Socket.io로 전환하면 장점이 여러가지가 있다.

1. 기존 send방식을 훨씬 다채롭게 사용할 수 있다.

무슨말이냐 하면, 기존 Websocket에서는 message를 전송할때 무조건 

socket.send(JSON.stringify({Object}))

의 형태로

send 함수만을 사용해야했고, 꼭 내부 메시지는 String 형태만으로 전송할 수 있어 Object 형태의 메시지를 전송하기 위해서는 꼭 JSON.stringify가 필요했다. 사실 다양한 형태의 socket 메시지를 전송하기위해서는 Object가 불가피하다.

하지만 Socket.io를 사용하면

socket.emit('메시지 이름', 보내고싶은자료)

이렇게 원하는 메시지 이름으로 전송할 수 있고, 서버에서도 원하는 메시지 이름을 간편하게 subscribe를 할 수 있다.

또한 보내고싶은 자료는 Object던 String이던 상관없이 모두 입력이 가능하다.

 

2. 자동으로 id값을 부여해준다.

기존에 Websocket 서버에서는  client 마다 id가 존재하지 않아 직접적으로 구분자를 넣어줘야했다.

    public handleConnection(client): void {
        client['id'] = String(Number(new Date()));
        client['nickname'] = '낯선남자' + String(Number(new Date()));
        this.client[client['id']] = client;
    }

이렇게 Connection 할 때마다 id 부여가 필요했으나, Socket.io는 기본적으로 client마다 고유 id를 부여해주기 때문에 신경쓰지 않아도 된다.

 

3. 기본적으로 room 기능이 탑재되어있다.

내가 직접 room의 개념을 구성 할 필요가 없다. socket.io에는 room이라는 구조를 기본적으로 제공 해 주고 심지어 Namespace라고 room보다 상위 그룹또한 존재한다. 이번 채팅방에서는 room 만 사용하지만, Namespace를 활용 해 보는것도 좋아보인다.

room기능이 있기 때문에 채팅방 구분이 명확하고, 해당 방 입장, 나가기가 명확하고, Room 마다 Broadcast 까지 지원하므로 너무나도 편리하다.

 

마저 이전하기

먼저 gateway.ts 파일를 수정 해 줄 것이다.

npm i socket.io

를 터미널에서 실행 해 주고

import { Server } from 'ws';
@WebSocketServer()
server: Server;

Websocket에서 가져오던 Server 형태를 

import { Server, Socket } from 'socket.io';
@WebSocketServer()
server: Server;

이렇게 변경 해 줌으로서 this.server 에 socket.io 의 Server type을 적용시켜준다.

ps. 추가로 handleDisconnect(client){ 이렇게 client 소켓을 any 형태로밖에 사용할 수 없었는데, Socket.io는 Socket이라는 type을 제공 해 주어 handleDisconnect(client: Socket){ 이렇게 type 지정하여 사용이 가능하다.

 

Socket.io는 Websocket 보다 훨씬 편리하기 때문에 나머지 이전은 너무 수월하다.

 

구) Websocket

    @SubscribeMessage('message')
    handleMessage(client: any, payload: any): void {
        for (const [key, value] of Object.entries(this.client)) {
            value.send(
                JSON.stringify({
                    event: 'events',
                    data: { nickname: client['nickname'], message: payload },
                }),
            );
        }
    }

현) Socket.io

    //메시지가 전송되면 모든 유저에게 메시지 전송
    @SubscribeMessage('sendMessage')
    sendMessage(client: Socket, message: string): void {
        server.emit('getMessage', message);
    }

너무 예쁘다.


생각보다 너무 길어져서 다음편에 이어서 쓰도록 해야겠다.

 

반응형

댓글