์ง์ฝ ๐ | SSE : Server-Sent-Events
๋ค์ด๊ฐ๊ธฐ์ ์์,
๊ธฐ์กด์ ์งํํ๋ ๋น๋๋ฉด ๋ชจ์ ์๋น์ค ํ๋ก์ ํธ์ ๋ชจ์ ๊ด๋ จ ์๋ฆผ๋ค์ ๋ฐ์ ์ ์๋ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ธฐ๋ก ํ์ด์. ์๋ฆผ ๊ธฐ๋ฅ์ ๊ธฐํํ๋ฉด์ ์ด๋ค ๊ธฐ์ ์ ์ด์ฉํ ์ง ๋ ผ์ํ๊ณ ๊ฒฐ์ ํ๋ ๊ณผ์ ์์ ๊ณต๋ถํ๋ ๋ด์ฉ๋ค์ ๊ธฐ๋กํด๋ณด๊ณ ์ ํฉ๋๋ค.
์ด๋ ค์ด ๊ฒฐ์ ์ ์๋์์ง๋ง, sse์ ๊ฐ์ด ๋ง์ด ์ธ๊ธ๋๋ web socket๊ณผ์ ์ฐจ์ด์ ๋ ํจ๊ป ์ดํด๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ์์.
๐ Server-Sent-Events๋ ?
์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ๋จ๋ฐฉํฅ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์ง์์ ์ผ๋ก ์ ์กํ ์ ์๋ ํต์ ๋ฐฉ์
ยท ๋จ๋ฐฉํฅ ํต์ ์ง์ : ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก๋ง ๋ฐ์ดํฐ ์ ์ก ๊ฐ๋ฅ
ยท ์๋ ์ฌ์ฐ๊ฒฐ ๊ธฐ๋ฅ : ์ฐ๊ฒฐ์ด ๋์ด์ ธ๋ ํด๋ผ์ด์ธํธ๊ฐ ์๋์ผ๋ก ๋ค์ ์ฐ๊ฒฐ
ยท ์ด๋ฒคํธ ๊ธฐ๋ฐ ๋ฉ์์ง ์ฒ๋ฆฌ : ํน์ ์ด๋ฒคํธ์ ๋ํ ๋ฐ์ดํฐ ์ ์ก ๊ฐ๋ฅ
ยท ๊ธฐ์กด HTTP ํ๋กํ ์ฝ ์ฌ์ฉ : WebSockets์ ๋ฌ๋ฆฌ ์๋ก์ด ํ๋กํ ์ฝ์ ์๊ตฌํ์ง ์์๐ WebSocket ์ด๋ ?
ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ์๋ฐฉํฅ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋กํ ์ฝ
ยท ์๋ฐฉํฅ ํต์ ์ง์ : ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์.
ยท Full-Duplex : ๋์์ ๋ฐ์ดํฐ๋ฅผ ์ก์์ ๊ฐ๋ฅ
ยท Connection Persistence : ํ ๋ฒ ์ฐ๊ฒฐ๋๋ฉด ์ง์์ ์ธ ๋ฐ์ดํฐ ๊ตํ ๊ฐ๋ฅ
ยท ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ ์ง์ : JSON, ํ
์คํธ๋ฟ๋ง ์๋๋ผ ์ด๋ฏธ์ง, ์ค๋์ค, ๋น๋์ค ์ ์ก ๊ฐ๋ฅSSE vs. WebSocket
| ๊ตฌ๋ถ | SSE | WebSocket |
|---|---|---|
| ํต์ ๋ฐฉํฅ | ๋จ๋ฐฉํฅ (์๋ฒ โ ํด๋ผ์ด์ธํธ) | ์๋ฐฉํฅ |
| ํ๋กํ ์ฝ | HTTP ๊ธฐ๋ฐ | ๋ณ๋ ํ๋กํ ์ฝ |
| ๊ตฌํ ๋์ด๋ | ๋น๊ต์ ๋จ์ | ์๋์ ์ผ๋ก ๋ณต์ก |
| ์ฌ์ฐ๊ฒฐ ์ฒ๋ฆฌ | ๋ธ๋ผ์ฐ์ ๊ธฐ๋ณธ ์ง์ | ์ง์ ๊ตฌํ ํ์ |
| ์ฃผ์ ์ฌ์ฉ ์ฌ๋ก | ์๋ฆผ, ์ค์๊ฐ ์ ๋ฐ์ดํธ | ์ฑํ , ์ค์๊ฐ ํ์ |
๊ธฐํํ๋ ์๋ฆผ ๊ธฐ๋ฅ์ ๊ฒฝ์ฐ ๊ฐ ๊ณ์ ๋ง๋ค ์๋ฆผ์ด ์์ด๋ ํ์์ผ๋ก, ๋จ๋ฐฉํฅ ์ ์ก๋ง ํ์ํ ์ํฉ์ด์๊ธฐ ๋๋ฌธ์ ๊ตฌํ ๋์ด๋๊ฐ ๋น๊ต์ ๋ฎ์ ํธ์ด๊ณ ๋จ๋ฐฉํฅ ์ก์์ ์ ํนํ๋์ด์๋ sse๊ฐ ๋ ์ ํฉํ๋ค๋ ๊ฒฐ๋ก ์ ๋ด๋ฆฌ๊ฒ ๋์์ด์!
Short Polling
ํด๋ผ์ด์ธํธ๊ฐ ์ผ์ ํ ์ฃผ๊ธฐ๋ก ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ณ , ์๋ฒ๊ฐ ์ฆ์ ์๋ตํ๋ ๋ฐฉ์

