인셔셔RSS 관심 있는 블로그, 뉴스, 기술 정보를 효율적으로 추적하고 읽으세요
원문 읽기 InertiaRSS에서 열기

추천 피드

博客园 - 司徒正美
V
V2EX
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
aimingoo的专栏
aimingoo的专栏
Apple Machine Learning Research
Apple Machine Learning Research
IT之家
IT之家
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
月光博客
月光博客
T
The Blog of Author Tim Ferriss
宝玉的分享
宝玉的分享
Martin Fowler
Martin Fowler
博客园 - 聂微东
The GitHub Blog
The GitHub Blog
V
Visual Studio Blog
WordPress大学
WordPress大学
酷 壳 – CoolShell
酷 壳 – CoolShell
Engineering at Meta
Engineering at Meta
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python)
서버-보낸 이벤트(SSE)는 무엇인가? 2026년 개발자 가이드
Rahul J · 2026-05-24 · via DEV Community

TL;DR

서버에서 보낸 이벤트 (SSE)는 평범한 HTTP를 기반으로 한 서버 → 브라우저 방향의 단일 스트리밍 프로토콜입니다. 브라우저는 new EventSource(url)과 연결을 열고, 서버는 연결을 유지하며 원하는 대로 data: 줄을 푸시합니다. 그것이 전부입니다. 웹소켓 핸드셰이크 없이, 없이ws:// 업그레이드는 없습니다. 자동으로 재연결되고, HTTP를 사용하는 모든 프록시를 통해 작동하며, 2012년 이후 모든 브라우저에 포함되어 있습니다.

서버 → 클라이언트 스트리밍이 필요하고 양방향 메시징이 필요하지 않다면, SSE는 2026년에 중요한 모든 측면에서 WebSockets보다 우수합니다 : 코드가 적고 인프라가 간단하며 자동 재연결, 자동 이벤트 ID를 위한 재개 가능성. WebSocket을 사용할 유일한 이유는 양방향 채팅 스타일의 트래픽이나 이진 프레임입니다.

지금 바로 스트리밍을 보고 싶으신가요? 무료 SSE 테스터를 엽니다. SSE 엔드포인트를 붙여넣고 실시간으로 이벤트를 확인하세요.

30초 심리 모델

[ Browser ]  ──── GET /events ───►  [ Server ]
              ◄── HTTP 200, keep alive
              ◄── data: hello\n\n
              ◄── data: world\n\n
              ◄── data: ...

전체 화면 모드로 입력하세요 전체 화면 모드 종료

그것은 닫히지 않는 오래 지속되는 HTTP GET 요청으로, 응답 본문에는 특정 텍스트 형식이 있습니다. MIME 유형은 text/event-stream입니다. 새 줄바꿈마다 이벤트를 onmessage으로 플러시합니다. 브라우저는 프레임링, 파싱, 재연결을 처리합니다 — 당신은 eventSource.onmessage = ...을 작성하면 끝납니다.

가장 작은 예제

서버 (Node.js — 라이브러리가 필요 없음):

import http from 'node:http';

http.createServer((req, res) => {
  if (req.url !== '/events') {
    res.writeHead(404).end();
    return;
  }

  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  let n = 0;
  const interval = setInterval(() => {
    res.write(`data: tick ${++n}\n\n`);
  }, 1000);

  req.on('close', () => clearInterval(interval));
}).listen(3000);

전체 화면 모드로 전환 전체 화면 모드 종료

클라이언트 (어디서든 — <script> 태그 포함):

const es = new EventSource('/events');
es.onmessage = (e) => console.log('got:', e.data);
es.onerror = () => console.warn('disconnected, browser will retry');

전체 화면 모드로 전환 전체 화면 모드 종료

서버를 실행하고, 클라이언트를 브라우저 콘솔에 붙여넣으면 20줄 미만의 코드로 스트리밍을 할 수 있습니다. 빌드 단계가 없습니다. 라이브러리도 없습니다. 업그레이드 협상도 없습니다. 그것이 전체 캠페인입니다.

2026년에 SSE가 얼마나 낮게 평가받는지에 대해

