Backend

Server-Sent Event (SSE)란? feat Node.js

연_우리 2022. 9. 20. 01:35
반응형

목차



    기존 프로젝트에선 백엔드 -> 프론트로 데이터를 보내줄때 웹소켓을 사용하였다.
    웹소켓은 양방향인데, 굳이 프론트 -> 백엔드방향으로 연결되어있을 필요가 없어서
    이것저것 찾아보니 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로 데이터를 필터링해서 받을 수도 있다 !
    반응형
    • 네이버 블러그 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 구글 플러스 공유하기
    • 카카오톡 공유하기