SSE(Server-Sent Events) ์•Œ์•„๋ณด๊ธฐ๐Ÿ”Ž

Shinยท2026๋…„ 4์›” 9์ผ

SSE (Server-Sent Events)๋ž€?

SSE(Server-Sent Events) HTTP ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ „์†กํ•˜๋Š” ๋‹จ๋ฐฉํ–ฅ ํ†ต์‹  ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค

๋ณดํ†ต ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜์ง€๋งŒ, SSE๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์ด ์—†์ด๋„ ์„œ๋ฒ„๊ฐ€ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ์ „๋‹ฌ๋œ ๋ฐ์ดํ„ฐ๋Š” ์›นํŽ˜์ด์ง€ ๋‚ด์—์„œ Event ๋ฐ์ดํ„ฐ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SSE์™€ ์œ ์‚ฌํ•œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†ก ๊ธฐ์ˆ ๋กœ๋Š” WebSocket, Polling, HTTP Streaming ๋“ฑ์ด ์ž์ฃผ ์–ธ๊ธ‰๋ฉ๋‹ˆ๋‹ค.
๊ฐ ๊ธฐ์ˆ ์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹๊ณผ ์—ฐ๊ฒฐ ์œ ์ง€ ๋ฐฉ์‹์—์„œ ์ฐจ์ด๋ฅผ ๋ณด์ด๋ฉฐ, ์‚ฌ์šฉ ๋ชฉ์ ๊ณผ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ํ•ฉํ•œ ์„ ํƒ์ด ๋‹ฌ๋ผ์ง‘๋‹ˆ๋‹ค.

์ด๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด, ๊ฐ ๊ธฐ์ˆ ์˜ ํŠน์ง•์„ ๊ฐ„๋‹จํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

WebScoket

๋‹จ๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์ œ๊ณตํ•˜๋Š” SSE์™€๋Š” ๋‹ค๋ฅด๊ฒŒ WebSocket์€ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ดˆ๊ธฐ HTTP ์š”์ฒญ์„ ํ†ตํ•ด ์—ฐ๊ฒฐ์„ ์ˆ˜๋ฆฝํ•œ ํ›„, ๋ณ„๋„์˜ ์š”์ฒญ์ด ์—†๋”๋ผ๋„ ์–‘์ชฝ์—์„œ ์ž์œ ๋กญ๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋•Œ๋ฌธ์— ์ฑ„ํŒ…, ๊ฒŒ์ž„ ๋“ฑ ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์— ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋ณ„๋„์˜ ํ”„๋กœํ† ์ฝœ์„ ์‚ฌ์šฉํ•˜๊ณ  ์—ฐ๊ฒฐ ๋ฐ ์ƒํƒœ๋ฅผ ์ง์ ‘ ๊ด€๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— SSE์™€ ๋น„๊ตํ–ˆ์„ ๋•Œ ๊ตฌํ˜„ ๋ณต์žก๋„๊ฐ€ ๋†’์€ ํŽธ์ž…๋‹ˆ๋‹ค.

Polliing

Polling์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ผ์ •ํ•œ ์ฃผ๊ธฐ๋กœ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

๊ตฌํ˜„์ด ๋‹จ์ˆœํ•˜์ง€๋งŒ, ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์ด ์—†๋”๋ผ๋„ ์ง€์†์ ์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„ํšจ์œจ์ ์œผ๋กœ ์ž‘์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํŠนํžˆ ์š”์ฒญ ๊ฐ„๊ฒฉ์ด ์งง์„์ˆ˜๋ก ์„œ๋ฒ„ ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ณ , ๋ฐ˜๋Œ€๋กœ ์š”์ฒญ ๊ฐ„๊ฒฉ์ด ๊ธธ์–ด์ง€๋ฉด ์‹ค์‹œ๊ฐ„์„ฑ์ด ๋–จ์–ด์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ œ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด SSE์™€ ๊ฐ™์€ ์„œ๋ฒ„ ํ‘ธ์‹œ ๊ธฐ๋ฐ˜์˜ ํ†ต์‹  ๋ฐฉ์‹์ด ๋“ฑ์žฅํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

HTTP Streaming

HTTP Streaming์€ ํ•˜๋‚˜์˜ HTTP ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•œ ์ƒํƒœ์—์„œ ์„œ๋ฒ„๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์—ฐ์†์ ์œผ๋กœ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์—ฐ๊ฒฐ์„ ๋Š์ง€ ์•Š๊ณ  ์ŠคํŠธ๋ฆผ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์† ์ˆ˜์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SSE๋Š” HTTP Streaming์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฒคํŠธ ํ˜•์‹์„ ์ •์˜ํ•˜์—ฌ ํ‘œ์ค€ํ™”๋œ ๋ฐฉ์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“  ๊ธฐ์ˆ ์ด๋ผ๊ณ  ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๋™์ž‘ ๋ฐฉ์‹

SSE๋Š” ํด๋ผ์ด์–ธํŠธ๊ฐ€ ํ•œ ๋ฒˆ ์—ฐ๊ฒฐ์„ ๋งบ์œผ๋ฉด, ์„œ๋ฒ„๊ฐ€ ํ•ด๋‹น ์—ฐ๊ฒฐ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ง€์†์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง€๋ฉด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ์žฌ์—ฐ๊ฒฐ์„ ์‹œ๋„ํ•˜์—ฌ ์—ฐ๊ฒฐ์„ ์ง€์†์ ์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.

1. ์—ฐ๊ฒฐ ์š”์ฒญ

