Server-Sent Event(이하 SSE)는 서버에서 클라이언트로 실시간 데이터를 푸시하는 단방향 통신 방식입니다. 클라이언트가 서버와의 연결을 설정하면, 서버는 지속적으로 데이터를 클라이언트로 전송할 수 있습니다. SSE는 HTML5 표준으로, 브라우저에서 이벤트 스트림을 수신하는 기능을 제공합니다.
SSE의 특징
- 단방향 통신: 클라이언트가 서버에 연결을 설정한 후, 서버는 실시간으로 클라이언트에게 데이터를 보낼 수 있지만, 클라이언트는 이 연결을 통해 서버로 데이터를 전송할 수 없습니다. 클라이언트에서 서버로는 일반적인 HTTP 요청을 통해 데이터를 전송해야 합니다.
- 텍스트 데이터 전송: SSE는 서버에서 클라이언트로 텍스트 데이터를 전송합니다. JSON 같은 형식으로 데이터를 보내는 것이 일반적입니다.
- 브라우저 지원: SSE는 대부분의 최신 웹 브라우저에서 지원됩니다. JavaScript의 EventSource 객체를 사용하여 쉽게 구현할 수 있습니다.
- 자동 재연결: 연결이 끊어진 경우, 브라우저가 자동으로 재연결을 시도하는 기능이 내장되어 있습니다. 이를 통해 일시적인 네트워크 문제에도 연결이 지속될 수 있습니다.
- 단일 HTTP 연결: WebSocket처럼 별도의 프로토콜을 사용하지 않고, 단일 HTTP 연결을 유지하면서 서버에서 클라이언트로 데이터를 지속적으로 보낼 수 있습니다.
SSE의 사용 사례
- 실시간 알림 시스템: 서버에서 발생하는 중요한 이벤트(알림, 채팅 메시지 등)를 클라이언트에게 실시간으로 전송.
- 주식 가격 또는 실시간 데이터 피드: 실시간으로 변화하는 데이터를 클라이언트에게 스트리밍.
- 대시보드 업데이트: 실시간으로 서버 상태나 통계 데이터를 클라이언트에게 전달.
SSE의 장점
- 단순성: HTTP를 기반으로 하므로 기존 인프라에서 쉽게 사용할 수 있습니다.
- 자동 재연결: 클라이언트가 끊어진 경우 자동으로 재연결을 시도합니다.
- 다수의 브라우저 지원: 대부분의 현대적인 웹 브라우저에서 지원되며, 설정이 간단합니다.
SSE의 단점
- 단방향: 클라이언트에서 서버로의 데이터 전송은 지원되지 않으므로, 양방향 통신이 필요한 경우 WebSocket이 더 적합합니다.
실습을 통해 SSE에 대해서 알아보겠습니다.
동작원리
- 클라이언트가 서버로 EventSource(SseEmitter)를 요청합니다.
- 서버는 클라이언트 식별자를 이용하여 SseEmitter를 생성합니다.
- 서버는 생성된 SseEmitter를 클라이언트에 응답합니다.
- 서버에서 이벤트가 발생합니다.
- 서버는 클라이언트에 이벤트 메시지를 전송합니다.
코드
- 클라이언트가 서버로 EventSource(SseEmitter)를 요청합니다.
// 서버에 eventSource 요청
const eventSource = new EventSource('/notifications');
eventSource.addEventListener('notification', function(event) {
// 이벤트 메시지를 수신하면 처리될 로직
// event.data -> 이벤트 메시지
});
eventSource.onerror = function() {
eventSource.close();
};
eventSource.addEventListener(type, listener[, options]);
type: 서버가 보내는 이벤트의 유형
listener: 수신된 이벤트를 처리하는 리스너
- 서버는 클라이언트 식별자를 이용하여 SseEmitter를 생성합니다.
- 서버는 생성된 SseEmitter를 클라이언트에 응답합니다.
public class NotificationController {
private final SseEmitterService sseEmitterService;
@GetMapping("/notifications")
public SseEmitter subscribe(@AuthenticationPrincipal MemberPrincipal principal) {
// clientId는 커스텀으로 구현해야 합니다.
return sseEmitterService.createOrGetEmitter(principal.clientId());
}
}
public class SseEmitterService {
private final ConcurrentHashMap<String, SseEmitter> sseMap = new ConcurrentHashMap<>();
public SseEmitter createOrGetEmitter(String clientId){
if(sseMap.containsKey(clientId)){
return sseMap.get(clientId);
}
final SseEmitter sseEmitter = new SseEmitter(-1L);
sseMap.put(clientId, sseEmitter);
sseEmitter.onCompletion(() -> sseMap.remove(clientId));
sseEmitter.onTimeout(() -> sseMap.remove(clientId));
sseEmitter.onError(e -> sseMap.remove(clientId));
return sseEmitter;
}
}
클라이언트의 요청이 많아질수록 서버가 매번 SseEmitter 객체를 생성하면 리소스 부담이 커집니다.
클라이언트당 생성되는 SseEmitter는 한 개로 제한합니다.
SseEmitter는 메시지를 전송하거나, 타임아웃이 발생하거나, 에러가 발생할 경우 SseMap에서 제거합니다.
- 서버에서 이벤트가 발생합니다.
- 서버는 클라이언트에 이벤트 메시지를 전송합니다.
public class EventHandler {
private final SseEmitterService sseEmitterService;
@TransactionalEventListener
public void sendToClient(Event event){
try{
sseEmitterService.sendNotification(event.clientId(), 메시지);
} catch (IOException e) {
log.error(e.getMessage());
}
}
}
public class SseEmitterService {
...
public void sendNotification(String clientId, String message) throws IOException {
final SseEmitter emitter = sseMap.get(clientId);
if (emitter != null) {
emitter.send(SseEmitter.event().name("notification").data(message));
try{
emitter.complete();
}catch(Exception ex){
log.error(ex.getMessage());
emitter.completeWithError(ex);
}
}
}
}
SseEmitter는 complete(), completeWithError() 메서드를 통해 사용된 SseEmitter는 종료시킵니다.
클라이언트에서 선언한 이벤트 타입인 'notification'으로 서버는 메시지를 보내야 합니다.
'[개발] 프레임워크 > Spring' 카테고리의 다른 글
build.gradle 파일 문법에 대해서 알아보자. (1) | 2024.10.22 |
---|---|
MyBatis useGeneratedKeys 조심히 사용하기 (0) | 2024.10.07 |
Postman에서 카카오 OAuth AccessToken 발급 받기 (0) | 2024.09.08 |
Security 설정으로 static 파일 허용 (0) | 2024.08.20 |
Spring Retry (0) | 2024.08.11 |