WebFlux 박치기 - 1

yeolyeol·2023년 6월 25일
1

til

목록 보기
7/8

Spring Webflux

지금까지 웬만한 개발은 Spring MVC로 작성한 탓에 WebFlux를 공부하면서 새로 태어난 기분이었다. 정말 간단한 요청/응답 방식으로 개발을 했기 때문에 이번 공부에서 많은 것을 얻어 갈 수 있었다.


Flux

WebFlux로 코딩하기 전에 Flux가 어떤 흐름으로 작동되는지 알아보자.

Code

현재 코드는 실제로 개발할 때 사용되진 않고 흐름을 알기 위해 작성된 코드이다.
절대, 실제 프로젝트에서 개발 용도로 사용하면 안된다.

제일 먼저 Filter를 구성해보자.

  • Servlet.Filter : Client로 부터 Server로 요청이 들어오기 전에 서블릿을 거쳐서 필터링 하는 것
// MyFilter.java
public class MyFilter implements Filter { // 1

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("필터 실행됨");

        HttpServletResponse servletResponse = (HttpServletResponse) response; // 2
        servletResponse.setContentType("text/event-stream; charset=utf-8");
        PrintWriter out = servletResponse.getWriter(); // 3
        for(int i = 0; i < 5; i++) { // 4
            out.print("응답 " + i + "\n"); 
            out.flush();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

번호 주석 순으로 설명을 하자면,

  1. Servlet의 Filter를 상속받고 doFiler 메서드를 오버라이딩한다.
  2. ServletResponse 객체인 responseHttpServletResponse로 다운캐스팅한다.
    • HttpServletResponse는 ServletResponse 인터페이스를 상속 받아, 웹 애플리케이션을 개발하면서 응답 관련 작업을 수행하기 위한 HTTP 프로토콜 통신 기반의 응답 관련 메소드들도 확장하여 포함하기 때문이다.
  3. 브라우저에 출력하기 위한 객체 생성한다.
  4. 브라우저에 1초 단위로 순차적으로 출력한다.

다음으로, Filter를 설정해보자.

// MyFilterConfig.java
@Configuration
public class MyFilterConfig{

    @Bean
    public FilterRegistrationBean<Filter> addFilter(){
        System.out.println("필터 등록됨.");
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter());
        bean.addUrlPatterns("/sse");
        return bean;
    }
}

프로젝트 실행시, bean을 하나 생성한다.
bean에 대한 내용은 Filter 객체를 담고 있는 FilterRegistrationBean 을 반환해주는데, 전에 만들어줬던 MyFilter로 생성한다.
그리고, /sse라는 url패턴으로 등록하고 해당 url로 요청시 Filter 에 걸리게 설정한다.

Result

하나의 브라우저에 localhost:8080/sse를 띄우면, 다음과 같은 결과를 볼 수 있다.
sse 결과

이처럼, loop가 끝날 때까지 브라우저에 출력해준다.
한가지 특징이 있다면, 뒤로가기 버튼 위에 계속 빙글빙글 돌고 있다는 것이다.~저게 뭔지 모르겠다~
즉, 필터가 작동 중이라는 것이고, loop가 끝나면 필터를 종료시켜 나머지 작업을 진행한다.

당연한 것이라고 생각할 수 있지만, 우리는 이것을 통해 이벤트가 발생하면 즉각적으로 반응하게 구현할 수 있다.


Event

어떤 이벤트를 발생할지 생각해보자.
가장 단순한 것은 어떤 데이터가 들어왔거나, 바뀌었거나 등등을 생각해볼 수 있다.
그럼, String 리스트와 boolean 변수를 이용하여 boolean 값이 true일 때 이벤트를 발생시켜보자.

Code - EventNotify 객체

이벤트를 발생시키기 위한 객체이다. 코드는 아래와 같다.

// EventNotify.java
@Component
public class EventNotify {
    private final List<String> events = new ArrayList<>(); // 1
    private boolean change = false; // 2

    public void add(String data) { // 3
        events.add(data);
        change = true;
    }

    public List<String> getEvents() {
        return events;
    }

    public boolean isChange() {
        return change;
    }

