๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Spring Boot/etc

[SSE] ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ์— ์—ฐ๊ฒฐ์„ ํ•œ๋‹ค๊ณ ?! (๋ฐ˜๋Œ€ ์•„๋‹ˆ์•ผ?!)

by Lee David 2026. 2. 4.
๋ฐ˜์‘ํ˜•

๐Ÿš€ Spring Boot(Kotlin)๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ: SSE ๊ธฐ์ดˆ

์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๊ธฐ๋Šฅ, ๋งค๋ฒˆ ์›น์†Œ์ผ“(WebSocket)์œผ๋กœ ๋ฌด๊ฒ๊ฒŒ ๊ตฌํ˜„ํ•˜์…จ๋‚˜์š”? ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ์ผ๋ฐฉ์ ์ธ ์•Œ๋ฆผ๋งŒ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค๋ฉด **SSE(Server-Sent Events)**๊ฐ€ ์ •๋‹ต์ž…๋‹ˆ๋‹ค.

1. SSE๋ž€?

SSE๋Š” ์„œ๋ฒ„์—์„œ ์›น ๋ธŒ๋ผ์šฐ์ €๋กœ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘ธ์‹œํ•˜๋Š” HTTP ํ‘œ์ค€ ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค.

ํŠน์ง• ์„ค๋ช…
ํ†ต์‹  ๋ฐฉํ–ฅ ๋‹จ๋ฐฉํ–ฅ (์„œ๋ฒ„ → ํด๋ผ์ด์–ธํŠธ)
ํ”„๋กœํ† ์ฝœ ์ผ๋ฐ˜ HTTP ์‚ฌ์šฉ (text/event-stream)
์—ฐ๊ฒฐ ์œ ์ง€ ํ•œ ๋ฒˆ ์—ฐ๊ฒฐํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ๊ณ„์† ๋ฐ์ดํ„ฐ ์ „์†ก ๊ฐ€๋Šฅ
์žฌ์—ฐ๊ฒฐ ๋„คํŠธ์›Œํฌ ์ค‘๋‹จ ์‹œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์žฌ์—ฐ๊ฒฐ ์‹œ๋„

2. Backend: Kotlin ํ•ต์‹ฌ ์ฝ”๋“œ

Spring์—์„œ ์ œ๊ณตํ•˜๋Š” SseEmitter๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•„์ฃผ ์งง์€ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

Kotlin
 
@RestController
class SseController {

    // 1. SSE ์—ฐ๊ฒฐ ์—”๋“œํฌ์ธํŠธ
    @GetMapping("/connect", produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
    fun connect(): SseEmitter {
        val emitter = SseEmitter(60 * 1000L) // 60์ดˆ ํƒ€์ž„์•„์›ƒ ์„ค์ •
        
        // ์—ฐ๊ฒฐ ์ฆ‰์‹œ ๋ฐ์ดํ„ฐ ์ „์†ก (503 ์—๋Ÿฌ ๋ฐฉ์ง€์šฉ)
        emitter.send(SseEmitter.event().name("connect").data("connected!"))
        
        return emitter
    }
}

3. Frontend: JavaScript ํ•ต์‹ฌ ์ฝ”๋“œ

๋ธŒ๋ผ์šฐ์ € ๋‚ด์žฅ API์ธ EventSource๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.

JavaScript
 
// 1. ์„œ๋ฒ„ ์—ฐ๊ฒฐ
const eventSource = new EventSource('http://localhost:8080/connect');

// 2. ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  ์ด๋ฒคํŠธ
eventSource.onmessage = (event) => {
    console.log("์ƒˆ๋กœ์šด ๋ฉ”์‹œ์ง€:", event.data);
};

// 3. ์—ฐ๊ฒฐ ์—๋Ÿฌ ์ฒ˜๋ฆฌ
eventSource.onerror = (error) => {
    console.error("SSE ์—ฐ๊ฒฐ ์‹คํŒจ:", error);
    eventSource.close(); // ํ•„์š”์‹œ ์—ฐ๊ฒฐ ์ข…๋ฃŒ
};

4. ๊ตฌํ˜„ ์‹œ ํ•ต์‹ฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ (Tips)

  • ํƒ€์ž„์•„์›ƒ ๊ด€๋ฆฌ: SseEmitter ์ƒ์„ฑ ์‹œ ์‹œ๊ฐ„์„ ๋„‰๋„‰ํžˆ ์ฃผ๊ฑฐ๋‚˜, ํด๋ผ์ด์–ธํŠธ์—์„œ ์žฌ์—ฐ๊ฒฐ ๋กœ์ง์„ ํ™•์ธํ•˜์„ธ์š”.
  • ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ „์†ก: ์—ฐ๊ฒฐ ์งํ›„ ์„œ๋ฒ„์—์„œ ์•„๋ฌด ๋ฐ์ดํ„ฐ๋„ ๋ณด๋‚ด์ง€ ์•Š์œผ๋ฉด ์—ฐ๊ฒฐ์ด ๋Š๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. "connect" ์ด๋ฒคํŠธ๋ฅผ ํ•œ ๋ฒˆ ์ด์ฃผ๋Š” ๊ฒŒ ๊ตญ๋ฃฐ์ž…๋‹ˆ๋‹ค.
  • Nginx ์„ค์ •: ๋งŒ์•ฝ ์„œ๋ฒ„ ์•ž์— Nginx๊ฐ€ ์žˆ๋‹ค๋ฉด ์•„๋ž˜ ์„ค์ •์„ ์ถ”๊ฐ€ํ•ด์•ผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
    • proxy_buffering off;
    • proxy_set_header Connection '';

์ •๋ฆฌํ•˜๋ฉฐ

๋‹จ์ˆœ ์•Œ๋ฆผ, ์‹ค์‹œ๊ฐ„ ์ฃผ๊ฐ€ ์ฐจํŠธ, ์ง„ํ–‰๋ฅ  ํ‘œ์‹œ ๋“ฑ์€ ์›น์†Œ์ผ“๋ณด๋‹ค SSE๊ฐ€ ํ›จ์”ฌ ๊ฐ€๋ณ๊ณ  ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ํŽธํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋Œ€์˜ ์„œ๋ฒ„ ํ™˜๊ฒฝ์—์„œ Redis๋ฅผ ํ™œ์šฉํ•ด SSE๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ค„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

๋ฐ˜์‘ํ˜•