[QnA 게시판] 알림기능 SSE(Server-Sent Events) 적용

Updated:

Categories:

Tags: , ,

📌 개인적인 공간으로 공부를 기록하고 복습하기 위해 사용하는 블로그입니다.
정확하지 않은 정보가 있을 수 있으니 참고바랍니다 :😸
[틀린 내용은 댓글로 남겨주시면 복받으실거에요]

SSE(Server-Sent Events) 적용하기

게시판 만들다가 알림 기능은 어떻게 구현할까 하고 찾아봤는데 spring 에서 지원해주는 SseEmitter 클래스가 있어서 알아보았다.

SSE란?

  • 서버가 클라이언트에게 실시간으로 메세지를 보내는 방법
  • 실시간 알림기능을 구현하는데 유용한 기술
  • 스프링부트 4.2 이상 버전부터 SseEmitter 클래스를 지원한다
  • SseEmitter 클래스는 Spring MVC의 일부로 서버가 클라이언트에게 단방향으로 이벤트를 보낼 수 있게 해주며 클라이언트는 EventSource API를 사용하여 서버에서 보낸 이벤트를 받을 수 있다.

SseEmitter 클래스 주요 기능과 메서드

  1. 생성자
    • SseEmitter() : 기본 생성자, 타임 아웃 설정 없이 인스턴스 생성
    • SseEmitter(Long timeout) : 지정된 타임아웃을 가진 SseEmitter 인스턴스를 생성
  2. 주요 메서드
    • send(Object object) : 단일 객체를 JSON 형식으로 클라이언트에게 전송한다. 클라이언트는 이 데이터를 기본 이벤트로 수신한다.
    • send(SseEmitter.SseEventBuilder event) : 이벤트 빌더를 사용하면 이벤트를 세밀하게 설정하고 클라이언트에게 전송한다. 이벤트 이름/ 데이터 / ID 등을 설정 할 수 있다.
    • complete( ) : SSE 스트림을 정상적으로 완료한다, 클라이언트에게 EOF(End Of File)를 전송하여 스트림을 닫는다.
    • completeWithError(Throwable ex) : 지정된 에러와 함께 SSE 스트림을 완료한다. 클라이언트에게 에러를 전송하고 스트림을 닫는다.
    • onCompletion(Runnable callback) : 스트림이 타임아웃 되었을 때 실행할 콜백을 설정한다.
    • onTimeout(Runnable callback) : 스트림이 타임아웃되었을 때 실행할 콜백을 설정한다.
    • setTimeout(Long timeout) : 타임아웃을 설정한다.
  3. 활용 방법

    SseEmitter 클래스는 실시간으로 데이터 업데이트를 클라이언트에게 푸시해야 하는 다양한 애플리케이션에 유용하다. 예를 들어, 채팅 애플리케이션, 실시간 알림 시스템, 라이브 피드 업데이트 등을 구현할 때 사용할 수 있다. SseEmitter를 사용하면 서버가 클라이언트에게 비동기적으로 데이터를 전송할 수 있어, 클라이언트는 새로운 데이터를 즉시 받아볼 수 있다.

적용 방법

  1. 클라이언트 → 서버 : 알림요청

    1
    
     const eventSource = new EventSource('/notifications');
    
  2. 서버 → 클라이언트 : 연결 설정 및 메세지 보내기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    
     // 연결 설정
        
     @GetMapping("/notifications")
     public SseEmitter subscribe() {
         SseEmitter sseEmitter = new SseEmitter(3600000L); // 1시간 동안 연결 유지
         // 연결이 종료되면 제거
         sseEmitter.onCompletion(() -> emitterRepository.delete(userId));
         sseEmitter.onTimeout(() -> emitterRepository.delete(userId));
         return sseEmitter;
     }
        
     // 메세지 보내기
        
     private void sendNotification(String message, String username) {
         notificationService.getEmittersForUser(username).forEach(emitter -> {
             try {
                 emitter.send(SseEmitter.event().name("notification").data(message));
             } catch (IOException e) {
                 emitter.completeWithError(e);
             }
         });
     }
        
    
  3. 클라이언트가 메세지 받기

    1
    2
    3
    4
    5
    
     eventSource.addEventListener('notification', function(event) {
         const newElement = document.createElement("li");
         newElement.textContent = event.data;
         document.getElementById("notifications").appendChild(newElement);
     });
    