"실시간"이라고 들으면 팀들이 웹소켓을 본능적으로 사용하려고 하고, 그런 다음 한 주 동안 프록시 타임아웃, 스티키 세션, ALB 업그레이드 헤더를 디버깅한다. 사용 사례의 반은 서버 → 클라이언트만이었는데 — 피드, 알림 다이어로그, 빌드 로그, 실시간 카운터 같은 것이었다. 그것은 SSE 영역이다.

SSE를 사용하면 웹소켓으로 직접 구축해야 할 것들을 무료로 얻을 수 있는 것은 다음과 같다.

1. 자동 재연결. 연결이 끊어지면 브라우저는 ~3초간 기다렸다가 다시 연결합니다. 이에 대한 코드를 작성하지 않습니다. WebSockets를 사용하면 경력 전체에 걸쳐 각 프로젝트에서 동일한 백오프 루프를 작성해야 합니다.

2. 이벤트 ID와 재개. 서버가 id: 42\n을 보내면 브라우저는 다음 재연결 시 헤더로 Last-Event-ID: 42을 보냅니다. 서버는 그곳에서 다시 시작할 수 있습니다. 이것은SSE가 빌드 로그, AI 스트리밍, 감사 피드에 탁월한 특징인 기능. WebSockets를 사용하면 이것은 당신이 직접 설계한 커스텀 프로토콜입니다.

3. 표준 HTTP 전역 사용. SSE는 GETAccept: text/event-stream를 가지고 있습니다. 모든 프록시, 모든 CDN, 모든 WAF, 모든 역프록시가 이를 이해합니다 (하나의 예외는 아래 참조). 쿠키,Authorization는 CORS, 압축 모두 정상적으로 작동합니다. WebSockets는 업그레이드 춤이 필요하며 프록시는 종종 이를 잘못 처리합니다.

4. 프레임 없음. 텍스트만, 줄 제한됨. 당신은 curl SSE 엔드포인트를 만들고 터미널에서 스트림을 읽을 수 있습니다. WebSockets로는 그것을 시도해 보세요.

curl -N -H "Accept: text/event-stream" https://api.example.com/events

전체 화면 모드로 전환 전체 화면 모드 종료

SSE 프레임의 구조

형식은 매우 간단하지만 사람들이 혼란스러워합니다. 각 이벤트는 하나 이상의 줄로 구성되며, 개의 새 줄로 끝납니다(공백 줄):

event: user-joined
id: 423
retry: 5000
data: {"userId":42,"name":"Ada"}

data: simple message
data: continuation of the same event

전체 화면 모드 입력 전체 화면 모드 종료

  • data: — 페이로드. 동일한 이벤트 내의 여러 data: 줄은 \n로 결합됩니다.
  • event: — 이벤트를 설정합니다이름을 듣고, es.addEventListener('user-joined', ...)을 사용합니다. 생략 시 onmessage이 발생합니다.
  • id: — 브라우저가 재연결 시 Last-Event-ID으로 보내는 내용입니다.
  • retry: — 재연결을 기다리기 전에 기다리는 밀리초입니다.

공백 줄은 필수입니다을 잊으면 안 됩니다.\n는 #1 SSE 버그입니다. 프록시와 클라이언트에서 이벤트 버퍼를 볼 수 없습니다.

SSE 대비 WebSockets 대비 Long Polling을 사용할 때

이것이 필요한 유일한 결정 트리입니다.

SSE를 사용할 때:

  • 트래픽이 서버 → 클라이언트만입니다 (알림, 피드, 대시보드, 로그, AI 토큰 스트리밍)
  • 자동 재연결을 원하지만 작성하지 않으려는 경우
  • 텍스트을 스트리밍하고 있습니다 (JSON, 마크다운 조각, 로그 라인)
  • 디버깅을 원하시면curl

웹소켓을 사용할 때:

  • 양방향이고 고주파인 트래픽이 있을 때 (채팅, 공동 편집, 멀티플레이 게임)
  • バイ너리 프레임이 필요할 때 (오디오/비디오, 사용자 지정 프로토콜)
  • 클라이언트 → 서버 메시지에 대해 100밀리초 미만의 라운드 트립이 필요할 때

긴 폴링을 사용할 때:

  • 당신이 두 가지 모두를 깨뜨리는 인프라에 갇혀 있을 때 (2026년에는 드물지만 적극적인 기업 프록시 뒤에서 발생할 수 있습니다)

