본문 바로가기
Springboot

Springboot -SSE ( 로컬에서는 실시간 o / https nginx 서버에서는 실시간 x)

by JunsC 2024. 6. 23.
728x90

스프링부트로 SSE 를 구현해서 소켓 대용으로 효율적인 실시간 스트림 관리를 하고 싶었다. 로컬 환경에서는 SSEemitter keep-alive 시간을 1분간 유지하도록 설정해두고 테스트를 해본 결과 만족스럽게 연동이 잘 되었다.

그리고 실 서버 에 업로드를 하고 테스트를 해본결과 갑자기 실시간 스트림이 연동되지 않았다 !! 두둥..

onCompleteion 만료되었을때만 담아두었던 데이터가 왔다. .. 

심지어 담아둔 데이터가 아니라 초기 더미데이터가 온것이다... 

2일이 걸려 해답을 찾았다...

 

내 생각에는 http, https 의 차이일거라고 생각했다. 거기에 중점을 두어서 구글링과 챗봇 찬스를 쓴 결과 

 

nginx 를 이용한 역방향 프록시로 인해 실시간 스트림이 nginx 버퍼링에 걸리게 되었고 이게 실시간 스트림으로 클라이언트에게 데이터가 바로 가는게 아니라 nginx 버퍼에 담아두고 일정 시간 이후 같이 보내준다는 것이었다 !!!

 

아.... 맞아..... 내가 환경설정 해 둔것이 역방향 프록시라는 부분이였구나...

 

SseEmitter에는 직접 헤더를 설정하는 기능이 없습니다. 그러나, X-Accel-Buffering 헤더를 추가하려면 필터를 통해 설정하거나 직접 HTTP 응답을 제어해야 합니다. X-Accel-Buffering 헤더를 추가하기 위해 Spring 필터를 사용할 수 있습니다.

Spring Filter 사용하여 헤더 추가

Spring에서 필터를 사용하여 모든 HTTP 응답에 X-Accel-Buffering 헤더를 추가할 수 있습니다.

  1. 필터 클래스 생성:
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class SSEFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 초기화 코드 (필요한 경우)
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        if (response instanceof HttpServletResponse) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("X-Accel-Buffering", "no");
        }
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 정리 코드 (필요한 경우)
    }
}
  1. 필터 등록:

Spring Boot 설정 클래스에서 필터를 등록합니다.

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<SSEFilter> sseFilter() {
        FilterRegistrationBean<SSEFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new SSEFilter());
        registrationBean.addUrlPatterns("/connect");
        return registrationBean;
    }
}

이 설정은 /connect 엔드포인트에 대한 모든 응답에 X-Accel-Buffering: no 헤더를 추가합니다. 이렇게 하면 Nginx가 이 엔드포인트에 대한 응답을 버퍼링하지 않게 됩니다.

응답 헤더에 X-Accel-Buffering 직접 추가

필터를 사용하지 않고도, 특정 컨트롤러 메소드 내에서 직접 헤더를 추가할 수 있습니다:

 

@GetMapping(value = "/connect", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> connectRedisSSE() throws IOException {
    SseEmitter emitter = sseService.connect("dummyData").getBody();
    return ResponseEntity.ok()
            .header("X-Accel-Buffering", "no")
            .body(emitter);
}
하지만 SseEmitter와 함께 이 방법을 사용하는 것은 권장되지 않습니다. 왜냐하면, Spring MVC의 SseEmitter는 자체적으로 스트리밍을 관리하기 때문에 응답 헤더를 수정하기 어렵습니다.

요약

Spring 필터를 사용하여 X-Accel-Buffering: no 헤더를 추가하는 것이 가장 깔끔하고 일관된 방법입니다. 이렇게 하면 특정 엔드포인트에 대해 Nginx 버퍼링을 비활성화할 수 있으며, SseEmitter를 사용하는 동안에도 적용됩니다.

 

 

그래서 나는 3번째 방법으로 해결하였다.. 후... 

 

 

 

이렇게 잘 해결 됐으니 SSE 가 무엇인지 알아보도록 해보자 !!

 

 

Spring Boot에서 **Server-Sent Events (SSE)**는 서버가 클라이언트에 실시간으로 데이터를 전송할 수 있는 기술이야. 주로 웹 애플리케이션에서 실시간 알림이나 데이터 업데이트를 구현할 때 사용돼. 여기서 SSE에 대해 자세히 설명해줄게.

1. Server-Sent Events (SSE)란?

  • 정의: SSE는 서버가 클라이언트에게 HTTP를 통해 지속적으로 데이터를 푸시하는 방식이야. 클라이언트는 서버로부터 연속적으로 업데이트를 받을 수 있어.
  • 특징:
    • 단방향 통신: 클라이언트는 서버로 요청을 보내고, 서버는 클라이언트에게 데이터를 푸시해. 클라이언트는 서버에 데이터를 보내지 않아.
    • 연결 유지: 클라이언트와 서버 간의 연결이 지속적으로 유지되며, 서버는 데이터가 있을 때마다 클라이언트로 전송해.
    • 텍스트 기반: SSE는 주로 텍스트 형식(JSON, XML 등)으로 데이터를 전송해.

2. SSE의 장점

  • 실시간 데이터 업데이트: 서버가 데이터를 변경할 때마다 클라이언트에 즉시 업데이트할 수 있어.
  • 효율성: 웹소켓보다 구현이 간단하고, HTTP 기반으로 동작하므로 복잡한 설정이 필요 없어.
  • 자동 재연결: 연결이 끊어지면 클라이언트가 자동으로 재연결을 시도해.

3. Spring Boot에서 SSE 사용법

Spring Boot에서 SSE를 구현하기 위해서는 SseEmitter 클래스를 사용해. 다음은 기본적인 사용 예시야.

예제 코드

 

1. Controller 생성:

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.Executors;

@RestController
public class SseController {

    @GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamData() {
        SseEmitter emitter = new SseEmitter();
        
        // 비동기 처리
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    // 서버에서 클라이언트로 데이터 전송
                    emitter.send("데이터: " + i);
                    Thread.sleep(1000); // 1초 대기
                }
                emitter.complete(); // 모든 데이터 전송 후 완료
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e); // 에러 발생 시 처리
            }
        });
        
        return emitter; // SseEmitter 반환
    }
}

 