SseEmitter 외 다른 알림 기능

WebSocket

  • 장점: 양방향 통신 가능, 실시간 데이터 전송에 적합
  • 단점: 서버와 클라이언트 모두 WebSocket을 지원해야 함
  • 채팅 애플리케이션, 실시간 게임, 주식 시세 등 실시간 데이터 전송이 필요한 애플리케이션에 적합

적용방법

  1. 의존성 추가

    1
    2
    3
    
     dependencies {
         implementation 'org.springframework.boot:spring-boot-starter-websocket'
     }
    
  2. WebSocket 설정

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
     import org.springframework.context.annotation.Configuration;
     import org.springframework.web.socket.config.annotation.EnableWebSocket;
     import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
     import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
        
     @Configuration
     @EnableWebSocket
     public class WebSocketConfig implements WebSocketConfigurer {
         @Override
         public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
             registry.addHandler(new NotificationHandler(), "/notifications").setAllowedOrigins("*");
         }
     }
        
    
  3. WebSocket 핸들러

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     import org.springframework.web.socket.TextMessage;
     import org.springframework.web.socket.WebSocketSession;
     import org.springframework.web.socket.handler.TextWebSocketHandler;
        
     public class NotificationHandler extends TextWebSocketHandler {
         @Override
         protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
             // 메시지 처리 로직
             session.sendMessage(new TextMessage("New notification!"));
         }
     }
        
    

Long Polling

  • 클라이언트가 서버에 요청을 보내고, 서버는 새로운 데이터가 있을 때까지 응답을 지연시키는 방식
  • 데이터가 준비되면 서버는 응답을 보내고, 클라이언트는 다시 요청을 보낸다
  • WebSocket이나 SSE를 사용할 수 없는 환경에서 사용될 수 있습니다.
  • 장점: 비교적 구현이 간단, 모든 브라우저에서 지원
  • 단점: 서버 리소스 소모가 큼, 지연 시간이 있을 수 있음

적용방법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class NotificationController {
    @GetMapping("/notifications")
    public DeferredResult<String> getNotifications() {
        DeferredResult<String> deferredResult = new DeferredResult<>(30000L);
        new Thread(() -> {
            try {
                Thread.sleep(10000); // 예시로 10초 대기
                deferredResult.setResult("New notification!");
            } catch (InterruptedException e) {
                deferredResult.setErrorResult(e);
            }
        }).start();
        return deferredResult;
    }
}

Push Notifications

모바일 애플리케이션과 웹 애플리케이션에서 많이 사용

서버는 푸시 서비스(APNs, FCM 등)를 통해 클라이언트에게 푸시 메시지를 보낸다.

  • 장점: 모바일 및 웹 애플리케이션 모두에 적합, 사용자가 앱을 사용 중이지 않을 때도 알림 전송 가능
  • 단점: 설정이 복잡할 수 있음, 브라우저 푸시 지원 필요

적용 방법

  1. 의존성 추가

    1
    2
    3
    
     dependencies {
         implementation 'nl.martijndwars:web-push:5.1.0'
     }
    
  2. Controller

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
     import nl.martijndwars.webpush.Notification;
     import nl.martijndwars.webpush.PushService;
     import org.springframework.web.bind.annotation.PostMapping;
     import org.springframework.web.bind.annotation.RequestBody;
     import org.springframework.web.bind.annotation.RestController;
        
     @RestController
     public class NotificationController {
         private PushService pushService = new PushService();
        
         @PostMapping("/sendNotification")
         public void sendNotification(@RequestBody Subscription subscription) throws Exception {
             Notification notification = new Notification(subscription, "New notification!");
             pushService.send(notification);
         }
     }
        
    

Project 카테고리 내 다른 글 보러가기

Leave a comment