Server-Sent Events — 단방향 실시간 통신의 간단한 대안
Server-Sent Events(SSE)는 서버에서 클라이언트로 단방향 실시간 데이터를 전송하는 기술입니다. WebSocket보다 훨씬 간단하고, HTTP 기반이라 기존 인프라와 호환성이 좋습니다. 알림, 실시간 피드, AI 스트리밍 응답에 적합합니다.
EventSource 기본 사용법
// 서버에 연결
const eventSource = new EventSource("/api/events");
// 기본 메시지 수신
eventSource.addEventListener("message", (event) => {
console.log("받음:", event.data);
});
// 연결 성공
eventSource.addEventListener("open", () => {
console.log("SSE 연결됨");
});
// 에러 (네트워크 끊김 시 자동 재연결됨)
eventSource.addEventListener("error", (event) => {
if (eventSource.readyState === EventSource.CONNECTING) {
console.log("재연결 중...");
} else {
console.error("SSE 에러");
}
});
// 연결 종료
eventSource.close();
서버 측 구현 (Node.js)
// Express 예시
app.get("/api/events", (req, res) => {
// SSE 헤더 설정
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
// 데이터 전송 형식: "data: 내용\n\n"
const sendEvent = (data) => {
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// 주기적으로 데이터 전송
const interval = setInterval(() => {
sendEvent({ time: new Date().toISOString(), value: Math.random() });
}, 1000);
// 클라이언트 연결 종료 시 정리
req.on("close", () => {
clearInterval(interval);
});
});
이벤트 타입 지정
서버에서 event: 필드를 사용하면 커스텀 이벤트를 보낼 수 있습니다.
// 서버 측
res.write(`event: notification\ndata: {"message": "새 알림"}\n\n`);
res.write(`event: userUpdate\ndata: {"userId": 1, "status": "online"}\n\n`);
// 클라이언트 측 — 이벤트 타입별 리스너
const eventSource = new EventSource("/api/events");
eventSource.addEventListener("notification", (event) => {
const data = JSON.parse(event.data);
showNotification(data.message);
});
eventSource.addEventListener("userUpdate", (event) => {
const data = JSON.parse(event.data);
updateUserStatus(data.userId, data.status);
});
SSE 프로토콜 형식
// 기본 메시지
data: 안녕하세요\n\n
// 여러 줄 데이터
data: 첫 번째 줄\n
data: 두 번째 줄\n\n
// 이벤트 타입 지정
event: chat\n
data: {"user": "정훈", "text": "안녕!"}\n\n
// ID 설정 (재연결 시 Last-Event-ID로 전송)
id: 12345\n
data: 메시지 내용\n\n
// 재연결 간격 설정 (밀리초)
retry: 5000\n
data: 재연결 간격 변경\n\n
자동 재연결과 Last-Event-ID
SSE의 가장 큰 장점 중 하나는 ** 자동 재연결 **입니다.
// 서버 측 — ID 포함하여 전송
let messageId = 0;
const sendEvent = (data) => {
messageId++;
res.write(`id: ${messageId}\n`);
res.write(`data: ${JSON.stringify(data)}\n\n`);
};
// 재연결 시 클라이언트가 보내는 헤더 확인
app.get("/api/events", (req, res) => {
const lastEventId = req.headers["last-event-id"];
if (lastEventId) {
// 놓친 메시지부터 재전송
const missedMessages = getMessagesSince(parseInt(lastEventId));
missedMessages.forEach(sendEvent);
}
// ... 이후 실시간 메시지 전송
});
인증 처리 — EventSource의 한계
EventSource는 커스텀 헤더를 설정할 수 없습니다. 인증이 필요하면 대안이 필요합니다.
// 방법 1: URL 쿼리 파라미터로 토큰 전달 (보안 주의)
const eventSource = new EventSource(`/api/events?token=${accessToken}`);
// 방법 2: 쿠키 기반 인증 (withCredentials)
const eventSource = new EventSource("/api/events", {
withCredentials: true,
});
// 방법 3: fetch + ReadableStream으로 직접 구현
async function sseWithHeaders(url, headers) {
const response = await fetch(url, { headers });
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// 이벤트 파싱
const events = buffer.split("\n\n");
buffer = events.pop(); // 마지막 미완성 이벤트는 버퍼에 유지
for (const event of events) {
if (event.trim()) {
const data = event.replace(/^data: /gm, "");
handleEvent(JSON.parse(data));
}
}
}
}
sseWithHeaders("/api/events", {
Authorization: `Bearer ${accessToken}`,
});
실전 활용 — AI 스트리밍 응답
async function streamAIResponse(prompt) {
const response = await fetch("/api/ai/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
fullText += chunk;
updateChatUI(fullText); // 실시간 타이핑 효과
}
return fullText;
}
SSE vs WebSocket
| 기능 | SSE | WebSocket |
|---|---|---|
| 방향 | 서버 → 클라이언트 | 양방향 |
| 프로토콜 | HTTP | ws:// |
| 자동 재연결 | 내장 | 직접 구현 |
| 커스텀 헤더 | 불가 (EventSource) | 불가 (핸드셰이크) |
| 바이너리 데이터 | 불가 | 가능 |
| 브라우저 연결 제한 | 도메인당 6개 (HTTP/1.1) | 제한 없음 |
| 구현 난이도 | 쉬움 | 중간 |
| 적합한 용도 | 알림, 피드, AI 스트리밍 | 채팅, 게임 |
** 기억하기 **: SSE는 서버에서 클라이언트로의 단방향 스트리밍이 필요할 때 가장 간단한 선택입니다. 자동 재연결이 내장되어 있고, HTTP 기반이라 기존 인프라와 호환됩니다. 양방향이 필요하면 WebSocket을, 단방향이면 SSE를 선택하면 됩니다.
댓글 로딩 중...