
기본적인 Filter를 사용하여 요청에 대해 text/plain 응답을 출력하는 예입니다.
하지만 설명에서 언급된 내용에 대해 좀 더 명확히 하고, 실질적인 예제로 개선할 수 있습니다.
또한, 예제에서 발생하는 문제를 해결하기 위해 SSE (Server-Sent Events)를 활용한 방식으로도 예제를 보완해보겠습니다.
SSE(Server-Sent Events) 사용 시 처리: SSE는 클라이언트에 실시간으로 데이터를 전송하는 방식으로, text/event-stream으로 Content-Type을 설정하면 버퍼링을 비활성화하고 데이터를 즉시 전송합니다.
버퍼링 문제: 기본적으로 HttpServletResponse는 버퍼링을 사용하며, 데이터를 flush() 호출 후에도 서버에서 일정한 크기까지 버퍼링한 뒤 전송됩니다.
이로 인해 출력 지연이 발생합니다.
응답 헤더와 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);
}
}
}
}
Content-Type 설정: response.setContentType("text/event-stream; charset=UTF-8"); 이 부분을 사용하여 응답 데이터를 SSE(Server-Sent Events) 형식으로 설정합니다.
이 설정은 클라이언트가 실시간으로 데이터를 스트리밍 받을 수 있게 합니다.
응답 헤더 설정: response.setHeader("Cache-Control", "no-cache");와 response.setHeader("Connection", "keep-alive");는 브라우저가 데이터를 캐시하지 않도록 하고, 연결을 유지하도록 합니다.
이는 SSE에서 필요한 설정입니다.
데이터 전송: out.write("data: 응답 데이터 " + i + "\n\n"); 형식으로 SSE 규격에 맞게 데이터를 전송합니다.
data:는 각 메시지의 시작을 나타내며, \n\n은 메시지의 끝을 나타냅니다. 이후 out.flush()를 호출하여 데이터를 즉시 전송합니다.
Thread.sleep(1000): 1초 간격으로 데이터를 출력하고, 클라이언트는 이를 실시간으로 처리할 수 있습니다.
1. 서버 실행 후 클라이언트 요청: 서버를 실행하고 브라우저나 curl을 사용하여 /stream/* URL로 요청을 보냅니다.
curl -N http://localhost:8080/stream/hello
이때, 클라이언트는 실시간으로 1초마다 응답 데이터를 받을 수 있습니다.
예외 처리: onError()와 같은 예외 처리 메커니즘을 추가할 수 있습니다.
예를 들어, 네트워크 문제나 클라이언트 연결이 끊어졌을 때 적절히 처리할 수 있습니다.
백프레셔 관리: 만약 클라이언트가 데이터를 받는 속도가 서버와 맞지 않는 경우, 백프레셔를 고려하여 전송 속도를 제어하는 기능을 추가할 수 있습니다.
이 예제에서는 SSE를 사용하여 Content-Type을 text/event-stream으로 설정하고, 데이터를 실시간으로 전송하는 방식을 구현했습니다.
flush()를 호출하여 데이터를 즉시 클라이언트로 전송하며, 서버와 클라이언트 간의 연결을 유지할 수 있습니다.