์์ฒญ์ด ์ฌ ๋๋ง๋ค ์๋ฒ๋ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๋์ง ํ์ธํ ํ ์๋ต์ ๋ฐํํด์. ๊ตฌํ์ ๋จ์ํ์ง๋ง, ๋ฐ์ดํฐ๊ฐ ์์ด๋ ๋ฐ๋ณต์ ์ผ๋ก ์์ฒญ์ด ๋ฐ์ํ์ฌ ์๋ฒ ์์ฒญ์๊ฐ ํ์์ด์์ผ๋ก ๋ง์์ง๋ค๋ ๋จ์ ์ด ์์ต๋๋ค. ์ฆ, HTTP overhead๊ฐ ์ฆ๊ฐํ๊ฒ ๋๋๊ฑฐ์ฃ . ์ด๋ฅผ ๊ฐ์ ํ ๋ฐฉ๋ฒ์ผ๋ก Long Polling ๊ฐ๋ ์ด ๋์ค๊ฒ ๋์์ต๋๋ค.
Long Polling
ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ์ฆ์ ์๋ตํ์ง ์๊ณ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋๊น์ง ๋๊ธฐํ๋ ๋ฐฉ์

์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด ์๋ฒ๋ ์๋ต์ ๋ฐํํ๊ณ , ํด๋ผ์ด์ธํธ๋ ๋ค์ ์์ฒญ์ ๋ณด๋ด ์ฐ๊ฒฐ์ ์ ์งํด์. ์ด๋ฐ ๋ฐฉ์์ Short Polling ๋๋น ๋ถํ์ํ ์์ฒญ ์๋ฅผ ์ค์ฌ HTTP overhead๊ฐ ๊ฐ์ํ๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค. ํ์ง๋ง ๋งค๋ฒ ์ฐ๊ฒฐ์ ์๋ก ๋งบ์ด์ผ ํ๋ฏ๋ก ์ฌ์ ํ ๋นํจ์จ์ด ์กด์ฌํ์ด์..
SSE
๋ฐ๋ณต์ ์ธ polling ์์ฒญ ์์ด, ์๋ฒ๊ฐ ์ฐ๊ฒฐ์ ์ ์งํ๋ฉฐ ์ด๋ฒคํธ ๋ฐ์ ์ ํด๋ผ์ด์ธํธ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๋ฐฉ์

