Event-Source-Polyfill로 모든 브라우저에서 SSE 구현하기

루비·2024년 5월 20일
1

Server-Sent Events (SSE) 통신 로직

Server-Sent Events (SSE)는 클라이언트가 서버로부터 실시간 업데이트를 수신할 수 있도록 하는 서버 푸시 기술입니다. HTTP를 통해 단방향 통신을 수행하며, 주로 실시간 피드나 알림 시스템에 사용됩니다. SSE는 이벤트 소스를 통해 클라이언트가 서버로부터 지속적으로 데이터를 받을 수 있게 합니다.

이유

  1. 실시간 업데이트: SSE는 클라이언트가 실시간으로 데이터를 수신할 수 있게 해줍니다. 이는 주식 시세, 스포츠 점수, 뉴스 피드 등 실시간 정보 업데이트에 유용합니다.
  2. 간단한 설정: 웹소켓에 비해 설정과 사용이 간단하며, HTTP/1.1 프로토콜을 사용하므로 방화벽 문제를 쉽게 회피할 수 있습니다.
  3. 자동 재연결: SSE는 연결이 끊어지면 자동으로 재연결을 시도하므로, 네트워크 장애 상황에서도 안정적인 연결을 유지할 수 있습니다.
  4. 저렴한 리소스: 웹소켓보다 리소스 소모가 적어 서버 부하가 적습니다.

Next.js + TypeScript 환경에서 SSE 사용 예제

1. 서버 코드 (Next.js API Route)