SSE ์—ฐ๊ฒฐ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋จผ์ € ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„๋กœ ์—ฐ๊ฒฐ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
ํด๋ผ์ด์–ธํŠธ๋Š” EventSource ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ์„œ๋ฒ„์— ์—ฐ๊ฒฐ ์š”์ฒญ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

const eventSource = new EventSource('/events');

2. ์„œ๋ฒ„์˜ ์ŠคํŠธ๋ฆผ ์‘๋‹ต

ํด๋ผ์ด์–ธํŠธ์—์„œ ์—ฐ๊ฒฐ ์š”์ฒญ์ด ์˜ค๋ฉด ์„œ๋ฒ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต ํ—ค๋”๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

3. ์ด๋ฒคํŠธ ๋ฐ์ดํ„ฐ ์ „์†ก

์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์„œ๋ฒ„๋Š” data: Hello!ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋ฉฐ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ด๋ฒคํŠธ ์ด๋ฆ„์„ ํฌํ•จํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

event: message
data: Hello! // -> ์‹ค์ œ ๋ฐ์ดํ„ฐ

๊ฐ ๋ฉ”์‹œ์ง€๋Š” ๋นˆ ์ค„ \n\n ๋กœ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.

4. ํด๋ผ์ด์–ธํŠธ์˜ ์ด๋ฒคํŠธ ์ˆ˜์‹  ๋ฐ ์ฒ˜๋ฆฌ

์„œ๋ฒ„์—์„œ ์ด๋ฒคํŠธ๊ฐ€ ์ „์†ก๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” ํ•ด๋‹น ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

eventSource.onmessage = (event) => {
		console.log(event.data);
}

event ํ•„๋“œ๋ฅผ ๊ฐ–๋Š” ์„œ๋ฒ„์˜ ๋ฉ”์‹œ์ง€๋Š” event ์— ๋ช…์‹œ๋œ ์ด๋ฆ„์˜ ์ด๋ฒคํŠธ๋กœ ์ˆ˜์‹ ๋ฉ๋‹ˆ๋‹ค. (3๋ฒˆ ๊ณผ์ •์˜ ์ด๋ฒคํŠธ ์ด๋ฆ„์„ ํฌํ•จํ•œ ํ˜•์‹ ์ฐธ๊ณ )

eventSource.addEventListener('message', (event) => {
    console.log(event.data);
  });

5. ์—ฐ๊ฒฐ ์œ ์ง€

SSE๋Š” ์—ฐ๊ฒฐ์„ ๊ณ„์† ์œ ์ง€ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์„œ๋ฒ„๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๊ณ„์† ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค๋งŒ, ์•„๋ฌด ๋ฐ์ดํ„ฐ๋„ ์—†์„ ๋•Œ ์—ฐ๊ฒฐ์ด ๋Š๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋•Œ๋ฌธ์— ์—ฐ๊ฒฐ์ด ์œ ์ง€๋˜๋„๋ก ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฌด์˜๋ฏธํ•œ ๋ฐ์ดํ„ฐ(beat)๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ธฐ์ˆ ์„ Heartbeat๋ผ๊ณ  ์นญํ•ฉ๋‹ˆ๋‹ค.

6. ์ž๋™ ์žฌ์—ฐ๊ฒฐ

์—ฐ๊ฒฐ์ด ๋Š์–ด์ง€๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์žฌ์—ฐ๊ฒฐ์„ ์‹œ๋™ํ•ฉ๋‹ˆ๋‹ค.

retry: 3000

๐Ÿ‘‰ 3์ดˆ ํ›„ ์žฌ์—ฐ๊ฒฐ์„ ์˜๋ฏธ

7. ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆผ ๋‹ซ๊ธฐ

6๋ฒˆ์—์„œ ์ด์•ผ๊ธฐํ–ˆ๋“ฏ์ด ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๋Š” ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง€๋ฉด ์—ฐ๊ฒฐ์ด ๋‹ค์‹œ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค. ์—ฐ๊ฒฐ์„ ๋Š๊ธฐ ์œ„ํ•ด์„œ๋Š” .close() ๋ฉ”์„œ๋“œ๋กœ ์ข…๋ฃŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

eventSource.close();

์—๋Ÿฌ ํ•ธ๋“ค๋ง

์„œ๋ฒ„ ์—ฐ๊ฒฐ์ด ๋Š๊ธฐ๊ฑฐ๋‚˜, ๋„คํŠธ์›Œํฌ ๋ฌธ์ œ, ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ ํฌ๋งท ๋“ฑ ์ •์ƒ์ ์œผ๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์—†๋Š” ์ƒํ™ฉ์—์„œ ์—๋Ÿฌ ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. EventSource ๊ฐ์ฒด์— onerror ์ฝœ๋ฐฑ์„ ๊ตฌํ˜„ํ•˜์—ฌ ์—๋Ÿฌ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

eventSource.onerror = (error) => {
		alert("SSE ์—ฐ๊ฒฐ ์˜ค๋ฅ˜ ๋ฐœ์ƒ");
		console.log('SSE ์—ฐ๊ฒฐ ์˜ค๋ฅ˜: ', err);
}

์ด๋ ‡๊ฒŒ SSE์˜ ๊ฐœ๋…๊ณผ ๋™์ž‘ ๋ฐฉ์‹์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” React, SpringBoot๋กœ SSE๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๐Ÿ˜Š

0๊ฐœ์˜ ๋Œ“๊ธ€