
기본 게시판 CRUD를 만들고 파일 업로드까지 마쳤다.
지금 할 수 있는 것 중 쉬운 것은 "웹소켓"
웹소켓을 이용하여 채팅을 구현해보자.

NestJS에서 웹소켓은 Gateway를 통해 이뤄진다. 공식 문서에 따르면, Gateway는 Provider처럼 제공된다.
웹소켓을 연결하게 할 수 있게 해주는 것을 이르는 말 정도로 할 수 있겠다.
먼저 모듈을 설치해주자.
npm i --save @nestjs/websockets @nestjs/platform-socket.io
yarn add @nestjs/websockets @nestjs/platform-socket.io --save
그 다음 모듈과 게이트웨이를 만들어주자.
nest g mo socket
nest g ga socket // nest g gateway socket
게이트웨이를 만들었다면 아래와 같이 나타날 것이다.
@WebSocketGateway()
export class AsdfGateway {
@SubscribeMessage('message')
handleMessage(client: any, payload: any): string {
return 'Hello world!';
}
}
웹소켓으로 하면 왜인지 모르겠지만 테스트 코드에서도 웹소켓 연결조차 되지 않는다.
CORS 문제일까 생각해서 origin을 모든 곳에서 받는 것으로 했는데도 안되고...
따라서 Socket.IO를 사용하였다.
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: {
origin: '*',
},
}) // 기본 포트로 설정(3000)
export class EventsGateway {
// socket.io 이용하기 위한 방법
@WebSocketServer() // ① SocketIO로 사용하겠다 설정
server: Server;
@SubscribeMessage('events')
handleMessage( // ②
@MessageBody() data: any,
// 클라이언트 정보를 확인하기 위해서는 ConnectedSocket 어노테이션 사용
@ConnectedSocket() sender: Socket
) : void {
...
}
}
@WebSocketServer()을 이용해 Socket.IO로 웹소켓 통신을 하도록 한다.handleMessage : Socket.IO를 사용하기 위해 메소드 수정웹소켓은 소켓 연결 시 헤더를 넘겨줄 수 없다. 헤더를 전달하기 위해서는
1. 소켓 연결 전 http handshake로 헤더 전달
2. 헤더 검증을 마치고 소켓 연결
이렇게 해야한다.
socket.io에서는 extraHeaders를 통해 헤더를 전달할 수 있다.
socket = io('http://localhost:3000', {
extraHeaders: {
Authorization: "Bearer asdf.zxcv.qwer",
roomName: "test",
...
}
});
// @SubscribeMessage('events')
handleConnection(client: Socket, ...args: any[]) {
const headers = client.handshake.headers;
const token = headers.authorization as string;
const roomName = headers.roomName as string;
...
}
소켓 연결 시 헤더로 방 정보를 보낼 수 있다는 장점 때문에 WebSocket 대신 Socket.IO를 사용하였다.
Socket.IO를 이용하여 구현하였다.

위를 구현하기 위해 아래의 구조의 형식으로 만들 예정이다.
rooms = {
"room1": [
user1,
user2,
user3,
...
],
"room2": [
user1,
user4,
...
],
...
}
// Map<string, Set<Socket>>
// Map : 딕셔너리처럼 관리 가능
// Set : 중복을 제거하는 Set 사용. 혹시라도 모를 재진입 방지
Map.get({ name })이 Call-By-Value가 아닌 Call-By-Reference 방식으로 동작해서 너무 다행이다. get으로 얻어온 객체를 수정해도 원본의 데이터가 수정이 된다!
handleConnection(client: Socket, ...args: any[]) {
const headers = client.handshake.headers;
const roomName = headers.roomName as string
if (headers.authorization !== "Bearer asdf.zxcv.qwer") {
client.send("disconnected");
client.disconnect();
return;
}
if (!this.rooms.get(roomName)) {
this.rooms.set(roomName, new Set<Socket>());
}
const room = this.rooms.get(roomName);
room.add(client);
client.send("conneted");
}
handleDisconnect(client: Socket) {
const headers = client.handshake.headers;
const roomName = headers.roomName as string
const room = this.rooms.get(roomName);
room.delete(client); // 유저 삭제
if (room.size === 0) { // 채팅방에 유저가 없다면
this.rooms.delete(roomName); // 채팅방 삭제
}
}
현재 채팅방에 있는 모든 사람 중 sender가 아닌 사람에게만 보내면 된다.
@SubscribeMessage('events')
handleMessage(
@MessageBody() data: any,
// 클라이언트 정보를 확인하기 위해서는 ConnectedSocket 어노테이션 사용
@ConnectedSocket() sender: Socket
): Observable<WsResponse<number>> {
const headers = sender.handshake.headers;
const room = this.rooms.get(headers.roomName as string);
room.forEach( (client) => { // forEach로 탐색 후 메시지 전달
if (client !== sender) {
client.send(data);
}
});
return;
}
웹소켓까지 성공적으로 구현하였다. Socket.IO를 이용하여서 간단하게 소켓 통신까지 구현하였다.
해결되지 않은 부분 또는 구현해야할 내용은 아래와 같다.