양쪽 모두에 코드가 포함된 전면적인 문제 해결은 SSE 대비 WebSockets 대비 긴 폴링 심층 분석.

세 가지 생산성 함정

이는 SSE를 처음 배포할 때 각 팀을 공격하는 버그입니다.

1. 프록시 버퍼가 당신을 죽입니다

Nginx는 기본적으로 응답을 버퍼링합니다. 당신의 res.write가 버퍼에 도착하면 클라이언트는 몇 분 동안 아무것도 볼 수 없습니다. 두 가지 해결책이 있으며, 둘 다 해결하세요:

서버 응답 헤더:

X-Accel-Buffering: no

전체 화면 모드로 전환 전체 화면 모드 종료

Nginx 구성:

proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;

전체 화면 모드를 입력하세요 전체 화면 모드를 종료하세요

Cloudflare는 버퍼가 너무 많습니다 — SSE 엔드포인트를 캐시되지 않은 경로에 배치하거나 Cloudflare를 인지하는 스트리밍 설정을 사용해야 합니다.

2. 브라우저의 6개 연결당 원래 제한

브라우저는 약 6개의 열린 HTTP/1.1 연결을 원래에 제한합니다. 만약 앱이 열면EventSource와 사용자가 5개 더 탭을 열면 7번째 탭이 연결 슬롯이 없어 멈춥니다. 두 가지 해결책:

  • HTTP/2 또는 HTTP/3 사용 — 연결 멀티플렉싱은 하나의 TCP 연결에 수백 개의 스트림을 의미합니다. 이것이 현대적인 해결책입니다.
  • 탭 간에 BroadcastChannel 또는 SharedWorker를 통해 조정합니다 — 하나의 탭이 EventSource를 유지하며 이벤트를 자매 탭으로 브로드캐스팅합니다.

3. 심박수와 대기 시간 초과

웹소켓과 동일한 함정: 로드 밸런서는 대기 중인 연결을 닫습니다. AWS ALB는 기본적으로 50초입니다. 15-30초마다 한 줄의 주석 줄을 전송하세요:

setInterval(() => res.write(':keepalive\n\n'), 15000);

전체 화면 모드로 전환 전체 화면 모드 종료

:로 시작하는 줄은 사양에 따른 주석입니다 — 이들은 연결을 따뜻하게 유지하면서 발동하지 않습니다onmessage on the client.

인증 및 SSE

EventSource는 하나의 귀찮은 제한 사항이 있습니다: 커스텀 헤더를 를 구성할 때 설정할 수 없습니다.Authorization: Bearer .... 선택지:

1. 쿠키(브라우저 앱용으로 권장됨) — 브라우저가 자동으로 전송합니다.withCredentials: true와 CORS-허용을 원본으로 설정하세요.

const es = new EventSource('/events', { withCredentials: true });

전체 화면 모드로 전환 전체 화면 모드 종료

2. 쿼리 문자열 토큰 — 작동하지만 토큰이 서버 로그에 남음.

3. fetch + ReadableStream 패턴EventSourcefetch에서 제거하며, 커스텀 헤더를 허용합니다. 자동 재연결을 잃고 본인이 형식을 파싱해야 하지만, 토큰 인증을 사용하는 현대 앱에 적합한 결정입니다:

const res = await fetch('/events', {
  headers: { Authorization: `Bearer ${token}` },
});
const reader = res.body.getReader();
// ... parse text/event-stream chunks manually

전체 화면 모드를 입력합니다 전체 화면 모드를 종료합니다

@microsoft/fetch-event-source와 같은 라이브러리는 이를 대신해주고, 대부분의 현대 AI 스트리밍 클라이언트가 밑단에서 사용하는 것입니다.

SSE는 AI 스트리밍을 가능하게 하는 것입니다

지난 두 년 동안 ChatGPT, Claude, 또는 어떤 AI 채팅 UI를 사용했다면 SSE의 작동을 본 적이 있을 거예요. 서버는 토큰이 생성될 때마다 스트리밍합니다:

data: {"choices":[{"delta":{"content":"Hello"}}]}

data: {"choices":[{"delta":{"content":" world"}}]}

data: [DONE]

전체 화면 모드로 전환 전체 화면 모드 종료

