Spring Boot/MSA

Spring Boot MSA 게이트웨이 필터

최-코드 2024. 4. 27. 12:03

스프링 클라우드 게이트웨이에서 글로벌 필터는 모든 라우팅에 대해서 적용되는 필터이다. 필터만 구현하면 특별한 설정 없이 적용된다. 클리언트의 요청은 필터 -> 마이크로서비스 -> 필터 형태로 이동되면 같은 필터라도 마이크로서비스를 접근하기 이전이면 pre, 이후면 post라고 명명한다.각각의 필터는 Order 값을 가질 수 있는데, pre필터의 경우 Order 값이 작을 수록 빠르게(다른 필터보다 먼저) 동작하며, post 필터의 경우는 정반대로 동작한다.

 

@Component
public class G1Filter implements GlobalFilter, Ordered {
    @Override
            //Mono는 Spring Webfulx 기능으로 Non-blocking 방식의 서버를 지원할 때 단일 값 전달 활용 때 사용
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //pre 부분             //SevletHttpRequest-Response 가지고있음, netty엔진에서는 Servlet대신 Server가 붙어줘야 함
        System.out.println("pre global filter order -1");
        return chain.filter(exchange)
                .then(Mono.fromRunnable(() -> {
                    //post 부분
                    System.out.println("post global filter order -1");
                }));
    }
    @Override
    public int getOrder() {
        //순번 값 설정
        return -1;
    }
}

 

스프링 클라우드 게이트웨이에서 지역 필터는 특정 마이크로서비스 라우팅에 대해서만 동작을 진행하는 필터이다. 글로벌 필터와 마찬가지로 동일 필터에 대해 pre와 post가 있고, 모두 거치게 된다. 또한 Order를 통해 순서를 정해줄 수 있다.

 

@Component
public class L1Filter extends AbstractGatewayFilterFactory<L1Filter.Config> {
    
    //config 클래스에 대한 정보 넘겨야 함
    public L1Filter() {
        super(Config.class);
    }


    @Override               //AbstractGatewayFilterFactory에 넘겨준 것과 같은 타입이어야 됨
    public GatewayFilter apply(Config config) {
                //GatewayFilter의 Filter 메소드 구현 람다식
        return (exchange, chain) -> {
            //pre 부분 
            if (config.isPre()) {
                System.out.println("pre local filter 1");
            }
            return chain.filter(exchange)
                    .then(Mono.fromRunnable(() -> {
                        //post 부분
                        if (config.isPost()) {

                            System.out.println("post local filter 1");
                        }
                    }));
        };
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    public static class Config {
        private boolean pre;
        private boolean post;
    }
}

 

아래는 라우터에 필터를 추가하는 메소드 설정이다.

.route(p -> p.path("/currency-conversion/**")
                        .filters(f->f.filter(l1Filter.apply(new L1Filter.Config(true, true))))

 

추가적으로 filter에 대한 로직은 org.springframework.cloud.gateway.config.GatewayAutoConfiguration에서 Bean으로 등록된 FilteringWebHandler의 객체가 생성되면서 아래의 loadFilters 메서드를 통해 GlobalFilter들을 GatewayFilter들로 변경해서 멤버 변수로 갖고 handle 함수에 의해 globalfilter들이 실행된다.

private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
    return (List)filters.stream().map((filter) -> {
        GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
        if (filter instanceof Ordered) {
            int order = ((Ordered)filter).getOrder();
            return new OrderedGatewayFilter(gatewayFilter, order);
        } else {
            Order orderx = (Order)AnnotationUtils.findAnnotation(filter.getClass(), Order.class);
            return (GatewayFilter)(orderx != null ? new OrderedGatewayFilter(gatewayFilter, orderx.value()) : gatewayFilter);
        }
    }).collect(Collectors.toList());
}

 

아래는 handle 함수에서 내부적으로 실행하는 함수이다.

public Mono<Void> filter(ServerWebExchange exchange) {
    return Mono.defer(() -> {
        if (this.index < this.filters.size()) {
            GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);
            DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this, this.index + 1);
            return filter.filter(exchange, chain);
        } else {
            return Mono.empty();
        }
    });
}

이 함수는 다음 필터로 동작은 넘기는 역할이다. 이 때 filter.filter()에서 filter()함수는 GatewayFilterAdapter 클래스의 filter 함수가 실행됨으로써 위에 우리가 오버라이딩한 함수가 실행된다. 우리가 오버라이팅한 apply 함수에서도 이 함수를 호출하게 된다. 가장 처음으로 apply가 실행될 때는 GatewayFilterFactory<C> 클래스의 아래와 같은 함수를 실행하는 것 같다.

default GatewayFilter apply(String routeId, Consumer<C> consumer) {
    C config = this.newConfig();
    consumer.accept(config);
    return this.apply(routeId, config);
}

아마 지역 필터는 라우터의 id를 참고해서 내부적으로 실행하는 것 같다.

Mono.then(Mono.fromRunnable)은 해당 Mono가 완료되면 Runnable 또는 Supplier를 실행하여 새로운 Mono를 생성한다. 그 새로운 Mono는 Runnable 또는 Supplier의 실행 결과를 발행한다.