[Spring] SSE(Server-Sent Events)란? EventSource, SseEmitter 구현
by 우와한개발자
1. Server-SentEvent란?
- 서버에서 클라이언트로 단방향 실시간 데이터를 스트리밍하는 기술
- WebSocket과 달리 HTTP 프로토콜을 그대로 사용
- 서버가 클라이언트에게 일방적으로 데이터를 푸시
1) SSE vs WebSocket 비교
| 구분 | SSE | WebSocket |
| 통신 방향 | 단방향 (서버 → 클라이언트) | 양방향 |
| 프로토콜 | HTTP | ws:// (별도 프로토콜) |
| 자동 재연결 | 브라우저가 자동 처리 | 직접 구현 필요 |
| 구현 난이도 | 쉬움 | 복잡함 |
| 사용 사례 | 알림, 뉴스피드, 진행률 | 채팅, 게임, 실시간 협업 |
2. EventSource 객체
- 클라이언트(브라우저)에서 SSE 연결을 수립하고 서버로부터 이벤트를 수신하는 브라우저 내장 API
1) EventSource 속성/이벤트
| 속성/이벤트 | 설명 |
| readyState | 서버와의 접속 상태를 나타냄. (상수로 확인 가능). - CONNECTING : 서버에 접속 요청 중 (0) - OPEN : 서버에 접속됨 (1) - CLOSED : 서버에 접속되지 않음 (2) |
| open | 서버와 접속이 되면 발생 |
| message | 서버로부터 응답이 도착하면 발생 |
| error | 오류 발생 시 또는 연결이 끊어졌을 때 발생 |
| close() | EventSource 연결을 직접 종료하는 메서드 |
const es = new EventSource("/sse/connect");
console.log(es.readyState); // 0 : CONNECTING
es.onopen = () => {
console.log(es.readyState); // 1 : OPEN
};
es.onmessage = (event) => {
console.log(event.data);
};
es.onerror = () => {
console.log(es.readyState); // 2 : CLOSED
};
2) EventSource 객체 생성
const eventSource = new EventSource("/sse/connect");
3) 데이터 수신
// 기본 메시지 수신 (이벤트명 없는 경우)
eventSource.onmessage = (event) => {
console.log("받은 데이터 : " + event.data);
};
// 특정 이벤트명으로 수신
eventSource.addEventListener("notification", (event) => {
console.log("알림 : " + event.data);
});
// 연결 수립 시
eventSource.onopen = () => {
console.log("SSE 연결 성공");
};
// 오류 발생 시
eventSource.onerror = (error) => {
console.error("SSE 오류 : ", error);
eventSource.close(); // 연결 종료
};
4) 서버의 응답 데이터 형식
- SSE 응답은 text/event-stream Content-Type으로
- 각 필드는 필드명: 값 형식으로 구성
data: 안녕하세요\\n\\n
event: notification\\n
data: 새 알림이 있습니다\\n\\n
id: 1\\n
event: update\\n
data: {"name": "홍길동"}\\n
retry: 3000\\n\\n
(1) 사용할 수 있는 필드명
- 각 이벤트는 빈 줄(\n\n)로 구분
- data만 필수이고 나머지는 선택.
| 필드명 | 설명 |
| data | 전송할 실제 데이터 (필수) |
| event | 이벤트 이름 (생략 시 onmessage로 수신) |
| id | 이벤트 ID (재연결 시 Last-Event-ID 헤더로 전달) |
| retry | 재연결 대기 시간 (밀리초) |
3. SSE 구현 - Spring Boot (Maven)
1) pom.xml
- SSE는 별도 의존성 없이 spring-boot-starter-web만 있으면 사용 가능
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2) Controller
@RestController
@RequestMapping("/sse")
public class SseController {
// 연결된 emitter 목록 관리
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
// 클라이언트 연결
@GetMapping("/connect")
public SseEmitter connect() {
SseEmitter emitter = new SseEmitter(60 * 1000L); // 60초 타임아웃
emitters.add(emitter);
// 연결 종료/타임아웃/오류 시 목록에서 제거
emitter.onCompletion(() -> emitters.remove(emitter));
emitter.onTimeout(() -> emitters.remove(emitter));
emitter.onError(e -> emitters.remove(emitter));
return emitter;
}
// 모든 클라이언트에게 이벤트 전송
@PostMapping("/send")
public ResponseEntity<Void> send(@RequestParam String message) {
for (SseEmitter emitter : emitters) {
try {
emitter.send(SseEmitter.event()
.name("notification") // event 필드
.data(message)); // data 필드
} catch (IOException e) {
emitters.remove(emitter);
}
}
return ResponseEntity.ok().build();
}
}
4. SSE 구현 - Spring Legacy
1) pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.30</version>
</dependency>
2) Controller
@Controller
@RequestMapping("/sse")
public class SseController {
private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@GetMapping("/connect")
@ResponseBody
public SseEmitter connect() {
SseEmitter emitter = new SseEmitter(60 * 1000L);
emitters.add(emitter);
emitter.onCompletion(() -> emitters.remove(emitter));
emitter.onTimeout(() -> emitters.remove(emitter));
return emitter;
}
@PostMapping("/send")
@ResponseBody
public ResponseEntity<Void> send(@RequestParam String message) {
for (SseEmitter emitter : emitters) {
try {
emitter.send(SseEmitter.event()
.name("notification")
.data(message));
} catch (IOException e) {
emitters.remove(emitter);
}
}
return ResponseEntity.ok().build();
}
}
5. Spring Boot vs Spring Legacy 비교
| 구분 | Spring Boot | Spring Legacy |
| 의존성 | spring-boot-starter-web (기본 포함) | spring-webmvc 별도 추가 |
| 설정 방식 | 자동 설정 | 수동 설정 필요 |
| SseEmitter | 동일하게 사용 | 동일하게 사용 |
| 배포 방식 | JAR 또는 WAR | WAR (외부 톰캣) |
'프레임워크 > Spring' 카테고리의 다른 글
| [Spring] Spring Boot 테스트 코드 작성하기 – JUnit5, Mockito, MockMvc 구현 (0) | 2026.04.12 |
|---|---|
| [ Spring ] Spring Boot 배포 방식 – JAR vs WAR, 외부 톰캣 배포 (0) | 2026.04.07 |
| [Spring] WebSocket이란? 스프링 프로젝트 웹소켓 구현 (0) | 2026.04.07 |
| [Spring] Spring HATEOAS란? – EntityModel, ModelAssembler 구현 (0) | 2026.04.07 |
| [Spring] HTTP Method Conversion과 ETag(Entity Tag)란? X-HTTP-Method-Override, ShallowEtagHeaderFilter (0) | 2026.04.07 |
블로그의 정보
우와한개발자 님의 블로그
우와한개발자