Spring Framework

Spring Webflux - Filter

흥부가귀막혀 2022. 3. 14. 12:53

Filter

  • Spring Webflux 에서는 MVC 의 Interceptor 와 비슷한 역할을 하는 HandlerFilterFunction 과 Servlet Filter 와 비슷한 역할을 하는 WebFilter Interface 를 제공하고 있다.

HandlerFilterFunction

  • 핸들러 펑션에 필터를 적용할 땐 RouterFunction Builder 를 사용하면 되는데, 다음과 같은 함수가 있다.
    • before : Handler 함수 수행 전에 동작하는 함수를 등록하며 인자로 Function<ServerRequest, ServerRequest> 를 받는다.
    • after : Handler 함수가 수행 완료되면 동작하는 함수를 등록하며 인자로 BiFunction<ServerRequest, ServerResponse, ServerResponse> 를 받는다.
    • filter : 인자로 HandlerFilterFunction<ServerRequest, ServerResponse> 를 받으며, 필터를 어떻게 수행하고 다음 handler function 을 어떻게 호출할지의 일련의 동작을 모두 구현할 수 있다.
  • 필터는 빌더의 모든 라우터 펑션에 적용된다. 이 말은 필터를 감싸져 있는 라우터에서 정의하면, 상위 레벨에는 적용되지 않는다는 뜻이다.
RouterFunction<ServerResponse> route = RouterFunctions.route()
    .path("/person", b1 -> b1
        .nest(RequestPredicates.accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople)
            .before(request -> ServerRequest.from(request) // (1) 
                .header("X-RequestHeader", "Value")
                .build()))
        .POST("/person", handler::createPerson))
    .after((request, response) -> logResponse(response)) // (2) 
    .build();

(1) X-RequestHeader 를 추가하는 filter 는 두 GET Router 에만 적용되며 Router 실행 전에 수행된다.
(2) response 를 logging 하는 filter 는 모든 Router 에 적용되며 Router 실행 후 수행된다.

  • filter 메소드는 HandlerFilterFunction을 인자로 받는다. 이 인터페이스는 ServerRequest, HandlerFunction을 받아 ServerResponse를 리턴하는 함수다. 두번째 인자로 받는 HandlerFunction 파라미터는 체인에 있는 다음 HandlerFunction 이다. 보통 이 HandlerFunction 은 라우팅할 핸들러지만, HandlerFilterFunction 이 여러 개가 등록되어있다면 HandlerFilterFunction 일 수도 있다.
  • path를 보고 요청을 허가할지 말지 결정하는 SecurityManager가 있다고 가정하고, filter 함수를 통해 보안 필터를 라우터에 적용해 보면 다음과 같다.
SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = RouterFunctions.route()
    .path("/person", b1 -> b1
        .nest(RequestPredicates.accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET("", handler::listPeople))
        .POST("/person", handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();

위 예제를 보면 next.handle(ServerRequest) 호출은 선택이라는 점을 알 수 있다. 여기선 접근을 허가할 때만 실행했다.
⚠️ filter 를 구현하다보면 Request 객체에 attribute 나 header 등의 값을 주입하거나 변경해야할 때가 있는데, 그럴 때 ServerRequest.Builder 나 혹은 Builder 를 사용하는 API(from) 로 ServerRequest 를 새로 생성하여 변경할 경우 다음 HandlerFunction 에서 request body 를 가져올 수 없다. 반드시 주입받은 ServerRequest 를 다음 HandlerFunction 에 넘겨주도록 구현해야 한다.

WebFilter

  • WebFilter 를 구현하여 Bean 으로 등록(혹은 @Component Annotation 을 등록)하면 WebFilter 를 사용할 수 있다.
  • WebFilter 는 여러개가 등록 가능하며 순서를 맞추고자 한다면 @Order 를 사용한다.
  • 아래는 Spring Webflux 에서 HttpHandler, ExceptionHandler, WebFlilter, DispatcherHandler 간의 동작 과정이다. FilteringWebHandler 부터 DefaultWebFilterChain 까지가 WebFilter 의 동작 부분이라 볼 수 있으며 Bean(혹은 @Component Annotation)으로 등록한 WebFliter 는 DefaultWebFilterChain 안에서 동작하게 된다.

⚠️ HandlerFilterFunction 은 위 동작에 포함되지 않으며 DispatcherHandler 수행시 동작하게 된다.

  • 인증, ACL, Access Logging 과 같이 요청시 우선적으로 실행해야 하거나 HandlerFilterFunction 에 영향을 받지 않도록 하려면 WebFilter 를 구현하여 사용하는게 좋다.
  • Spring Webflux 에서 제공하는 대표적인 WebFilter 로 CorsFilter 가 있다.