    public void setChange(boolean change) {
        this.change = change;
    }
}
  1. 문자열을 받는 리스트를 생성한다. 브라우저에 출력하기 위한 용도이다.
  2. 이벤트가 발생한 것을 나타내기 위한 변수다. 변수명이나 타입은 본인이 정해도 된다.
  3. 리스트에 값을 넣고 상태를 true 로 바꿔 변화했다는 것을 알려준다.

그리고, EventNotify객체를 받아야 하므로 기존 MyFilter를 수정해주자.

public class MyFilter implements Filter {

    private EventNotify eventNotify; // 1

    public MyFilter(EventNotify eventNotify) {
        this.eventNotify = eventNotify;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /*
        * 중략
        */

        while(true){ // 2
            try {
                if (eventNotify.isChange()){ // 3
                    int last = eventNotify.getEvents().size() - 1;
                    out.print("응답 " + eventNotify.getEvents().get(last) + "\n");
                    out.flush();
                    eventNotify.setChange(false);
                }
                Thread.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  1. EventNotify 필드와 생성자를 만들어준다.
  2. while(true) 로 계속 필터에 머물도록 실행해준다. 이벤트를 통해 필터에서 바로 처리하기 위함이다.
  3. eventNotify 필드가 바뀌었다면, 데이터가 들어있는 리스트의 마지막 요소를 브라우저로 뿌린다. 그 후, changefalse로 바꾼다.

다음으로, EventNotify에 데이터를 넣어줄 필터가 필요하다.
이름은 단순하게 MyFilter2라고 하자.

// MyFilter2.java
public class MyFilter2 implements Filter {

    private final EventNotify eventNotify;

    public MyFilter2(EventNotify eventNotify) {
        this.eventNotify = eventNotify;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("필터2 실행됨");

        // /add를 통해 데이터를 발생시켜서 /sse에 반영
        eventNotify.add("새로운 데이터");
    }
}

똑같이, EventNotify 필드와 생성자를 생성해준다.
그리고 MyFilter2가 실행하면 eventNotify새로운 데이터라는 데이터를 넣는 로직을 작성해보자.


이를 정상적으로 실행시키기 위해, `MyFilterConfig`에서 새로운 bean을 등록하자.
// MyFilterConfig.java
@Configuration
@RequiredArgsConstructor
public class MyFilterConfig{

    private final EventNotify eventNotify;

    @Bean
    public FilterRegistrationBean<Filter> addFilter(){
        System.out.println("필터 등록됨.");
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter(eventNotify)); // 1
        bean.addUrlPatterns("/sse");
        return bean;
    }

    @Bean
    public FilterRegistrationBean<Filter> addFilter2(){ // 2
        System.out.println("필터 등록됨.");
        FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>(new MyFilter2(eventNotify));
        bean.addUrlPatterns("/add");
        return bean;
    }
}
  1. 수정한 MyFilter 생성자로 수정한다.
  2. MyFilter2 에 해당하는 bean을 등록한다. 이때, MyFilter2/add url 패턴에 필터가 걸리게 설정한다.

모든 준비는 완료되었다.
바로 실행해보자.

Result

하나의 브라우저에 즉각적인 응답을 확인하기 위해 localhost:8080/sse를 띄우고, 데이터를 삽입하는 (이벤트를 발생시키는) URL localhost:8080/add로 요청을 보내면 아래와 같은 결과를 확인 할 수 있다.
sse_add


느낀점

Spring MVC에서도 Flux와 비슷한 흐름을 경험할 수 있어서 WebFlux를 공부하는데 큰 도움이 됐다.
비록, 지금 올린 코드를 그대로 개발에 사용할 순 없지만 이러한 흐름을 알고 공부하는 것과 모르고 공부하는 것에는 큰 차이에 있을 것이라고 생각한다.
백엔드 개발자의 주요 역할은 클라이언트가 요구하는 데이터를 빠르고 정확하게 제공하는 것이라고 생각한다.
사람이 많이 몰리는 대규모 트래픽에도 문제없이 작동돼야 하기 때문에 논블로킹 + 비동기로 처리하는 WebFlux를 알아둬야 한다고 생각한다.
다음 포스트에서 WebFlux에서 제공하는 인터페이스를 다뤄보겠다.


References

메타코딩의 Springboot-WebFlux

profile
한 걸음씩 꾸준히

0개의 댓글