이전까지 페이지에 접속하면 채팅방에 접속하여 채팅방으로서의 역할만을 하는 기능을 제작했다.
이번에는
- 채팅방을 생성할 수 있다.
- 채팅방 목록 페이지에서 본인의 닉네임을 설정하고, 변경은 목록페이지에서만 가능하다.
- 채팅방에 접속하면 이전 채팅글을 볼 수 있고, 100개단위로 이전 채팅글을 불러올 수 있다.
- 글자수 혹은 json size의 제한을 통해 바이너리형태의 직접적인 데이터 전송, xss 등의 보안부분도 추가해준다.
- 닉네임등록시 금지어를 설정하여 비속어 등을 제한한다.
- 사이트를 껐다 킬 경우에도 채팅방에 닉네임은 유지된다.
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;
}
}
위와 같이 설정해주고,
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);
}
너무 예쁘다.
생각보다 너무 길어져서 다음편에 이어서 쓰도록 해야겠다.
'Javascript' 카테고리의 다른 글
블록스코프, 함수스코프 란(feat. var, const, let) (0) | 2021.08.09 |
---|---|
NestJS + Websocket으로 채팅만들기 #4 (feat. 채팅 구색갖추기) (0) | 2021.07.26 |
NestJS + Websocket으로 채팅만들기 #3 (feat. 채팅 "방" 만들기) (2) | 2021.07.26 |
Prisma의 단점 - Timezone, 다른ORM은? (2) | 2021.07.23 |
NestJS + Websocket으로 채팅만들기 #1 (1) | 2021.07.16 |
댓글