๋ชฉ์ฐจ
๊ธฐ์กด ํ๋ก์ ํธ์์ ๋ฐฑ์๋ -> ํ๋ก ํธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ค๋ ์น์์ผ์ ์ฌ์ฉํ์๋ค.
์น์์ผ์ ์๋ฐฉํฅ์ธ๋ฐ, ๊ตณ์ด ํ๋ก ํธ -> ๋ฐฑ์๋๋ฐฉํฅ์ผ๋ก ์ฐ๊ฒฐ๋์ด์์ ํ์๊ฐ ์์ด์
์ด๊ฒ์ ๊ฒ ์ฐพ์๋ณด๋ SSE๋ฅผ ์๊ฒ๋์๊ณ , ์ฌ์ฉํด๋ณด์๋ค!
SSE๋ ?
SSE๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์คํธ๋ฆฌ๋ฐ ํ๋ ๊ธฐ์ ์ด๋ค.
๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ์ง์์ ์ผ๋ก API๋ฅผ ํธ์ถํ์ฌ ๋๊ธฐํํ๋ ์์
์ ์์จ ์ ์๋ ๊ฒ์ด๋ค!
- ์น์์ผ์ WSS ํ๋กํ ์ฝ์ ๋ฐ๋ก ์ฌ์ฉํ์ง๋ง
SSE๋ HTTP๋ฅผ ์ฌ์ฉํ๊ธฐ๋๋ฌธ์ ๋ณ๋ค๋ฅธ ์๋ฒ ์
ํ
์ด ํ์ํ์ง ์๋ค๋ ์ฅ์ ์ด ์๋ค.
- ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์ต์ด ํ๋ฒ HTTP์ฐ๊ฒฐ์ ๋งบ์ผ๋ฉด ๊ทธ ๋ค๋ก ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์ง์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์๋ค.
EX )
์๋ฒ์์ ๊ฐ๋์ฉ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ผํ๋๋ฐ, N์ด๋์ ๋ฐ์ดํฐ๊ฐ ์ค์๊ฐ์ผ๋ก ๋ณ๊ฒฝ๋๋ค.
์ฌ์ง์ด N์ด๋ ๋งค๋ฒ ๊ฐ์ด ๋ฐ๋๋คํ์.
API ํธ์ถ : 1ํ ํธ์ถ์ 1ํ ์๋ต๊ฐ์ ์ค์์๋ค. ์ง์์ ์ผ๋ก ๊ฐ์ ๋ณํ๋ฅผ ์๋ ค๋ฉด ๋งค ์ด๋ง๋ค api๋ฅผ ํธ์ถํด์ผํ๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์ฃผ์ฒด๊ฐ๋๋ค.
websocket : ์์ผ์ ์
ํ
ํ์ฌ ํด๋ผ์ด์ธํธ์ ์๋ฒ๋ฅผ ์ฐ๊ฒฐํ๋ค. ํด๋ผ์ด์ธํธ/์๋ฒ ๋๋ค ์ฃผ์ฒด๊ฐ ๋ ์ ์๋ค.
API์ WebSocket ์ค๊ฐ์์ ๊ฐ๋ณ๊ฒ ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด SSE์ด๋ค.
SSE๋ ํด๋ผ์ด์ธํธ์์ 1ํ ํธ์ถํ๋ฉด ์๋ฒ์์ ์ง์ ํ ์๊ฐ๋งํผ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด์ค ์ ์๋ค. ์๋ฒ๊ฐ ์ฃผ์ฒด๊ฐ๋๋ค.
SSE๋ฅผ ๋
ธํฐ, ํธ์, pub/sub์ผ๋ก ์ฌ์ฉํ๋ ์์ ๊ฐ ์ข
์ข
๋ณด๋ฉด์
์๋ฒ์์ ์ด๋ฒคํธ(ํธ์)๋ฅผ ์ฌ๋ฌ๋ฒ ๋ณด๋ด๋ ๊ฒ๋ ๊ฐ๋ฅํ๊ฑด๊ฐ?! ํ๊ณ ํผ๋๋์๋ค.
setInterval ๋ด๋ถ์์ ๋ถ๊ธฐ์ฒ๋ฆฌํด์ ํด๋ผ์ด์ธํธ๋ ํธ์๋ฅผ ๋ฐ๋ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง
์๋ฒ์์๋ ๊ณ์ํด์ ๋ฐ์ดํฐ๋ฅผ ์ฒดํฌํด์ผํ๋ ๊ฒ ๊ฐ๋ค.
๋ด๊ฐ ์ํ๋๊ฑด ๊ทธ๋ฅ send ํ์ ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐ์ดํฐ๊ฐ ์ ์ก๋๋ ๊ฒ์ด์๋๋ฐ..
์ฐพ์๋ณผ์๋ก ๋
ธํฐ, ํธ์๋ SSE์ ๋ชฉ์ ๊ณผ๋ ์ข ๋ค๋ฅด๊ฒ ์ฐ์ด๋ ๊ฒ ๊ฐ์ ๋๋์ ๋ฐ์๋ค!
๋ฐ์ดํฐ ํฌ๋งท
id: testN1\n
event: red\n
data: {"message" : "hello SSE!", "text" : "blah-blah"}\n\n
"\n" ๊ฐํ๋ฌธ์๋ก ๊ฐ ํญ๋ชฉ์ ๊ตฌ๋ถํ๋ค. \n๋ฅผ ๋๋ฒ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ์ ์ก์ ๋์ผ๋ก ์ฒ๋ฆฌํ๋ค.
id๋ฅผ ์ถ๊ฐํ๋ฉด ๋ง์ง๋ง์ ๋ฐ์ํ ์ด๋ฒคํธ๋ฅผ ์ถ์ ํ ์ ์๋ค. (event.lastEventId)
event๋ฅผ ์ถ๊ฐํ๋ฉด pub/sub์ ์ฑ๋์ฒ๋ผ ๊ตฌ๋ถํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋ค.
test.html
<html>
<body>
<script>
//๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ URL์ ์์ฑํ๋ค.
const eventSource = new EventSource("http://localhost:3000/test", {withCredentials:false});
//๋ธ๋ผ์ฐ์ ๊ฐ SSE์ง์ํ๋์ง ์ฒดํฌ
if(typeof(EventSource) !== "undefined") {
console.log("sse์ง์");
} else {
console.log("sse๋ฏธ์ง์");
}
// ์๋ฒ์ ์ปค๋ฅ์
์ด ๋งบ์ด์ง ๋ ๋์ํ๋ค
eventSource.addEventListener('open', function(e) {
console.log(`connection is open`);
});
// ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋ event์์ด ๋ณด๋ด๋ฉด ๋์ํ๋ค
eventSource.addEventListener('message', function(e) {
console.log(event.data);
});
// ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋ event๋ฅผ red๋ก ์ค์ ํด์ ๋ณด๋ผ ๋ ๋์ํ๋ค
eventSource.addEventListener('red', event => {
const data = JSON.parse(event.data);
console.log(`red : ${data.message}`);
});
// ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ๋ event๋ฅผ blue๋ก ์ค์ ํด์ ๋ณด๋ผ ๋ ๋์ํ๋ค
eventSource.addEventListener('blue', event => {
const data = JSON.parse(event.data);
console.log(`blue : ${data.message}`);
});
// ์๋ฌ ๋ฐ์ ์ ๋์ํ๋ค.
eventSource.addEventListener('error', function(e) {
if (e.eventPhase == EventSource.CLOSED){
eventSource.close()
}
if (e.target.readyState == EventSource.CLOSED) {
console.log("Disconnected");
}
else if (e.target.readyState == EventSource.CONNECTING) {
console.log("Connecting...");
}
}, false);
</script>
</body>
</html>
Node Express Server
ํ ์คํธ 1
router.get('/test', (req, res) => {
res
.setHeader("Access-Control-Allow-Origin", "*")
.setHeader("Content-Type", "text/event-stream")
.setHeader("Connection", "keep-alive")
.setHeader("Cache-Control", "no-cache")
.status(200)
.write(
'event: red\n'+
'data: {"message" : "hello '+value+'"}\n\n'
);
});
SSE๋ ์์ ์ฝ๋๋ ๋์ํ ๊น?
test.html์ ์ด๋ฉด ์ฌ์ง์ฒ๋ผ ๋์ค๊ณ ๋์ด์ ์๋ฌด๋ฐ ๋์๋ ํ์ง ์๋๋ค.
์ฒซ๋ฒ์งธ ์ปค๋ฅ์
์ฐ๊ฒฐ ์ดํ ์๋ฒ์์ ๋ด๋ ค์ฃผ๋ ๊ฐ์ ํ๋๋ฐ์ ์๋๋ฐ ์ข
๋ฃ๋ ๋์ง์์ผ๋.. ์์ง์ด์ง ์๋๊ฒ ๋น์ฐํ๋ค
(๋๋ ์ ์ฝ๋์์ /test๋ฅผ ๋ค์ ํธ์ถํ๋ฉด ๋ฐ์ดํฐ๊ฐ ๋ด๋ ค์ฌ ์ค ์์๋ค.. ํํ)
ํ ์คํธ 2
let value = 'SSE';
router.get('/change', (req, res) => {
let {param} = req.query;
value = param;
res.end();
});
router.get('/test', (req, res) => {
res
.setHeader("Access-Control-Allow-Origin", "*")
.setHeader("Content-Type", "text/event-stream")
.setHeader("Connection", "keep-alive")
.setHeader("Cache-Control", "no-cache")
setInterval(() => {
res
.status(200)
.write(
'event: red\n'+
'data: {"message" : "hello '+value+'"}\n\n'
);
}, 2000);
});
ํ
์คํธ 2์ ์ฝ๋๋ ์๋ฒ๊ฐ 2์ด๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ๊ณ์ ๋ณด๋ด์ฃผ๋๊น test.html์ ์คํํ์๋ง์ ๊ณ์ํด์ ์๋ฒ์ ๋ฐ์ดํฐ๊ฐ์ ๋ฐ์์จ๋ค.
์ฌ๊ธฐ์ /change?param=green์ ํธ์ถํ์ฌ value๋ฅผ ๋ฐ๊พผ๋ค๋ฉด
๋ฐ์ดํฐ๊ฐ ์ ๋ฐ๋์ด์ ํ๋ก ํธ๋ก ์จ๋ค!
์๋ฒ์์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ์ ์ ์บ์นํด์ setInterval ๋ด๋ถ์์ res.writeํด์ฃผ๋ฉด
ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ ์ง์ํ์ง์๊ณ ๋ ๋ณ๊ฒฝ๋ ๊ฐ์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ ์ ์๊ฒ๋๋ค.
event๋ก ๋ฐ์ดํฐ๋ฅผ ํํฐ๋งํด์ ๋ฐ์ ์๋ ์๋ค !