SSE(Server Side Event)란 서버에서 클라이언트로 실시간 데이터를 보내는 기술이다. HTTP 통신은 일회성 요청이기 때문에 이후의 서버에서 데이터를 클라이언트로부터 요청받기 전까지는 응답할수 없다.
하지만 SSE를 이용하면 서버에서 클라이언트로 데이터를 실시간으로 응답할수 있다. 해당 데이터 흐름은 서버에서 클라이언트로 이동하는 단방향 통신이며 역으로는 통신할수 없다.
장점
1. WebSocket, Poling, LongPoling를 이용하지 않고도 서버에서 클라이언트에 데이터를 전송할수 있다.
2. HTTP의 GET요청을 이용해서 보내기 때문에 기존의 HTTP 프로토콜을 이용하면 된다.
3. 네트워크가 종료 되어도 다시 자동으로 연결을 시도한다.
4. HTTP의 GET요청을 이용해서 지연시간을 조절할수 있다. 예로들어 SSE의 시간을 1시간으로 하면 1시간 후에 SSE가 해제된다.
단점
1. SSE로 응답하는 클라이언트가 많을경우 서버에 부하가 가해진다.
2. GET 메소드만 지원해서 전달 가능한 파라메타가 제한이 된다.
3. 클라이언트에서 페이지를 닫아도 서버에서 알수가 없다.
4. 한번 보낸 데이터는 취소가 불가능하다.
위와 같은 장단점을 갖고있다. 그래도 WebSocket, SocketIO를 이용해서 작성하는 것보다 SSE가 더 간단히 작성할수 있는 장점이 크다.
Postman을 이용해서 테스트가 가능하다.
먼저 "localhost:3000/sse/1"로 GET요청을 한다. 여기서 숫자 1은 클아이언트에서 수신받을 ID이고 이 ID로 서버가 SSE응답을 한다.

// /src/sse/sse.controller.ts
import { Controller, Get, MessageEvent, Param, Sse } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Observable, Subject, takeUntil, timer } from 'rxjs';
@Controller('sse')
export class SseController {
// Subject를 통해 id별로 클라이언트 스트림을 관리합니다.
private clients: Map<number, Subject<MessageEvent>> = new Map();
constructor(private readonly eventEmitter: EventEmitter2) {
// 이벤트가 발생할 때 특정 id로 메시지를 푸시합니다.
this.eventEmitter.on('triggerEvent', (id: number, message: string) => {
const clientStream = this.clients.get(id);
if (clientStream) {
clientStream.next({ data: message });
}
});
}
// 클라이언트가 특정 id로 SSE를 시작합니다.
@Sse(':id')
handleSse(@Param('id') id: number): Observable<MessageEvent> {
const stream$ = new Subject<MessageEvent>();
this.clients.set(id, stream$); // 클라이언트 연결 저장
// 일정 시간이 지나면 연결 종료 (예: 90초)
const timeout$ = timer(90000); // 90초 타임아웃
const observable$ = stream$.pipe(
takeUntil(timeout$), // 타임아웃이 되면 스트림 종료
);
timeout$.subscribe(() => {
console.log(`Connection with id ${id} closed due to timeout.`);
stream$.complete();
this.clients.delete(id); // 연결 종료 후 클라이언트 제거
});
return observable$;
}
// 클라이언트 연결 종료 시 id 제거
@Get('close/:id')
closeSse(@Param('id') id: number) {
const clientStream = this.clients.get(id);
if (clientStream) {
clientStream.complete();
this.clients.delete(id); // 연결이 종료되면 제거
}
return { message: `SSE connection for id ${id} closed.` };
}
}
클라이언트에서 GET요청후 일정시간(위 예시는 90초)동안 서버로 부터 메세지를 수신받을수 있습니다.
이때 "localhost:3000/card/send/1/hello_world"로 1번 ID로 등록한 클라이언트에 해당 메세지(hello_world)를 보낼수 있습니다.
// /src/card/card.controller.ts
import { Controller, Param, Post } from '@nestjs/common';
import { CardService } from './card.service';
@Controller('card')
export class CardController {
constructor(private readonly cardService: CardService) {}
@Post('send/:id/:message')
create(@Param('id') id: number, @Param('message') message = 'testing') {
return this.cardService.triggerSomeEvent(id, message);
}
}
// /src/card/card.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { EventService } from 'src/sse/event/event.service';
import { SseService } from 'src/sse/sse.service';
@Injectable()
export class CardService {
constructor(
@Inject(SseService)
private readonly sseService: SseService,
private readonly eventService: EventService,
) {}
triggerSomeEvent(id: number, message: string = 'test') {
this.eventService.triggerEvent(id, message);
}
async changeUser(id: number) {
// 카드 업데이트시 이벤트 발생
// this.sseService.emitCardChangeEvent(id);
this.sseService.sendEventToClient(id, `${id} send message`);
return {
message: `${id}번 카드의 SSE push.`,
};
}
}
현재 코드로는 90초 이후 해제되게 되어 있다.
해당 코드는 아래 gitHub 링크에서 확인이 가능하다.
GitHub Link : https://github.com/Alex-Choi0/NestJS_SSE_Example.git
댓글
댓글 쓰기