최근 SSE라는 개념을 접하게 되었다. 지금까지의 진행한 프로젝트에서는 실시간으로 데이터를 받아와야 하는 일이 없었어서 처음 보는 개념이었다. 그래서 Server-Sent Events(SSE)에 대해 공부한 내용을 (프론트 개발자의 시점으로) 정리하려고 한다.
일반적으로 클라이언트는 새로운 데이터를 받기 위해 서버로 데이터를 요청한다. 하지만 Server-Sent Events 방식을 사용하면 웹페이지의 요청 없이도 언제든지 서버가 새로운 데이터를 보낼 수 있다. 이를 통해 실시간 스트리밍 형태로 데이터를 전달 받을 수 있다.
EventSource 객체로 편리하게 사용할 수 있다.나는 막연하게 실시간으로 데이터를 주고받는 데에는 WebSocket을 사용해야 한다고 알고 있었다. 그래서 SSE와 WebSocket은 각각 어떤 상황에서 사용해야 하는지 간략하게 표로 정리해봤다.
| 항목 | SSE | WebSocket |
|---|---|---|
| 프로토콜 | HTTP 기반 | WebSocket 프로토콜 |
| 통신 방향 | 서버 ➝ 클라이언트 | 서버 ⇄ 클라이언트 |
| 설정 난이도 | 간단함 (EventSource) | 복잡함 (Socket 서버 필요) |
| 용도 | 실시간 알림, 로그, 이벤트 스트림 | 채팅, 게임, 양방향 통신 필요 시 |
둘의 가장 큰 차이는 통신 방향이다.
따라서, 실시간 알림, 로그 전송 같은 가벼운 푸시 용도에는 SSE, 양방향 통신이 필요한 채팅, 게임 등은 WebSocket 이 더 적합하다.
EventSource 인터페이스를 사용해 SSE(Server-Sent Events)를 구현할 수 있다.
EventSource는 브라우저에서 서버가 보내는 스트리밍 데이터를 실시간으로 수신하기 위한 WebAPI 인터페이스로 브라우저에서만 제공되는 기능이다. 따라서 node 환경에서는 동작하지 않아 따로 폴리필을 설치해야 한다.

위 코드처럼 이벤트를 생성하는 스크립트의 URL을 사용해 EventSource 객체를 만들 수 있다.
만약 클라이언트와 서버의 오리진이 다르다면 CORS 에러가 발생할 수 있다. 이 경우 withCredentials옵션을 활성화 해야한다. (서버에서도 헤더설정을 따로 해주어야 한다.)
이제 .onmessage, .onerror, .addEventListener() 등을 이용해서 서버에서 푸시되는 메시지를 처리할 수 있다. 하나씩 살펴보자
서버에서는 보내는 메세지는 위와 같이 event 필드를 포함할 수도 있고, 생략할 수도 있다.
event 필드가 없는 경우 → onmessage 사용
기본적으로 event 필드가 생략된 메시지는 onmessage 핸들러로 수신된다. 서버가 메시지를 전송할 때마다 자동으로 onmessage가 호출되므로, 이 핸들러에 메시지를 수신한 뒤 실행할 로직을 등록하면 된다.e.data는 JSON 형태의 문자열 타입이다. 기본적으로 fetch()나 Axios를 통해 데이터를 받아올 때는 내부에서 자바스크립트 객체로 파싱해주기도 하지만 WebSocket이나 SSE를 통해 응답을 받게될 경우 반드시 파싱을 해야 한다.event 필드가 있는 경우 → addEventListener() 사용
서버가 event: customEvent와 같이 이벤트 이름을 명시한 경우에는, 클라이언트에서도 해당 이름으로 핸들러를 등록해줘야 한다. addEventListener메서드를 사용해 이벤트 이름을 첫 번째 인자로, 두번째 인자로는 메세지를 받았을 때 실행할 콜백함수를 작성하면 된다.SSE(Server-Sent Events)에서 서버가 클라이언트로 메시지를 보낼 때는 아래와 같이 정해진 텍스트 포맷을 따라야 한다.
text/event-stream 을 사용한다.\n\n)로 끝나야 전송이 완료됨을 의미한다.:)이면 해당 줄은 주석으로 처리된다. 이 주석은 클라이언트와의 연결 유지를 위해 사용된다.필드명: 값 형태의 한 줄로 작성된다.필드 종류는 아래와 같다.
event(선택) : 이벤트 유형을 식별하는 문자열. 클라이언트 측에서는 event 필드가 있으면 addEventListener() 를 사용하고, event 필드가 없으면 onmessage핸들러를 호출한다.data(필수) : 클라이언트로 전달할 데이터id(선택) : 이벤트 소스 객체의 마지막 이벤트 ID. 연결이 끊겼을 때 이 ID 이후부터 재전송 가능retry(선택) : 재연결까지의 대기 시간(ms) 설정받을 수 있는 메세지 예시를 살펴보자

: 문자로 시작하므로 주석이다. 클라이언트에서는 이 줄을 무시하지만, 서버-클라이언트 간 연결 유지를 위한 ping 용도로 자주 사용된다.data: some text로 이루어진 단일 메시지다. \n\n으로 끝나기 때문에 클라이언트에서는 이 블록을 하나의 메시지로 인식하며, e.data에는 "some text"가 담긴다.data:로 시작하는 여러 줄로 구성되어 있다. 클라이언트는 이 줄들을 줄바꿈(\n)으로 이어 붙인 뒤, 하나의 메시지로 처리한다. 따라서 e.data에는 "another message\nwith two lines"가 전달된다event 필드를 포함한다. 클라이언트는 이벤트 이름 usermessage를 기준으로 메세지를 처리할 수 있다.MDN 공식문서에서 PHP로 작성된 서버 예제를 볼 수 있다.
네트워크 타임아웃, 서버 다운, 접근제어 등의 문제가 발생하면 onerror 핸들러가 실행된다. SSE는 기본적으로 자동 재연결을 시도하지만, 반복적인 실패에 대한 처리는 직접 구현해야 한다. 에러 발생 시 수행할 콜백함수는 onerror에 등록하면 된다.
close() 메서드를 호출하면 서버와의 스트림 연결이 종료된다. 페이지 이탈 시 또는 필요 없어진 시점에 명시적으로 닫아주는 것이 좋다.
앞서 SSE와 WebSocket의 차이에서 간단히 언급했지만, SSE가 실제로 어떤 상황에서 유용하게 쓰일 수 있는지를 조금 더 구체적으로 적어보았다.
EventSource로 쉽게 구현 가능하다.JSON.parse()를 잊고 있었다🥲 하지만 괜찮음. 이제 기억하면 되니까