SSE๋ ์ด๋ฌํ Polling ๋ฐฉ์์ ํ๊ณ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ๋ฑ์ฅํ๊ฒ ๋์ด์. ํด๋ผ์ด์ธํธ๊ฐ ํ ๋ฒ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๋ ์ฐ๊ฒฐ์ ์ ์งํ ์ฑ ์ง์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๊ฒ ๋ผ์. ๋ถํ์ํ ์ฌ์์ฒญ ์์ด ์ค์๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ ์ ์์ด ๋คํธ์ํฌ ํจ์จ์ด ๋์ต๋๋ค. HTTP ํ๋กํ ์ฝ ๊ธฐ๋ฐ์ด๊ธฐ ๋๋ฌธ์ ๊ธฐ์กด ์ธํ๋ผ์๋ ์ ์ด์ธ๋ฆฐ๋ค๋ ์ฅ์ ์ด ์์ด์.
SSE ๋ก์ง
SSE๋ ๋ธ๋ผ์ฐ์ ์์ EventSource ๊ฐ์ฒด๋ฅผ ํตํด ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค!
๐ย EventSource ๋ ?
SSE ์ฐ๊ฒฐ์ ํธ๋ฆฌํ๊ฒ ํ ์ ์๋๋ก ํด์ฃผ๋ Web API
1. GET ์์ฒญ์
๋๋ค.
2. ์ฐ๊ฒฐ์ด ๋๊ธฐ๋ฉด ์๋์ผ๋ก ๋ค์ ์ฐ๊ฒฐํฉ๋๋ค.
3. SSE ์ฐ๊ฒฐ ์์ฒญ์, header๋ฅผ ์์ ํ ์ ์์ต๋๋ค.EventSource ์ธ์คํด์ค ์์ฑ
1
2
3
const eventSource = new EventSource('/api/notifications', {
withCredentials: true, // ์ฟ ํค ๊ธฐ๋ฐ ์ธ์ฆ
});
ํด๋ผ์ด์ธํธ๋ SSE ์๋ํฌ์ธํธ๋ก EventSource๋ฅผ ์์ฑํ์ฌ ์๋ฒ์ ์ฐ๊ฒฐํด์.
EventSource์ ํธ์ถ์ ๋ํ ์๋ต์ ๋ฐ์ ์์ ๋ถํฐ ์๋ฒ์์ ์ง์์ ์ธ ์ฐ๊ฒฐ์ด ์ ์ง๋๊ฒ ๋ฉ๋๋ค.
๋ฉ์ธ์ง ์ด๋ฒคํธ ์์
1
2
3
4
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('์ ์๋ฆผ ์์ :', data);
}
์๋ฒ์์ ๊ธฐ๋ณธ ์ด๋ฒคํธ๋ฅผ ์ ์กํ๋ฉด onmessage ์ด๋ฒคํธ๊ฐ ํธ์ถํด์.
์ด๊ณณ์์ ์์ ํ ๋ฐ์ดํฐ๋ฅผ ํ์ฑํ์ฌ ์๋ฆผ ๋ชฉ๋ก ๊ฐฑ์ ๋ฑ์ UI ์
๋ฐ์ดํธ๋ฅผ ์ํํฉ๋๋ค.
์๋ฌ ํธ๋ค๋ง
1
2
3
eventSource.onerror = (error) => {
console.error('SSE ์ฐ๊ฒฐ ์ค๋ฅ ๋ฐ์', error);
};
๋คํธ์ํฌ ์ค๋ฅ๋ ์๋ฒ ์ฐ๊ฒฐ ๋ฌธ์ ๋ฐ์ ์ ํธ์ถํด์.
ํ์์ ๋ฐ๋ผ ์ฌ์ฐ๊ฒฐ ๋ก์ง์ด๋ ์ฌ์ฉ์ ์๋ด ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ต๋๋ค.
SSE ์ฐ๊ฒฐ ์ข ๋ฃ
1
eventSource.close();
๋ ์ด์ ์ด๋ฒคํธ๋ฅผ ์์ ํ ํ์๊ฐ ์์ ๊ฒฝ์ฐ ์ฐ๊ฒฐ์ ์ข
๋ฃํด์.
์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์์ ์ด๋ ๋ก๊ทธ์์ ์์ ์ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ์ค์ ํ๋ก์ ํธ์ ์ด๋ป๊ฒ ์ ์ฉํ ์ ์์๊น์? ๐ง
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// setMessage : SSE๋ก ์ ๋ฌ๋ฐ์ ๋ฉ์ธ์ง๋ฅผ ํ
์ธ๋ถ์์ ์ฒ๋ฆฌํ๋ ๋ก์ง
export function useNotificationSSE(setMessage) {
useEffect(() => {
const eventSource = new EventSource('/api/notifications/subscribe', {
withCredentials: true,
});
eventSource.onmessage = (event) => {
setMessage(JSON.parse(event.data));
};
return () => eventSource.close();
}, [setMessage]);
}
EventSource ์ฐ๊ฒฐํ๋ ๋ก์ง์ Hook์ผ๋ก ๋ถ๋ฆฌํด์ ์ฌ์ฉํ๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์์ด์.
setMessage๋ฅผ ์ธ๋ถ์์ ์ฃผ์
ํ๋๋ก ์์ฑํ ์ด์ ๋ ์ฌ์ฌ์ฉ์ฑ๊ณผ Hook์ ์ฑ
์์ ๋ช
ํํ ํ๊ธฐ ์ํจ์ด์์ต๋๋ค.
Hook์ ํธ์ถ ์์น์ ๋ฐ๋ผ ๋ฉ์ธ์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ์ฐจ์ด๊ฐ ์์ ์ ์๊ณ , Hook์ ์ฑ
์์ SSE ์ฐ๊ฒฐ์ ์ ์ดํ๋ ๊ฒ์ผ๋ก ์ ํํ๊ฒ ๋๋ฉด ์ปดํฌ๋ํธ์์ ๋๋จธ์ง ์ฒ๋ฆฌ ๋ก์ง์ ๋ด๋นํ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ์ฒ๋ฆฌ ๋ก์ง์ ์์ ํ๊ฑฐ๋ ๋๋ฒ๊น
ํ๊ธฐ ์ํด Hook์ ๋ค์ฌ๋ค ๋ณผ ํ์๊ฐ ์๊ฒ ๋๊ธฐ ๋๋ฌธ์ด์ฃ ..
SSE์ ํ๋ฆ๋ง ์ดํดํ๊ณ ์๋ค๋ฉด, ์ด๋ ต์ง ์๊ฒ ์ฐ๊ฒฐํ ์ ์์ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ญ๋๋ค :)