SSE(Server-Sent Event)

개요


백엔드를 주 개발을 하면서 뷰까지 개발하게 되면 `SSR`을 사용하게 되는데, 이러한 `SSR`을 사용하게 되면 문제는 실시간으로 반영되는 데이터를 유저에게 보여줄 수 없다는 것입니다. 그렇다면 유저는 당연하게도 원하는 데이터를 화면에서 보기 위해서 페이지 새로고침을 해야 한다는 것입니다. 이러한 문제를 해결하기 위해서 사용되는 방식은 `SSE`와 웹소켓인데 이번 그 중에서 `SSE`를 `Nest.js`로 구현하면서 이해 해보고자합니다.

 

 

SSE vs Web Socket


기술을 사용하기 전에 실시간 통신을 하기위해 사용되는 `SSE`와 `Web Socket`은 어떠한 차이가 있는지 알아 보고자 합니다.

 

SSE

  • `단방향 통신` : SSE는 오로지 서버에서 클라이언트 쪽 서버로만 데이터를 보낼 수 있는 구조이다.
  • `자동 재연결` : 서버와의 연결이 끊어지면 자동적으로 재연결을 시도한다.
  • `간단한 구조` : 클라이언트에서는 EventSource 라는 인터페이스를 사용하면 쉽게 데이터를 구현 할 수 있다. 

 

Web Socket

  • `양방향 통신` : 서버와 클라이언트 측 둘 다 데이터를 보낼 수 있는 구조이다. 
  • `수동 재연결` : 연결이 끊어진 경우 수동으로 재접을 해야 한다. 

 

이러한 특성을 파악하여 어떠한 서비스를 만들어야 하는가에 따라 `SSE`와 `Web Socket`을 선택적으로 활용하여 구현을 할 수 있도록 하면 되겠습니다.

 

 

코드 구현


`Nest.js` 프레임워크에서 `SSE`를 쉽게 구현 할 수 있도록 제공을 하고 있습니다. 컨트롤러에서 `@Sse()` 데코레이터를 사용하면 쉽게 구현이 가능토록 하였는데 어떻게 구현하는지 코드를 보도록 하겠습니다.

 

@Controller()
export class AppController {
  private dataStream = new Subject<any>();
  constructor(private readonly appService: AppService) {}

  @Sse('sse')
  sse(): Observable<MessageEvent> {
    return interval(1000).pipe(
      map((_) => ({ data: { hello: 'world' } } as MessageEvent)),
    );
  }
}

 

 

`rxjs`에서 제공하는 파이프 함수를 이용해 각 데이터를 매 초마다 전송하도록 하고 있습니다. 이러한 서버를 구축한 이후에는 클라이언트 측 서버에서 확인 할 수 있도록 코드를 작성하여야 합니다. 위에서 설명하였듯이 `EventSource`를 사용하면 쉽게 데이터를 얻을 수 있습니다. 

 

현재는 `static` 파일을 제공하는 모듈을 설치하고 `public/index.html` 파일을 유저에게 제공하는 서버를 구축을 우선 하여야 합니다. 

 

`static` 파일을 제공하기 위해서 `@nestjs/serve-static` 모듈을 설치 해야 합니다

npm install @nestjs/serve-static

 

 

그리고 `index.html` 파일내에 `script` 문을 작성해주어야 합니다. 

<script type="text/javascript">
  const eventSource = new EventSource('/sse');
  eventSource.onmessage = ({ data }) => {
    const message = document.createElement('li');
    message.innerText = 'New message: ' + data;
    document.body.appendChild(message);
  };
</script>

 

이러한 준비가 완료가 되면 서버가 가동되는 서버의 url에 index.html을 붙여 주게 되면 다음과 같은 화면을 볼 수 있습니다. 

 

매 초 마다 데이터를 보내게 되면서 이러한 결과를 볼 수 있겠습니다. 

 

 

이번에는 동적인 데이터를 SSE로 전달하는 방식을 보겠습니다. 

@Controller()
export class AppController {
  private dataStream = new Subject<any>();
  constructor(private readonly appService: AppService) {}

  @Post('data')
  getData(@Body() body: any) {
    this.dataStream.next({ data: body });

    return {
      data: body,
    };
  }

  @Sse('sse')
  sse(): Observable<MessageEvent> {
    return this.dataStream
      .asObservable()
      .pipe(map((data) => ({ data } as MessageEvent)));
  }
}

 

`Post`요청으로 `body`를 보내게 되면 해당 데이터를 `dataStream`을 통해 `SSE` 서버용 컨트롤러 까지 전달을 하게 됩니다. 

 

 

 

이러한 구조에 대한 이해를 하고 요구사항에 맞춰 사용할 수 있다면 유저에게 좀 더 좋은 경험과 기능을 제공하는 서비스를 제공할 수 있는 기술이라고 생각합니다. 

'Backend > Nest.js' 카테고리의 다른 글

Nest.js Bull  (0) 2023.12.05
Nest.js Integration Test 와 Mocking  (1) 2023.10.10
Nest.js mongodb-memory-server, cache 적용  (1) 2023.10.05
Nest.js Rate Limiting 설정  (0) 2023.09.30
Nest.js Swagger 보안 설정  (0) 2023.09.30