SSE (Server Sent-Event)

saewoong jeon·2022년 12월 2일
3
post-thumbnail

FE 화면을 구성하다 보면 지속적으로 데이터를 갱신해야 하는 요구사항이 생기고는 합니다.

일반적으로 실시간성 데이터 갱신을 구현하기 위해서는 아래 두 가지을 고민 하게 됩니다.

  1. Polling / Long Polling
  2. WebSocket API

저 역시 프로젝트를 진행하며 데이터 갱신, 보다 정확히는 차트 데이터의 갱신 및 알림 기능을 구현해야 하는 요구 사항이 생겼습니다.

몇 가지 특징 들을 살펴본 뒤 SSE (Server Sent Event)를 통하여 해당 기능을 구현하고 있습니다.

SSE (Server Sent-Event)

https://blog.logrocket.com/wp-content/uploads/2021/12/server-sent-events.png

SSE는 서버의 Event를 stream 하는 기술입니다. 일반적 HTTP 통신이 요청에 따른 데이터를 제공한 뒤 연결을 끊는 것과는 달리, 초기 서버 이벤트를 구독해 놓으면, 서버에서는 지정한 이벤트가 발생 할 때마다 Client로 데이터를 보낼 수 있습니다.

특징은 함께 많이 거론되는 Polling, Websocket 과 비교하여 정리해 보았습니다.

vs Polling

  • Polling 방식은 요약하면 ‘브라우저에서 요청을 주기적, 반복적으로 보내는 것’ 입니다.
  • 초기 스트림 구독 외에는 프론트에서 서버로 요청을 보낼 필요가 없기에 서버 및 브라우저의 자원을 절약 할 수 있습니다.

vs WebSocket API

  • Web Socket은 서버와의 양방향 통신을 제공하며, Web Socket을 위한 별도의 서버 및 프로토콜 처리가 필요합니다.
  • SSE는 Web Socket과 달리 단방향성 통신입니다. 서버로부터 일방적으로 메시지를 받지만, 서버에 응답을 보낼 수는 없습니다.
  • 대신 HTML 표준 스펙에 정의 되어 있고 전통적인 HTTP 통신을 사용하기 때문에 이벤트 구독과 서버의 header 설정 외에는 별도의 작업이 필요하지 않습니다.

ETC

  • IE를 제외한 대부분의 브라우저에서 지원합니다.
  • IE는 놓아주어야..

Server (Node.js - express)

const express = require("express");
const cors = require("cors");
const app = express();
app.use(cors());

let counter = 0;

const headers = {
  "Content-Type": "text/event-stream",
  Connection: "keep-alive",
  "Cache-Control": "no-cache",
};
app.get("/subscribe", (req, res) => {
  console.log("request received");
  res.writeHead(200, headers);
  setInterval(async () => {
    res.write("event: notification\n");
    res.write(
      `data: ${JSON.stringify({
        text: counter,
        date: new Date().toDateString(),
      })}`
    );
    res.write("\n\n");
    counter++;
  }, 2000);
});
app.listen(4000, () => {
  console.log("listening...");
});

server는 간단하게 express로 테스트해 보았습니다. /subscribe 를 통해 구독요청을 한번만 보내면 지속적으로 데이터를 보낼 수 있습니다.

const headers = {
  "Content-Type": "text/event-stream",
  Connection: "keep-alive",
  "Cache-Control": "no-cache",
};

위와 같이 sse연결이라는 것을 나타내기 위해 "Content-Type": "text/event-stream" 으로 header를 설정해 주어야 합니다.

Client (React)

const [listening, setListening] = useState(false)
  const [msgs, setMsgs] = useState<INotification[]>([])
  useEffect(() => {
    if (!listening) {
      setListening(true)
      const eventSource = new EventSource(`http://localhost:4000/subscribe`)
      eventSource.onopen = (e) => {
        console.log('open')
      }

      eventSource.addEventListener('notification', (e) => {
        const data = JSON.parse(e.data as string) as INotification
        setMsgs((prev) => [data, ...prev])
      })
    }
  }, [listening])

React에서의 구현입니다. JS에서는 SSE를 구현하기 위한 EventSource interface를 제공합니다. html 객체의 이벤트를 다루듯 서버에서 발생하는 이벤트에 대한 데이터를 핸들링 할 수 있습니다.

onopen, onmessage, onerror 등의 메소드를 제공하며 addEventListener 를 사용하여 특정 토픽에 대한 이벤트를 선택하여 구독 할 수 있습니다.

연결이 끊을 스토리가 없는 화면이기에 연결을 끊는 코드는 생략했습니다.

Chrome 브라우저 network탭에서 이벤트 스트림 데이터를 확인 할 수 있습니다.\

결론

sse는 실시간 데이터가 필요한 경우, 특히 제 경우 처럼 서버로부터 데이터를 받기만 하는 시나리오에서 유용하게 사용할 수 있는 인터페이스인 것 같습니다.

socket.io 등을 주로 사용하는 웹소켓 구현에 비하여 FE,BE 모두 구현 복잡도가 많이 낮으며, 스펙상으로 성능 또한 socket 통신에 비해여 우수하다고 알려져 있기 때문에 실시간 통신에 있어서 우선적으로 검토해야할 기술이라는 생각했습니다.


Reference

Using Fetch Event Source for server-sent events in React - LogRocket Blog

HTML

Stream Updates with Server-Sent Events

0개의 댓글