SSE(Server-Sent Events)로 실시간 데이터 스트리밍 구현하기

궁금하면 500원·2024년 11월 6일

미생의 스프링

목록 보기
21/50


기본적인 Filter를 사용하여 요청에 대해 text/plain 응답을 출력하는 예입니다.

하지만 설명에서 언급된 내용에 대해 좀 더 명확히 하고, 실질적인 예제로 개선할 수 있습니다.

또한, 예제에서 발생하는 문제를 해결하기 위해 SSE (Server-Sent Events)를 활용한 방식으로도 예제를 보완해보겠습니다.

  1. SSE(Server-Sent Events) 사용 시 처리: SSE는 클라이언트에 실시간으로 데이터를 전송하는 방식으로, text/event-stream으로 Content-Type을 설정하면 버퍼링을 비활성화하고 데이터를 즉시 전송합니다.

  2. 버퍼링 문제: 기본적으로 HttpServletResponse는 버퍼링을 사용하며, 데이터를 flush() 호출 후에도 서버에서 일정한 크기까지 버퍼링한 뒤 전송됩니다.
    이로 인해 출력 지연이 발생합니다.

  3. 응답 헤더와 Content-Type 설정: text/event-stream을 사용하면 버퍼링이 비활성화되고, 클라이언트가 데이터를 수신할 때마다 즉시 처리할 수 있습니다.

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

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Configuration
public class MyFilterConfig {

    // 필터를 빈으로 등록하여 애플리케이션에 적용
    @Bean
    public FilterRegistrationBean<MyFilter> addFilter1() {
        FilterRegistrationBean<MyFilter> filterRegistrationBean = new FilterRegistrationBean<>(new MyFilter());
        filterRegistrationBean.addUrlPatterns("/stream/*"); // 특정 URL 패턴에 필터 적용
        return filterRegistrationBean;
    }

}

// 필터 구현 클래스
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // Content-Type을 text/event-stream으로 설정하여 스트리밍 방식으로 응답
        response.setContentType("text/event-stream; charset=UTF-8");

        // 응답의 버퍼를 비활성화하여 데이터가 즉시 전송되도록 설정
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        PrintWriter out = response.getWriter();

        // 5초 동안 데이터를 5번에 걸쳐 출력
        for (int i = 0; i < 5; i++) {
            // 응답에 데이터를 즉시 출력
            out.write("data: 응답 데이터 " + i + "\n\n");
            out.flush(); // 응답 버퍼를 강제로 플러시하여 데이터를 즉시 전송

            try {
                Thread.sleep(1000); // 1초마다 데이터 전송
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

코드에 대한 설명

  1. Content-Type 설정: response.setContentType("text/event-stream; charset=UTF-8"); 이 부분을 사용하여 응답 데이터를 SSE(Server-Sent Events) 형식으로 설정합니다.
    이 설정은 클라이언트가 실시간으로 데이터를 스트리밍 받을 수 있게 합니다.

  2. 응답 헤더 설정: response.setHeader("Cache-Control", "no-cache");와 response.setHeader("Connection", "keep-alive");는 브라우저가 데이터를 캐시하지 않도록 하고, 연결을 유지하도록 합니다.
    이는 SSE에서 필요한 설정입니다.

  3. 데이터 전송: out.write("data: 응답 데이터 " + i + "\n\n"); 형식으로 SSE 규격에 맞게 데이터를 전송합니다.
    data:는 각 메시지의 시작을 나타내며, \n\n은 메시지의 끝을 나타냅니다. 이후 out.flush()를 호출하여 데이터를 즉시 전송합니다.

  4. Thread.sleep(1000): 1초 간격으로 데이터를 출력하고, 클라이언트는 이를 실시간으로 처리할 수 있습니다.

테스트

1. 서버 실행 후 클라이언트 요청: 서버를 실행하고 브라우저나 curl을 사용하여 /stream/* URL로 요청을 보냅니다.

curl -N http://localhost:8080/stream/hello

이때, 클라이언트는 실시간으로 1초마다 응답 데이터를 받을 수 있습니다.

  • 예외 처리: onError()와 같은 예외 처리 메커니즘을 추가할 수 있습니다.
    예를 들어, 네트워크 문제나 클라이언트 연결이 끊어졌을 때 적절히 처리할 수 있습니다.

  • 백프레셔 관리: 만약 클라이언트가 데이터를 받는 속도가 서버와 맞지 않는 경우, 백프레셔를 고려하여 전송 속도를 제어하는 기능을 추가할 수 있습니다.

정리

이 예제에서는 SSE를 사용하여 Content-Typetext/event-stream으로 설정하고, 데이터를 실시간으로 전송하는 방식을 구현했습니다.
flush()를 호출하여 데이터를 즉시 클라이언트로 전송하며, 서버와 클라이언트 간의 연결을 유지할 수 있습니다.

profile
그냥 코딩할래요 재미있어요

0개의 댓글