์ด๋ฒ์๋ Pipe๋ฅผ ์ฌ์ฉํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
REST API์์์ pipe์ Gateway์์์ API๋ ์๋ก ๋ค๋ฅด์ง ์์ต๋๋ค. ๊ณต์๋ฌธ์์๋ ๋ณ๋ก ๋ค๋ฅด์ง ์๋ค๊ณ ํฉ๋๋ค.
ํฌ์คํธ๋งจ์ผ๋ก 1๋ฒ ์ฌ์ฉ์๋ฅผ ์ฐ๊ฒฐ ํ JSON์ผ๋ก ์๋ฌด๋ฐ ๊ฐ์ ์ฃผ์ง์๊ณ create_chat
์ ํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๋จผ์ ๋ฆฌ์ค๋์ผ๋ก exception
๊ณผ receive_message
๋ฅผ ํ์ฑํ ํด์ค๋๋ค.
์ด๋ ๊ฒ ๋์ค๋ฉด ์๋ฉ๋๋ค. ์๋ํ๋ฉด ์ด๋ ํ ๊ฐ์ด ๋ญ๊ฐ ์๋ชป ๋์๋ค๊ณ ๋์์ผํฉ๋๋ค. ๊ทธ๋ฐ๋ฐ InternalServerError๊ฐ ๋์ค๊ฒ ๋ฉ๋๋ค.
์ฐ๋ฆฌ๋ ์ด๋ฏธ main.ts
์์ Validation pipe๋ฅผ ์ ์ญ์ ์ผ๋ก ์์ฉํ๋๋ก ๋ง๋ค์์ต๋๋ค. ํ์ง๋ง ์๋์ ํ์ง ์์ต๋๋ค.
app.useGlobalPipes(new ValidationPipe({
transform: true, // ๋ณํ๋ ํด๋ ๋๋ค.
transformOptions: {
enableImplicitConversion: true // ์์๋ก ๋ณํํ๋ ๊ฒ์ ํ๊ฐํ๋ค.
},
whitelist: true,
forbidNonWhitelisted: true,
}));
์์ฝ๊ฒ๋ ๊ธ๋ก๋ฒ ํ์ดํ๋ฅผ ์ ์ญ์ ์ผ๋ก ์์ฉํ๋ ๊ฒ์ ์ค์ง REST API ์ปจํธ๋กค๋ฌ
์๋ง ์ ์ฉ์ด ๋ฉ๋๋ค. ๊ทธ๋์ ์ฐ๋ฆฌ๊ฐ Gateway๋ฅผ ์ฌ์ฉํ ๋๋ ๋ฐ๋ก Validation์ gateway
์ ์ถ๊ฐํด์ค์ผ ํฉ๋๋ค.
๋ฐ๋ผ์ @SubscribeMessage
๋ณ๋ก @UsePipes
๋ฅผ ์ ์ฉํด์ผํฉ๋๋ค.
@UsePipes(new ValidationPipe({
transform: true, // ๋ณํ๋ ํด๋ ๋๋ค.
transformOptions: {
enableImplicitConversion: true // ์์๋ก ๋ณํํ๋ ๊ฒ์ ํ๊ฐํ๋ค.
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@SubscribeMessage('create_chat')
async createChat(
@MessageBody() data: CreateChatDto,
@ConnectedSocket() socket: Socket,
) {
const chat = await this.chatsService.createChat(
data,
);
}
ํฌ์คํธ๋งจ์ผ๋ก ๋ค์ ๋ณด๋ด๋ ์๋ฌ๊ฐ ๋ฐ์ํฉ๋๋ค.
ํ์ฌ ์๋ฌ์ ์์น๋ฅผ ๋ณด๋ฉด validation.pipe๋ก ๋์ต๋๋ค. ์ฆ, dto์์๋ ํต๊ณผ๊ฐ ๋์๋ค๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง Bad Request Exception์ด ๋ฉ์ธ์ง๋ก ์ ๋ฌ์ด ๋์ง์๊ณ ํฐ์ ธ๋ฒ๋ ธ์ต๋๋ค.
์๋ํ๋ฉด ์ฐ๋ฆฌ๋ ์๋ฌ๋ฅผ ๋์ง ๋ WsException์ผ๋ก ๋์ ธ์ผํฉ๋๋ค. ํ์ง๋ง class-validator๋ ๊ธฐ๋ณธ์ ์ผ๋ก REST API๋ฅผ ์ํด์ ์ค๊ณ๊ฐ ๋๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋ชจ๋ Expection๋ค์ด HTTP Excetpion์ extendsํ๊ณ ์์ต๋๋ค.
ํ์ง๋ง WsException์ HTTP Exception์ extendsํ๊ณ ์์ง ์์ต๋๋ค. ๊ทธ๋์ ๋ค์๊ณผ ๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ ๋๋ค.
๊ทธ๋ฌ๋ฉด ์ฐ๋ฆฌ๋ HTTP Exception๋ค์ด ๋ฐ์ํ์ ๋, WsException์ผ๋ก ๋ณํํด์ฃผ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ๋๋ ๊ฒ์ ๋๋ค.
์ด์ด์ HTTP Exception๋ค์ด ๋ฐ์ํ์ ๋, WsException์ผ๋ก ๋ณํํด์ฃผ๋ ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
Exception Filter์์ HTTP Exception์ ๋ชจ๋ ์ก์์ WsException์ผ๋ก ๋ฐ๊ฟ์ฃผ๋ฉด ์๋ฌ๋ค์ ์ ๋ถ ์ก์ ์ ์์ต๋๋ค.
common ํด๋์์ ์์ ์ ์งํํ๊ฒ ์ต๋๋ค.
@Catch(HttpException)
export class SocketCatchHttpExceptionFilter extends BaseWsExceptionFilter<HttpException> {
// BaseWsExceptionFilter๋ฅผ ์์ํ๋ฉด Ws๊ด๋ จ Exception์ ๋ง๋ค ์ ์๋ค.
catch(exception: HttpException, host: ArgumentsHost): void {
super.catch(exception, host);
const socket = host.switchToWs().getClient(); // ์์ผ ๊ฐ์ ธ์ค๊ธฐ
socket.emit( // ํ์ฌ ์์ผ์๋ค๊ฐ๋ง emitํ๊ธฐ
'exception', // ์ด๋ฒคํธ ์ด๋ฆ
{
// ์ค์ ์๋ต์์ ๋ฐ๋ {๋ฉ์ธ์ง ํํ๋ค}
data: exception.getResponse(),
}
)
}
}
Exception Filter
๋ฅผ ์ ์ฉํ๊ฒ ์ต๋๋ค.
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter) // ์ ์ฉ
@SubscribeMessage('create_chat')
async createChat(
@MessageBody() data: CreateChatDto,
@ConnectedSocket() socket: Socket,
) {
const chat = await this.chatsService.createChat(
data,
);
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธ๋ฅผ ํ๊ฒ ์ต๋๋ค.
User 1๊ณผ User 2๋ชจ๋ ๋ฆฌ์ค๋๋ฅผ exception
, receive_message
๋ฅผ ์ด๊ฒ ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ create_chat
์ ์คํํ๋ฉด ์๋ฌ๊ฐ 2๊ฐ๊ฐ ๋์ต๋๋ค.
์ด์ ๋ super.catch()
๋๋ฌธ์
๋๋ค. ๋ฐ๋ผ์ ํด๋น ์ฝ๋๋ฅผ ์ญ์ ํฉ๋๋ค. ์ดํ์ ๋ค์ ๋๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ํ
์คํธ๋ฅผ ํ๊ฒ๋๋ฉด
๋ค์ ์๋ฌ ๋ฉ์ธ์ง๋ User 1๋ฒ์๊ฒ๋ง ๊ฐ๊ฒ ๋ฉ๋๋ค. User 2๋ฒ์ ์ด๋ ํ ์๋ฌ ๋ฉ์ธ์ง๋ฅผ ๋ฐ์ง ๋ชปํฉ๋๋ค. ์๋ํ๋ฉด ์ฐ๊ฒฐ๋ ์ฌ์ฉ์ํํ
๋ง ๊ฐ๋ emit
๋๋ฌธ์
๋๋ค.
์ฆ, Gateway์์๋ Pipe๋ฅผ ์ ์ฉํ๊ธฐ ์ํด์๋ ๊ฐ๊ฐ์ ๋ฉ์๋ ์์๋ค๊ฐ ์ ์ฉ์ ํด์ค์ผํฉ๋๋ค.
์ด๋ฒ์๋ Guard๋ฅผ ์ ์ฉํด๋ณด๊ฒ ์ต๋๋ค. REST API์์ ์ ์ฉํ ๋ฐฉ๋ฒ๊ณผ ๋์ผํฉ๋๋ค.
@Injectable()
export class SocketBearerTokenGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly userService: UsersService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// ์ง๊ธ ์ฐ๊ฒฐํด์ ์ฌ์ฉํ๊ณ ์๋ ์์ผ
const socket = context.switchToWs().getClient();
// ํค๋ ๊ฐ์ ธ์ค๊ธฐ
const headers = socket.handshake.headers;
// Bearer xxx
const rawToken = headers['authorization'];
if (!rawToken) throw new WsException('ํ ํฐ์ด ์์ต๋๋ค. ');
const token = this.authService.extractTokenFromHeader(
rawToken,
true
);
const payload = this.authService.verifyToken(token);
const user = await this.userService.getUserByEmail(payload.email);
socket.user = user;
socket.token = token;
socket.tokeType = payload.tokenType;
}
}
์ด์ try catch๋ก ๋ฌถ๊ฒ ์ต๋๋ค. ์๋ํ๋ฉด ๊ธฐ๋ณธ์ ์ผ๋ก HTTP Exception์ ์ฃผ๊ธฐ ๋๋ฌธ์ WsException์ผ๋ก ๋ณ๊ฒฝ์ ํด์ค์ผ ํฉ๋๋ค.
@Injectable()
export class SocketBearerTokenGuard implements CanActivate {
constructor(
private readonly authService: AuthService,
private readonly userService: UsersService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const socket = context.switchToWs().getClient();
const headers = socket.handshake.headers;
const rawToken = headers['authorization'];
if (!rawToken) throw new WsException('ํ ํฐ์ด ์์ต๋๋ค. ');
try {
const token = this.authService.extractTokenFromHeader(
rawToken,
true
);
const payload = this.authService.verifyToken(token);
const user = await this.userService.getUserByEmail(payload.email);
socket.user = user;
socket.token = token;
socket.tokeType = payload.tokenType;
return true;
} catch (error) {
throw new WsException('ํ ํฐ์ด ์ ํจํ์ง ์์ต๋๋ค.')
}
}
}
chats.module.ts์์ provider๋ก ๋ฑ๋ก์ ํ๊ฒ ์ต๋๋ค.
@Module({
imports: [
TypeOrmModule.forFeature([
ChatsModel,
MessagesModel,
]),
CommonModule,
AuthModule,
UsersModule,
],
controllers: [
ChatsController,
MessagesController,
],
providers: [
ChatsGateway,
ChatsService,
ChatsMessagesService,
],
})
export class ChatsModule {}
์ด์ chats.gateway.ts์ ์ ์ฉ์ ํ๊ฒ ์ต๋๋ค.
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@UseGuards(SocketBearerTokenGuard) // ์ถ๊ฐ
@SubscribeMessage('create_chat')
async createChat(
@MessageBody() data: CreateChatDto,
// ์ธํฐ์น์
: user๊ฐ UsersModel์ด๋ผ๊ณ ์กด์ฌํ๋ค.
// ํ ํฐ์ด ํต๊ณผ๋๋ฉด user๊ฐ ์๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
@ConnectedSocket() socket: Socket & {user: UsersModel},
) {
const chat = await this.chatsService.createChat(
data,
);
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธ๋ฅผ ํ๊ฒ ์ต๋๋ค. ๋ก๊ทธ์ธ์ ํ์ง ์๊ณ create_chat์ ๋๋ฅด๋ฉด ์์ํ๋๋ก ๋์ต๋๋ค.
๋ก๊ทธ์ธ์ ํ๊ณ ์๋ต๋ฐ์ accessToken์ Headers์ ๋ฃ์ด์ฃผ๊ฒ ์ต๋๋ค.
๋ฐ๋ผ์ REST API์์ ์ ์ฉํ ๊ฒ์ฒ๋ผ ๋๊ฐ์ด Gateway์์๋ Guard๋ฅผ ์ ์ฉํ ์ ์๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
๋๋จธ์ง ๊ธฐ๋ฅ์ ์์ฑํ๊ฒ ์ต๋๋ค. enter_chat
์๋ Guard, Filter, Pipe๋ฅผ ์ ์ฉํ๊ฒ ์ต๋๋ค. ๋ก๊ทธ์ธ์ ํด์ผ๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋๋ก ๋ง๋ค๊ฒ ์ต๋๋ค.
๋ํ send_message
์๋ ๋์ผํ๊ฒ ์ ์ฉ์ ํ๊ฒ ์ต๋๋ค.
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@UseGuards(SocketBearerTokenGuard)
@SubscribeMessage('send_message')
async sendMessage(
.
.
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@UseGuards(SocketBearerTokenGuard)
@SubscribeMessage('enter_chat')
async enterChat(
์ถ๊ฐ์ ์ผ๋ก CreateMessageDto์์ authorId๋ฅผ number๋ก ๋ฐ๊ณ ์์์ต๋๋ค. ์ด ๋ํ ์ ๊ฑฐ๋ฅผ ํ๊ณ ์ง์ ๋ฐ๋๋ก ํ๊ฒ ์ต๋๋ค.
import { PickType } from "@nestjs/mapped-types";
import { MessagesModel } from "../entities/messages.entity";
import { IsNumber } from "class-validator";
export class CreateMessagesDto extends PickType(MessagesModel, [
'message',
]) {
@IsNumber()
chatId: number;
// ์ ๊ฑฐ
}
async createMessage(
dto: CreateMessagesDto,
authorId: number
) {
const message = await this.messagesRepository.save({
chat: {
id: dto.chatId,
},
author: {
id: authorId, // ๋ณ๊ฒฝ
},
message: dto.message,
});
return this.messagesRepository.findOne({
where: {
id: message.id,
},
relations: {
chat: true,
}
});
}
@UsePipes(new ValidationPipe({
transform: true, // ๋ณํ๋ ํด๋ ๋๋ค.
transformOptions: {
enableImplicitConversion: true // ์์๋ก ๋ณํํ๋ ๊ฒ์ ํ๊ฐํ๋ค.
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@UseGuards(SocketBearerTokenGuard)
@SubscribeMessage('send_message')
async sendMessage(
@MessageBody() dto: CreateMessagesDto,
@ConnectedSocket() socket: Socket & {user: UsersModel}, // ๋ณ๊ฒฝ
) {
const chatExists = await this.chatsService.checkIfChatExists(
dto.chatId,
);
if (!chatExists) {
throw new WsException(
`์กด์ฌํ์ง ์๋ ์ฑํ
๋ฐฉ์
๋๋ค. Chat ID : ${dto.chatId}`,
);
}
const message = await this.messagesService.createMessage(
dto,
socket.user.id
);
socket.to(message.chat.id.toString()).emit('receive_message', message.message);
}
๋ค์๊ณผ ๊ฐ์ด ๋ฐ๊พธ๋ ์ด์ ๋ ์์ธ์ค ํ ํฐ์ผ๋ก ๋ถํฐ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฐ์์ค๊ธฐ ์ํด์ ์ ๋๋ค.
์ง๊ธ๋ถํฐ๋ accessToken์ ์ ์ฉํ๋ ๋ฌธ์ ์ ๋ํด์ ์์๋ณด๊ฒ ์ต๋๋ค.
๋ก๊ทธ์ธ์ ํ๊ฒ ๋๋ฉด accessToken๊ณผ refreshToken์ ์ ๊ณตํฉ๋๋ค. accessToken์ ๊ฒฝ์ฐ 5๋ถ์ ๋๋ค. ๊ทธ ์ดํ์๋ ๋ง๋ฃ๊ฐ ๋์ด์ ๋ค์ refreshToken์ผ๋ก ์ฌ๋ฐ๊ธ์ ๋ฐ์์ผํฉ๋๋ค.
ํฌ์คํธ๋งจ์ ๊ฒฝ์ฐ connection์ ํ๊ฒ๋๋ฉด ๋ฐ๊ฟ ์ ์๊ฒ ๋์ด๋ฒ๋ฆฝ๋๋ค.
๊ทธ๋ผ 5๋ถ ์์ผ๋ก๋ ์ํต์ด ๊ฐ๋ฅํ์ง๋ง, ๊ทธ ์ดํ ๋ง๋ฃ๊ฐ ๋๋ฉด ํ ํฐ์ด ์ ํจํ์ง ์์ต๋๋ค.
์๋ฌ๋ฅผ ๋์ง๊ฒ ๋ฉ๋๋ค. ๊ทธ๋์ ๋งค๋ฒ ๋ฌด์์ธ๊ฐ๋ฅผ ๊ฒ์ฆ์ ํ๋ค๋ฉด Guard
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ง์ต๋๋ค.
ํ์ง๋ง Bearer Token
๊ฒ์ฆ์ ํตํด ์ฌ์ฉ์์์ socket๊ณผ ์ฐ๊ฒฐ์๋ ๋งค๋ฒ Guard
๋ฅผ ์ด์ฉํ ๊ฒ์ฆ์ ํ ํ์๊ฐ ์์ต๋๋ค.
์ด์ ๋ถํฐ ์ฌ์ฉ์๋ฅผ ์ด๋ป๊ฒ ๊ฐ๊ฐ์ ์์ผ๊ณผ ์ฐ๊ฒฐํ๋์ง๋ฅผ ์์๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ chats.gateway.ts
์ ์๋ Guard
๋ฅผ ๋ชจ๋ ์ง์์ค๋๋ค.
@WebSocketGateway({
// ws:localhost:3000/chats
namespace: '/chats',
})
export class ChatsGateway implements OnGatewayConnection{
constructor(
private readonly chatsService: ChatsService,
private readonly messagesService: ChatsMessagesService
){}
@WebSocketServer()
server: Server;
handleConnection(socket: Socket) {
console.log(`on connect called : ${socket.id}`);
}
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@SubscribeMessage('enter_chat')
async enterChat(
@MessageBody() data: EnterChatDto,
@ConnectedSocket() socket: Socket & {user: UsersModel},
) {
for (const chatId of data.chatIds) {
const exists = await this.chatsService.checkIfChatExists(
chatId,
);
if (!exists) {
throw new WsException({
code: 100,
message: `์กด์ฌํ์ง ์๋ chat ์
๋๋ค. chatId: ${chatId}`,
});
}
}
socket.join(data.chatIds.map((x) => x.toString()));
}
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@SubscribeMessage('create_chat')
async createChat(
@MessageBody() data: CreateChatDto,
@ConnectedSocket() socket: Socket & {user: UsersModel},
) {
const chat = await this.chatsService.createChat(
data,
);
}
@UsePipes(new ValidationPipe({
transform: true,
transformOptions: {
enableImplicitConversion: true
},
whitelist: true,
forbidNonWhitelisted: true,
}))
@UseFilters(SocketCatchHttpExceptionFilter)
@SubscribeMessage('send_message')
async sendMessage(
@MessageBody() dto: CreateMessagesDto,
@ConnectedSocket() socket: Socket & {user: UsersModel},
) {
const chatExists = await this.chatsService.checkIfChatExists(
dto.chatId,
);
if (!chatExists) {
throw new WsException(
`์กด์ฌํ์ง ์๋ ์ฑํ
๋ฐฉ์
๋๋ค. Chat ID : ${dto.chatId}`,
);
}
const message = await this.messagesService.createMessage(
dto,
socket.user.id
);
socket.to(message.chat.id.toString()).emit('receive_message', message.message);
}
}
Guard๋ฅผ ์ง์ฐ๊ณ ์ด๋ป๊ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์ฌ ์ ์์๊น?
Socket์ด๋ผ๋ ๊ฒ์ ์ฐ๊ฒฐ์ด ๋๋ฉด ์๋ก Pipe๊ฐ์ ๊ฒ์ด ์๊น๋๋ค. ๊ทธ๋์ ํ๋ฒ ์ฐ๊ฒฐ๋์ด ์์ผ๋ฉด ๊ทธ ์ฐ๊ฒฐ์ ์ง์์ด ๋ฉ๋๋ค.
async handleConnection(socket: Socket & {user: UsersModel}) {
console.log(`on connect called : ${socket.id}`); // ์ด๋ค id ์์ผ์ด ์ฐ๊ฒฐ๋จ?
// ์์ ํ
์คํธ
const user = await this.usersService.getUserByEmail('codefactory@codefactory.ai');
socket.user = user; // ์์ผ์ ์ฌ์ฉ์ ์ ๋ณด ๋ฃ๊ธฐ
}
๋ฐ๋ผ์ 1๋ฒ ์ฐ๊ฒฐ๋๊ณ ๋๋ฉด socket์๋ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ผ ๋ ๊ณ์ํด์ ์ ์ง๊ฐ ๋ฉ๋๋ค. ์ด๊ฒ์ ๋๋ฒ๊น ์ ํตํด์ socket์ ๋ค์ด์๋ ๊ฐ์ ํ์ธํด๋ณด๋ฉด ์ ์ ์์ต๋๋ค.
๋ฐ๋ผ์ REST API์ฒ๋ผ ๋ชจ๋ ๊ณณ์ Guard๋ฅผ ๋ถ์ผ ํ์ ์์ด, handleConnection
์์ ํธ๋ค๋ง๋ง ํด์ฃผ๊ณ socket.user = user;
๋ก ๋ฃ์ด์ฃผ๋ฉด, ๋๋จธ์ง ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ผ ๋ ์์ผ์ ๋ค์ด์๋ ๊ฐ์ ์ ๋ขฐํ ์ ์๊ฒ ๋ฉ๋๋ค.
๋ํ ์ฌ๊ธฐ์๋ ์ค๊ฐ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์๋ฌ๋ฅผ throw ํ๋ ๊ฒ๋ณด๋ค๋ ์ฐ๊ฒฐ์ ๋์ด์ฃผ๊ฒ ์ต๋๋ค.
async handleConnection(socket: Socket & {user: UsersModel}) {
console.log(`on connect called : ${socket.id}`); // ์ด๋ค id ์์ผ์ด ์ฐ๊ฒฐ๋จ?
const headers = socket.handshake.headers; // ํค๋ ๊ฐ์ ธ์ค๊ธฐ
const rawToken = headers['authorization']; // Bearer xxx
if (!rawToken) socket.disconnect(); // ํ ํฐ์ด ์์ผ๋ฉด ์ฐ๊ฒฐ ๋๊ธฐ
try {
const token = this.authService.extractTokenFromHeader(
rawToken,
true
);
const payload = this.authService.verifyToken(token);
const user = await this.usersService.getUserByEmail(payload.email);
socket.user = user;
return true;
} catch (error) {
socket.disconnect(); // ์๋ฌ๊ฐ ๋๋ฉด ์ฐ๊ฒฐ ๋๊ธฐ
}
}
ํฌ์คํธ๋งจ์ผ๋ก ๋ก๊ทธ์ธ์ ํ๊ณ ํ ํฐ์์ด connect๋ฅผ ํ๋ฉด ๋ฐ๋ก ์ฐ๊ฒฐ์ด ๋๊ธฐ๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
์ฐ๊ฒฐ๋์์ ๋ handleconnection์ ํตํด์ ํน์ ๊ธฐ๋ฅ์ด ๋ฐ์ํ๋๋ก ๋ง๋ค์์ต๋๋ค. ์ด๊ฒ์ Lifecycle Hooks
์ด๋ผ๊ณ ํฉ๋๋ค. Lifecycle Hooks
์ 2๊ฐ๊ฐ ๋ ์์ต๋๋ค.
OnGatewayInit
์
๋๋ค. OnGatewayInit
๋ afterInit
ํจ์๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ๋ง๋ค์ด ์ค๋๋ค. ์ด ํจ์๋ ์ค์ ์๋ฒ๋ฅผ inject ๋ฐ์ ๋ ์ฌ์ฉ๋๋ ํจ์์ด๊ธฐ๋ ํ๊ณ , ๊ทธ๋ฆฌ๊ณ Gateway๊ฐ ์ด๊ธฐํ๋์์ ๋ ์คํํ ์ ์๋ ํจ์ ์
๋๋ค.
export class ChatsGateway implements OnGatewayConnection, OnGatewayInit{ // ์ถ๊ฐ
constructor(
private readonly chatsService: ChatsService,
private readonly messagesService: ChatsMessagesService,
private readonly usersService: UsersService,
private readonly authService: AuthService,
){}
@WebSocketServer()
server: Server;
afterInit(server: any) { // server: Server; ์ด๊ฒ๊ณผ ๋์ผํ ๊ฐ์ ๋ฐ๋๋ค.
console.log(`after gateway init`); // ๋ก๊ทธ ํ์ธ
}
after gateway init
์ดํ๋ก ์์ผ ๋ฉ์ธ์ง๋ค์ด ๊ตฌ๋
๋๊ฒ์ ์ ์ ์์ต๋๋ค. ๊ทธ๋์ Gateway๊ฐ ์์ํ์ ๋ ํน์ ํจ์ ๋๋ ๋ก์ง์ ์คํํ๊ณ ์ถ์ผ๋ฉด afterInit
์ด๋ผ๋ Lifecycle Hooks
์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
๋ค์์ ์ฐ๊ฒฐ์ด ๋๊ธด ์ดํ๋ฅผ ๊ด๋ฆฌํ๋ OnGatewayDisconnect
์
๋๋ค.
export class ChatsGateway implements OnGatewayConnection, OnGatewayInit, OnGatewayDisconnect{
.
.
handleDisconnect(socket: Socket) {
console.log(`on disconnect called : ${socket.id}`);
}
ํฌ์คํธ๋งจ์ผ๋ก ์ฐ๊ฒฐ ํ disconnect๋ฅผ ํ๊ฒ ์ต๋๋ค.