Server-Sent Events (SSE)는 클라이언트가 서버로부터 실시간 업데이트를 수신할 수 있도록 하는 서버 푸시 기술입니다. HTTP를 통해 단방향 통신을 수행하며, 주로 실시간 피드나 알림 시스템에 사용됩니다. SSE는 이벤트 소스를 통해 클라이언트가 서버로부터 지속적으로 데이터를 받을 수 있게 합니다.
// 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;
// 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;
EventSource
객체를 생성하여 서버의 SSE 엔드포인트(/api/events
)에 연결을 시도합니다.Content-Type
을 text/event-stream
으로 설정하고, 연결을 유지하기 위해 Cache-Control
과 Connection
헤더를 설정합니다.onmessage
이벤트 핸들러를 통해 수신된 데이터를 처리합니다.EventSource
객체는 close
메소드를 호출하여 연결을 종료합니다.Event ID 및 Last-Event-ID 헤더: SSE는 이벤트의 순서를 보장하기 위해 event ID
를 사용할 수 있습니다. 클라이언트는 Last-Event-ID
헤더를 통해 마지막으로 수신한 이벤트 ID를 서버에 전달하여, 연결이 재설정된 경우에도 데이터 손실 없이 이어서 받을 수 있습니다.
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);
});
Retry Mechanism: SSE는 자동으로 재연결을 시도하며, 기본 재연결 간격은 3초입니다. 서버는 retry
필드를 통해 클라이언트의 재연결 간격을 조정할 수 있습니다.
res.write(`retry: 5000\n`); // 5초 후 재연결 시도
브라우저 지원 및 폴리필: 대부분의 현대적인 브라우저는 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
라이브러리는 이러한 브라우저에서도 SSE 기능을 사용할 수 있게 해줍니다.이 부분은 기존과 동일합니다. 변경 없이 사용합니다.
// 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;
HTML 파일에서 event-source-polyfill
을 로드한 후, TypeScript 파일에서 사용합니다.
<!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>
// 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를 사용할 수 있습니다.