
nestjs에서 socket io를 활용해 채팅기능을 구현하려면 먼저 알아야 할 것이 있다.
nestjs의 공식문서에도 나와있는 gateway이다.
https://docs.nestjs.com/websockets/gateways
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea
docs.nestjs.com
🐕 nestjs에서 socket io 사용시 폴더 구조
module.ts
controller.ts
server.ts
entitiy.ts
이외 추가적으로
dto
type
🐶 설정 코드
//main.ts
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
const corsOptions: CorsOptions = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
};
app.enableCors(corsOptions);
//소켓 어뎁터로 연결(nest에서 웹소켓을 사용할 수 있도록)
app.useWebSocketAdapter(new IoAdapter(app));
//app.module
@Module({
imports: [
MongooseModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
uri: configService.get<string>('MONGO_URL'),
useNewUrlParser: true,
useUnifiedTopology: true,
}),
}),
//chat.module.ts
@Module({
imports: [MongooseModule.forFeature([{ name: Chat.name, schema: ChatSchema }]), UserModule, AuthModule],
controllers: [],
providers: [ChatService, ChatGateway],
})
export class ChatModule {}
mongodb 를 사용하지만 스케마를 지정해 주었다.
//chat.schema.ts
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { number } from 'joi';
import { Date, HydratedDocument, ObjectId } from 'mongoose';
import { ObjectIdColumn } from 'typeorm';
export type ChatDocument = HydratedDocument<Chat>;
@Schema({
collection: 'chat',
timestamps: true,
})
export class Chat {
@ObjectIdColumn()
_id: ObjectId;
@Prop()
userId: number;
@Prop()
liveId: string;
@Prop({ required: true })
content: string;
}
export const ChatSchema = SchemaFactory.createForClass(Chat);
🪴 @WebSocketGateway
이 데코레이터를 사용해서 실시간 양방향 통신이 가능하도록 돕는다.
따라서 최상단에 걸어주면 된다.
@WebSocketGateway({
cors: {
//origin: ['ws://localhost:3002/api/live'],
origin: '*',
},
})
🦊 채팅방 입장 기능
프론트에서 페이지가 로드되면 채팅방에 입장하도록 구현했다.
시청하고자 하는 라이브 방송을 클릭하면 채팅기능도 같이 켜져야 하기 때문이다.
//chat.gateway.ts
@WebSocketGateway({
cors: {
//origin: ['ws://localhost:3002/api/live'],
origin: '*',
},
})
@UseGuards(WsGuard)
export class ChatGateway {
@WebSocketServer() server: Server;
constructor(private readonly chatService: ChatService) {}
@SubscribeMessage('enter_room')
async enterLiveRoomChat(client: Socket, liveId: string): Promise<EnterRoomSuccessDto> {
const chat = await this.chatService.enterLiveRoomChat(liveId, client);
return {
statusCode: 200,
message: '채팅방 입장 성공',
};
}
@SubscribeMessage('new_message')
async createChat(client: Socket, [value, liveId]: [value: string, liveId: string]) {
const saveChat = await this.chatService.createChat(client, value, liveId);
this.server.to(liveId).emit('sending_message', saveChat.content, client.handshake.auth.user.nickname);
}
@SubscribeMessage('get_all_chat_by_liveId')
async getAllChatByLiveId(client: Socket, liveId: string) {
const socketId = client.id;
const messages = this.chatService.getAllChatByLiveId(liveId);
}
}
여기서 처음 소켓 연결부분이 어떻게 해야할까 싶었는데
@WebSocketServer() server: Server;
이 코드가 바로
@WebsocketServer()는 websocket서버 인스턴스를 해당 속성에 자동으로 할당하고,
소켓 연결, 소켓 해제 등의 이벤트를 처리하는 데 사용되는 코드이다.
🐲 난관
채팅 기능은 되었는데 이제 유저 닉네임을 가져와야 했다.
userInfo를 팀원분이 만들어두셔서 그걸 활용해서 가져오려고 했지만 왜인지 계속 인식되지 않았다.
알고보니 http 프로토콜과 ws 프로토콜은 달라서 http용으로 만든 UserInfo, AuthGuard 를 사용할 수 없었다.
ws 프로토콜 용으로 따로 만들어야 했던것!!!
어떻게 따로 만들어야 하나 싶어 기존 http 프로토콜에서 작성된 userInfo데코레이터를 참고해 만들어 봤지만 작동하지 않았다.
찾아보니,
ctx.switchToHttp().getRequest()`를 사용하는 것은
HTTP 요청이 아니라 WebSocket 컨텍스트에서는 예상대로 작동하지 않습니다.
WebSocket 컨텍스트에서는 HTTP 요청이 없기 때문입니다.
따라서 switchToWs로 바꿨다.
그렇지만 여전히 작동하지 않았고, wsGuard를만드는 방법에 대해 좀더 알아보아야 겠다고 생각해서
열심히 검색했다. 결국 스텍오버플로우 에서 참조할만한 코드를 찾았는데,
How to use guards with NestJs gateway(web sockets)?
In my NestJS API, I'm JWT token, stored in a cookie to authenticate my users. The user will have to call my login controller: @UseGuards(LocalAuthenticationGuard) @Post('login') async logIn(@...
stackoverflow.com
해당 코드를 참고하면서 작성했다.
실행컨텍스트가 switchToHttp가 아니라 switchToWs 라는 점이 달랐고,(미리 찾아보고 작성할껄 싶었따...ㅎㅎ)
해당 데이터를 넣고 가져오는 부분도 약간 달랐다.
열심히 작성해서 아래와 같이 작성 했는데, 또 작동하지 않았다.
이 함수에 콘솔들은 다 찍히는데, gateway.ts파일에 다음 내용들로 넘어가질 못했다.
//WsGuard.ts
@Injectable()
export class WsGuard implements CanActivate {
constructor(
private userService: UserService,
private readonly configService: ConfigService,
) {}
private readonly secretKey = this.configService.get<string>('JWT_SECRET_KEY');
canActivate(context: ExecutionContext): any {
const token = context.switchToWs().getClient().handshake.headers.cookie.split('=')[1];
console.log('token', token, context.switchToWs().getClient().handshake.headers);
try {
const verifyToken = async (token: string): Promise<any> => {
const verify = jwt.verify(token, process.env.JWT_SECRET_KEY);
console.log('verify', verify, typeof verify.sub);
const userId = verify.sub;
if (!verify) {
throw new UnauthorizedException('인증이 유효하지 않습니다. ');
} else {
const findUser = await this.userService.findByKakaoIdGetUserName(+userId);
context.switchToWs().getClient().handshake.auth.user = findUser;
return true;
}
};
verifyToken(token);
} catch (err) {
console.log(err); return false;
}
}
}
사실 되게 간단한 문제였는데, socket io와 nestjs에서 어떻게 사용해야 할지 한참 헤맸던 터라
그런 종류에 문제라고 나도 모르게 생각해 찾는데 어려웠다.
사실 verifyToken(token)이 비동기 적으로 호출되지 않았고,
canActivate도 비동기 처리를 하지 않아서 발생한 문제였다.
비동기 처리에 대해 잘 알고있다고 생각했는데,
막상 이 부분때문에 꽤 해매고 나니 다시 찾아보게 되었다.
🏞️ 비동기 함수 사용하는 경우
- 외부 서비스 호출: 다른 서버로 HTTP 요청을 보내거나, 데이터베이스와의 통신을 위해 필요하다. 데이터를 가져오는 동안 애플리케이션이 다른 작업을 수행할 수 있도록 한다.
- 파일 I/O: 파일을 읽거나 쓸 때 비동기 작업이 필요하며, 파일 시스템 작업은 시간이 오래 걸릴 수 있으므로 블로킹하지 않도록 비동기로 수행하는 것이 좋다.
- 타이머 또는 이벤트 리스너: 특정 시간이나 이벤트가 발생할 때까지 기다리는 작업은 비동기로 처리해야 한다.
- 병렬 작업: 여러 작업을 동시에 수행해야 할 때 비동기 작업을 사용한다. 이는 성능을 향상시키고 블로킹을 방지하는 효과가 있다.
서비스호출, 통신, 파일읽기, 병렬작업, 이벤트 등의 경우에는 비동기 !! 잊지 말자
'TIL' 카테고리의 다른 글
24/01/23 TIL __ 채팅기능 캐싱하기(redis) (0) | 2024.01.23 |
---|---|
24/01/22 TIL __ 채팅기능에 금칙어 기능 추가 ( aho-corasick 라이브러리) (0) | 2024.01.22 |
24/01/20 TIL __ nestjs에서 채팅기능(socket io)구현시 데이터베이스 선택 (0) | 2024.01.20 |
24/01/19 TIL __ ejs 에서 js파일 인식이 안될때(nest.js) (0) | 2024.01.19 |
24/01/18 TIL __ 웹소켓 (0) | 2024.01.18 |