Anthropic의, OpenAI의, 그리고 Google의 스트리밍 API는 모두 SSE (또는 거의 동일한 커스텀 이벤트 스트림 형식)를 사용합니다. SSE에 대해 알면 다른 웹 프로토콜과 동일한 도구를 사용하여 AI 스트리밍 엔드포인트를 디버깅할 수 있습니다.

SSE 엔드포인트를 테스트하는 방법

각각을 사용할 때 순서대로 세 가지 방법입니다.

1. curl -N — 버퍼 없음 플래그. 가장 빠른 건강 검사입니다.

curl -N -H "Accept: text/event-stream" https://api.example.com/events

전체 화면 모드 입력 전체 화면 모드 종료

2. 브라우저 내 SSE 테스터 — URL을 붙여넣고 실시간 이벤트를 확인하고 헤더를 확인하고 재연결 동작을 확인하세요.여기서 무료로 하나 받으세요 설치 없이.

3. 브라우저 개발 도구 → 네트워크 → 이벤트 스트림 탭 — 앱이 열리면 실시간으로 파싱된 이벤트 스트림을 볼 수 있습니다. 크롬 기반 브라우저에는 이를 위한 전용 탭이 있습니다.

일반적인 오해

  • "SSE는 죽었고, WebSockets가 그것을 대체했습니다." 웹소켓은 성장했지만 SSE는 결코 어디에도 가지 않았다 — AI 스트리밍, GitHub Actions 로그, Vercel 배포 로그, Stripe 웹훅(물론 기술적으로는 SSE가 아니지만 같은 아이디어), 그리고 당신이 매일 사용하는 대부분의 "알림" 기능을 지원한다.
  • "SSE는 HTTP/2와 호환되지 않는다." HTTP/2에서는 더 잘 작동한다 — 6-연결 제한 없이 모든 스트림이 멀티플렉싱된다.
  • "SSE는 텍스트만 사용하므로 이진 데이터를 보낼 수 없습니다." 참, 하지만 JSON 필드에 작은 이진 데이터를 base64로 인코딩하는 것은 괜찮으며, 실제 이진 스트림을 사용해야 할 경우에는 WebSocket을 사용하거나 그냥 HTTP 다운로드를 사용해야 합니다.
  • "SSE는 ping/pong 기능이 없습니다." 15초마다 주석 줄(:heartbeat\n\n)을 전송하세요. 끝.

FAQ

SSE가 긴 폴링과 같은 것입니까?
넘버 롱 폴링 = 클라이언트가 요청을 하면, 서버는 데이터가 있을 때까지 연결을 열어두고, 서버가 응답하면 클라이언트는 다음 요청을 합니다. SSE = 하나의 요청이 영원히 열어두어지고, 서버는 데이터가 있을 때 푸시합니다. 롱 폴링은 매번 연결을 재개합니다. SSE는 그렇지 않습니다.

SSE가 모바일 브라우저에서 작동하나요?
네, iOS Safari, Chrome Android, Firefox Mobile에서 완전한 지원을 합니다. 자동 재연결은 셀룰러 핸오프를 잘 처리합니다.

React / Vue / Svelte / Solid에서 SSE를 사용할 수 있나요?
네, 어떤 구독을 사용하는 것과 정확히 같습니다. EventSourceuseEffect (또는 동등한) 안에 생성하고, 정리 시 닫고, 각 메시지에 상태를 설정하세요.

서버는 동시에 최대 몇 개의 SSE 연결을 처리할 수 있나요?
Node.js와 합리적인 설정으로, 각 프로세스당 수만 개 — 연결은 대부분 비활성화되어 있고 각각은 몇 KB의 RAM을 소비합니다. 실제 최대 한도인 각 프로세스당 ~32k-65k 개의 열린 파일 설명자와 비교해보세요.

EventSource을 사용해야 할까요, 아니면 fetch + ReadableStream을 사용해야 할까요?
Cookie 인증이 작동한다면 EventSource을 사용하세요 (더 간단하며, 무료 자동 재연결).fetch 필요하다면 Authorization 헤더나 맞춤 요청 동작을 사용하고 재연결을 처리하는 라이브러리를 가져옵니다.


원래 AllDevToolsHub에서 게시되었습니다. 250개 이상의 무료, 프라이버시 우선 브라우저 기반 개발 도구 — SSE 테스터, 웹소켓 테스터, JWT 디코더 등 —는 alldevtoolshub.com를 참고하세요.