목차
기존 프로젝트에선 백엔드 -> 프론트로 데이터를 보내줄때 웹소켓을 사용하였다.
웹소켓은 양방향인데, 굳이 프론트 -> 백엔드방향으로 연결되어있을 필요가 없어서
이것저것 찾아보니 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로 데이터를 필터링해서 받을 수도 있다 !
'Backend' 카테고리의 다른 글
[SpringBoot] Apache Poi를 이용한 엑셀다운로드는 SXSSF를 쓰자..! (0) | 2022.12.03 |
---|---|
Spring Jpa SelfJoin 순환참조 방지하며 다른 엔티티와 맵핑하기 (2) | 2022.11.09 |
[SpringBoot] Apache Poi를 이용한 엑셀 다운로드 구현 (0) | 2022.08.20 |
[Spring Boot] FeignClient와 ExceptionHandler | FeignClient의 응답값 그대로 반환하기 (1) | 2022.07.29 |
모던 자바 인 액션 - 스트림 활용 (0) | 2022.07.16 |