// pages/api/events.ts
import { NextApiRequest, NextApiResponse } from 'next';

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const sendEvent = (data: string) => {
    res.write(`data: ${data}\n\n`);
  };

  const interval = setInterval(() => {
    const message = JSON.stringify({ time: new Date().toISOString() });
    sendEvent(message);
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
};

export default handler;

2. 클라이언트 코드

// components/RealTimeUpdates.tsx
import React, { useEffect, useState } from 'react';

const RealTimeUpdates: React.FC = () => {
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    const eventSource = new EventSource('/api/events');

    eventSource.onmessage = (event) => {
      const newMessage = JSON.parse(event.data);
      setMessages((prevMessages) => [...prevMessages, newMessage.time]);
    };

    return () => {
      eventSource.close();
    };
  }, []);

  return (
    <div>
      <h1>Real-Time Updates</h1>
      <ul>
        {messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default RealTimeUpdates;

통신 프로세스 설명

  1. 클라이언트 초기화: 클라이언트가 EventSource 객체를 생성하여 서버의 SSE 엔드포인트(/api/events)에 연결을 시도합니다.
  2. 서버 설정: 서버는 Content-Typetext/event-stream으로 설정하고, 연결을 유지하기 위해 Cache-ControlConnection 헤더를 설정합니다.
  3. 데이터 전송: 서버는 일정 시간 간격으로 클라이언트에게 이벤트 데이터를 전송합니다. 이 예제에서는 1초마다 현재 시간을 JSON 형식으로 전송합니다.
  4. 이벤트 수신: 클라이언트는 서버로부터 이벤트를 수신하고, onmessage 이벤트 핸들러를 통해 수신된 데이터를 처리합니다.
  5. 연결 종료: 클라이언트가 페이지를 떠나거나 연결이 종료되면 EventSource 객체는 close 메소드를 호출하여 연결을 종료합니다.

추가로 알아야 할 SSE 개념 및 토픽

  1. Event ID 및 Last-Event-ID 헤더: SSE는 이벤트의 순서를 보장하기 위해 event ID를 사용할 수 있습니다. 클라이언트는 Last-Event-ID 헤더를 통해 마지막으로 수신한 이벤트 ID를 서버에 전달하여, 연결이 재설정된 경우에도 데이터 손실 없이 이어서 받을 수 있습니다.

  2. Custom Event Types: 기본 이벤트 타입 외에도, 커스텀 이벤트 타입을 정의할 수 있습니다. 서버에서 특정 이벤트 타입을 설정하고, 클라이언트에서는 해당 타입에 대한 핸들러를 설정하여 다양한 이벤트를 처리할 수 있습니다.

    // 서버에서 이벤트 타입 설정
    res.write(`event: customEvent\n`);
    res.write(`data: ${JSON.stringify({ message: 'Custom Event Data' })}\n\n`);
    
    // 클라이언트에서 핸들러 설정
    eventSource.addEventListener('customEvent', (event) => {
      console.log(event.data);
    });
  3. Retry Mechanism: SSE는 자동으로 재연결을 시도하며, 기본 재연결 간격은 3초입니다. 서버는 retry 필드를 통해 클라이언트의 재연결 간격을 조정할 수 있습니다.

    res.write(`retry: 5000\n`); // 5초 후 재연결 시도
  4. 브라우저 지원 및 폴리필: 대부분의 현대적인 브라우저는 SSE를 지원하지만, Internet Explorer와 같은 구형 브라우저에서는 지원되지 않을 수 있습니다. 이런 경우 폴리필 라이브러리를 사용하여 호환성을 보장할 수 있습니다.

    <!-- 예: eventsource-polyfill -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/event-source-polyfill/0.0.9/eventsource.min.js"></script>

Event-Source-Polyfill 라이브러리 활용 이유

event-source-polyfill 라이브러리를 사용하는 이유는 주로 다음과 같은 문제들을 해결하기 위함입니다:

  1. 브라우저 호환성: 모든 브라우저가 SSE(Server-Sent Events)를 기본적으로 지원하지 않습니다. 특히, Internet Explorer (IE)와 구형 버전의 Microsoft Edge는 SSE를 지원하지 않습니다. event-source-polyfill 라이브러리는 이러한 브라우저에서도 SSE 기능을 사용할 수 있게 해줍니다.
  2. 일관된 API 제공: 브라우저마다 SSE 구현 방식에 약간의 차이가 있을 수 있습니다. 폴리필 라이브러리를 사용하면, 모든 브라우저에서 일관된 API를 제공받을 수 있습니다.
  3. 향상된 안정성: 폴리필은 원래 API의 버그나 결함을 보완할 수 있습니다. 이는 SSE를 사용하는 애플리케이션의 안정성을 높여줍니다.

Event-Source-Polyfill을 사용한 코드 예제

1. 서버 코드 (Next.js API Route)

이 부분은 기존과 동일합니다. 변경 없이 사용합니다.

// pages/api/events.ts
import { NextApiRequest, NextApiResponse } from 'next';

const handler = (req: NextApiRequest, res: NextApiResponse) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const sendEvent = (data: string) => {
    res.write(`data: ${data}\n\n`);
  };

  const interval = setInterval(() => {
    const message = JSON.stringify({ time: new Date().toISOString() });
    sendEvent(message);
  }, 1000);

  req.on('close', () => {
    clearInterval(interval);
    res.end();
  });
};

export default handler;

2. 클라이언트 코드 (폴리필 적용)

HTML 파일에서 event-source-polyfill을 로드한 후, TypeScript 파일에서 사용합니다.

HTML 파일

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>SSE with Polyfill</title>
</head>
<body>
  <div id="root"></div>
  <!-- Load the polyfill for EventSource -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/event-source-polyfill/0.0.9/eventsource.min.js"></script>
  <script src="/main.js"></script>
</body>
</html>

TypeScript 파일

// components/RealTimeUpdates.tsx
import React, { useEffect, useState } from 'react';

const RealTimeUpdates: React.FC = () => {
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    // Check if the polyfill is loaded and replace the global EventSource if necessary
    const EventSourcePolyfill = (window as any).EventSourcePolyfill || EventSource;
    const eventSource = new EventSourcePolyfill('/api/events');

    eventSource.onmessage = (event: MessageEvent) => {
      const newMessage = JSON.parse(event.data);
      setMessages((prevMessages) => [...prevMessages, newMessage.time]);
    };

    eventSource.onerror = (error) => {
      console.error('EventSource failed:', error);
      eventSource.close();
    };

    return () => {
      eventSource.close();
    };
  }, []);

  return (
    <div>
      <h1>Real-Time Updates</h1>
      <ul>
        {messages.map((message, index) => (
          <li key={index}>{message}</li>
        ))}
      </ul>
    </div>
  );
};

export default RealTimeUpdates;

요약

event-source-polyfill 라이브러리는 브라우저 호환성 문제를 해결하고, 모든 브라우저에서 일관된 SSE 경험을 제공하는데 유용합니다. 위 예제에서는 HTML 파일에 폴리필 스크립트를 포함시키고, TypeScript 파일에서 폴리필을 사용하여 SSE 기능을 구현했습니다. 이렇게 하면 모든 주요 브라우저에서 안정적으로 SSE를 사용할 수 있습니다.

profile
개발훠훠

0개의 댓글