2. 클라이언트 측에서 SSE 구독: 클라이언트에서는 JavaScript의 EventSource API를 사용해 SSE를 구독할 수 있어.    

 

const eventSource = new EventSource('/sse');

eventSource.onmessage = function(event) {
    console.log('서버에서 받은 데이터:', event.data);
};

eventSource.onerror = function(event) {
    console.error('SSE 오류 발생:', event);
};
 

4. 주의사항

  • 브라우저 지원: 대부분의 최신 브라우저는 SSE를 지원하지만, IE와 같은 일부 구형 브라우저에서는 지원하지 않으므로 확인이 필요해.
  • CORS 처리: 다른 도메인에서 SSE를 사용할 경우 CORS 설정이 필요할 수 있어.
  • 네트워크 안정성: 연결이 끊어질 수 있으므로 클라이언트에서 자동 재연결 로직을 구현하는 것이 좋겠어.

 

5. SSE의 고급 기술

a. SseEmitter의 비동기 처리

SseEmitter를 사용하면 비동기적으로 이벤트를 전송할 수 있어. 이때, 서버에서 클라이언트로 데이터를 전송하는 동안 다른 작업이 블로킹되지 않도록 비동기 스레드를 활용해.

  • 스레드 풀 사용: Java의 Executors를 사용하여 스레드 풀을 만들고, 이를 통해 여러 클라이언트에 동시에 데이터를 전송할 수 있어.
Executors.newCachedThreadPool().execute(() -> {
    // 데이터 전송 로직
});
 

b. 에러 및 완료 처리

SSE를 사용할 때 에러나 완료 상태를 클라이언트에 알릴 수 있어. SseEmitter의 complete() 메서드를 호출하여 스트림이 끝났음을 알리고, completeWithError(Throwable) 메서드로 에러를 전달할 수 있어.

 

emitter.complete(); // 정상 종료
emitter.completeWithError(new RuntimeException("서버 오류")); // 에러 발생 시

 

c. 대량 데이터 전송

대량의 데이터를 SSE로 전송할 경우, 클라이언트가 데이터를 처리할 수 있는 속도를 고려해야 해. 이를 위해 백프레셔를 적용하거나 클라이언트 측에서 데이터 처리를 최적화하는 것이 중요해.

 

 

6. SSE와 웹소켓의 차이

  • 단방향 vs. 양방향: SSE는 서버에서 클라이언트로만 데이터가 흐르지만, 웹소켓은 클라이언트와 서버 간의 양방향 통신을 지원해.
  • 프로토콜: SSE는 HTTP 프로토콜을 사용하고, 웹소켓은 ws:// 또는 wss:// 프로토콜을 사용해.
  • 목적: SSE는 주로 실시간 데이터 업데이트에 적합하고, 웹소켓은 대화형 애플리케이션(예: 채팅, 게임)에서 주로 사용돼.

 

 

7. SSE의 사용 사례

a. 실시간 알림 시스템

예를 들어, 사용자가 친구의 메시지를 받거나 특정 이벤트가 발생했을 때 알림을 받을 수 있도록 SSE를 사용할 수 있어.

b. 대시보드

실시간 데이터 대시보드에서 서버의 상태, 사용자 활동, 통계 등을 실시간으로 업데이트하는 데 유용해.

c. IoT 애플리케이션

IoT 장치에서 센서 데이터를 실시간으로 서버에 전송하고, 클라이언트에서 이를 표시할 때 SSE를 사용할 수 있어.

d. 주식 거래 시스템

주식 가격 변동을 실시간으로 클라이언트에 푸시하여 사용자에게 즉각적인 정보를 제공하는 데 적합해.

 

8. SSE 구현 시 고려사항

a. 네트워크 안정성

SSE는 연결이 끊어질 수 있으므로 클라이언트 측에서 자동 재연결 로직을 구현해야 해. EventSource는 기본적으로 연결이 끊어지면 자동으로 재연결을 시도하지만, 서버 측에서도 적절한 처리 로직을 구현하는 것이 좋겠어.

b. CORS 정책

클라이언트와 서버가 다른 도메인에 있는 경우 CORS 정책을 설정해야 해. Spring Boot에서 @CrossOrigin 어노테이션을 사용하여 CORS를 허용할 수 있어.

c. 서버 성능

SSE는 지속적인 연결을 유지하므로, 많은 클라이언트가 동시에 연결될 경우 서버에 부담이 될 수 있어. 이를 해결하기 위해 서버 스케일링이나 부하 분산 기술을 사용할 수 있어.

 

 

9. SSE와 다른 기술의 통합

SSE는 다른 기술과 통합하여 사용하기도 해. 예를 들어, Spring Security와 함께 인증 및 권한 관리를 적용하거나, Spring Cloud를 통해 마이크로서비스 아키텍처에서 사용될 수 있어.

결론

SSE는 Spring Boot를 사용하여 실시간 데이터를 효율적으로 전송하는 데 매우 유용한 기술이야. 특히 실시간 업데이트가 필요한 웹 애플리케이션에서 큰 장점을 제공해. 위에서 설명한 다양한 기술과 고려사항을 참고하여 구현하면 더욱 효과적인 SSE 솔루션을 만들 수 있을 거야! 

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."