Talk about GlobalFilter of spring cloud gateway.

  springcloud

Order

This article mainly studies GlobalFilter of spring cloud gateway.

GatewayAutoConfiguration

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java

@Configuration
@ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
@EnableConfigurationProperties
@AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
@AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
@ConditionalOnClass(DispatcherHandler.class)
public class GatewayAutoConfiguration {
    //......
    @Bean
    public FilteringWebHandler filteringWebHandler(List<GlobalFilter> globalFilters) {
        return new FilteringWebHandler(globalFilters);
    }

    @Bean
    public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
                                                                       RouteLocator routeLocator) {
        return new RoutePredicateHandlerMapping(webHandler, routeLocator);
    }
    //......
}
  • GlobalFilters (NettyWriteResponseFilter、ForwardPathFilter、RouteToRequestUrlFilter、LoadBalancerClientFilter、AdaptCachedBodyGlobalFilter、WebsocketRoutingFilter、NettyRoutingFilter、ForwardRoutingFilter) as a constructor parameter
  • On the other hand, according to FilteringWebHandler and routeloader, routeloader is created, where routeloader is CachingRouteLocator.

FilteringWebHandler

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/handler/FilteringWebHandler.java

/**
 * WebHandler that delegates to a chain of {@link GlobalFilter} instances and
 * {@link GatewayFilterFactory} instances then to the target {@link WebHandler}.
 *
 * @author Rossen Stoyanchev
 * @author Spencer Gibb
 * @since 0.1
 */
public class FilteringWebHandler implements WebHandler {
    protected static final Log logger = LogFactory.getLog(FilteringWebHandler.class);

    private final List<GatewayFilter> globalFilters;

    public FilteringWebHandler(List<GlobalFilter> globalFilters) {
        this.globalFilters = loadFilters(globalFilters);
    }

    private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
        return filters.stream()
                .map(filter -> {
                    GatewayFilterAdapter gatewayFilter = new GatewayFilterAdapter(filter);
                    if (filter instanceof Ordered) {
                        int order = ((Ordered) filter).getOrder();
                        return new OrderedGatewayFilter(gatewayFilter, order);
                    }
                    return gatewayFilter;
                }).collect(Collectors.toList());
    }

    /* TODO: relocate @EventListener(RefreshRoutesEvent.class)
    void handleRefresh() {
        this.combinedFiltersForRoute.clear();
    }*/

    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
        List<GatewayFilter> gatewayFilters = route.getFilters();

        List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
        combined.addAll(gatewayFilters);
        //TODO: needed or cached?
        AnnotationAwareOrderComparator.sort(combined);

        logger.debug("Sorted gatewayFilterFactories: "+ combined);

        return new DefaultGatewayFilterChain(combined).filter(exchange);
    }
    //......
}
  • The loadFilters method is called in the constructor to convert List<GlobalFilter > to List<GatewayFilter >
  • The subsequent handle method merges the gatewayFilters of the selected route with the converted gatewayFilters, and then reorders them.
  • After that, the merged gatewayFilters were used to create DefaultGatewayFilterChain, and the filters went down one by one.

GatewayFilterAdapter

    private static class GatewayFilterAdapter implements GatewayFilter {

        private final GlobalFilter delegate;

        public GatewayFilterAdapter(GlobalFilter delegate) {
            this.delegate = delegate;
        }

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            return this.delegate.filter(exchange, chain);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder("GatewayFilterAdapter{");
            sb.append("delegate=").append(delegate);
            sb.append('}');
            return sb.toString();
        }
    }

Here, GlobalFilter is adapted as GatewayFilter, and finally the filter method is called.

DefaultGatewayFilterChain

    private static class DefaultGatewayFilterChain implements GatewayFilterChain {

        private final int index;
        private final List<GatewayFilter> filters;

        public DefaultGatewayFilterChain(List<GatewayFilter> filters) {
            this.filters = filters;
            this.index = 0;
        }

        private DefaultGatewayFilterChain(DefaultGatewayFilterChain parent, int index) {
            this.filters = parent.getFilters();
            this.index = index;
        }

        public List<GatewayFilter> getFilters() {
            return filters;
        }

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

Here, the responsibility chain mode is used, in which the filter method is used to iterate through the execution one by one. the incoming chain contains the current index, which is used to control jumping out of the responsibility chain.

RoutePredicateHandlerMapping

spring-cloud-gateway-core-2.0.0.RC2-sources.jar! /org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java

public class RoutePredicateHandlerMapping extends AbstractHandlerMapping {

    private final FilteringWebHandler webHandler;
    private final RouteLocator routeLocator;

    public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator routeLocator) {
        this.webHandler = webHandler;
        this.routeLocator = routeLocator;

        setOrder(1);
    }

    @Override
    protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
        exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());

        return lookupRoute(exchange)
                // .log("route-predicate-handler-mapping", Level.FINER) //name this
                .flatMap((Function<Route, Mono<?>>) r -> {
                    exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
                    }

                    exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
                    return Mono.just(webHandler);
                }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
                    exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
                    if (logger.isTraceEnabled()) {
                        logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
                    }
                })));
    }

    @Override
    protected CorsConfiguration getCorsConfiguration(Object handler, ServerWebExchange exchange) {
        //TODO: support cors configuration via global properties and
        // properties on a route see gh-229
        // see RequestMappingHandlerMapping.initCorsConfiguration()
        // also see https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/cors/reactive/CorsWebFilterTests.java
        return super.getCorsConfiguration(handler, exchange);
    }
    //......
}

This RoutePredicateHandlerMapping mainly implements getHandlerInternal of the parent class

DispatcherHandler

spring-webflux-5.0.6.RELEASE-sources.jar! /org/springframework/web/reactive/DispatcherHandler.java

/**
 * Central dispatcher for HTTP request handlers/controllers. Dispatches to
 * registered handlers for processing a request, providing convenient mapping
 * facilities.
 *
 * <p>{@code DispatcherHandler} discovers the delegate components it needs from
 * Spring configuration. It detects the following in the application context:
 * <ul>
 * <li>{@link HandlerMapping} -- map requests to handler objects
 * <li>{@link HandlerAdapter} -- for using any handler interface
 * <li>{@link HandlerResultHandler} -- process handler return values
 * </ul>
 *
 * <p>{@code DispatcherHandler} is also designed to be a Spring bean itself and
 * implements {@link ApplicationContextAware} for access to the context it runs
 * in. If {@code DispatcherHandler} is declared with the bean name "webHandler"
 * it is discovered by {@link WebHttpHandlerBuilder#applicationContext} which
 * creates a processing chain together with {@code WebFilter},
 * {@code WebExceptionHandler} and others.
 *
 * <p>A {@code DispatcherHandler} bean declaration is included in
 * {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux}
 * configuration.
 *
 * @author Rossen Stoyanchev
 * @author Sebastien Deleuze
 * @author Juergen Hoeller
 * @since 5.0
 * @see WebHttpHandlerBuilder#applicationContext(ApplicationContext)
 */
public class DispatcherHandler implements WebHandler, ApplicationContextAware {

    @SuppressWarnings("ThrowableInstanceNeverThrown")
    private static final Exception HANDLER_NOT_FOUND_EXCEPTION =
            new ResponseStatusException(HttpStatus.NOT_FOUND, "No matching handler");


    private static final Log logger = LogFactory.getLog(DispatcherHandler.class);

    @Nullable
    private List<HandlerMapping> handlerMappings;

    @Nullable
    private List<HandlerAdapter> handlerAdapters;

    @Nullable
    private List<HandlerResultHandler> resultHandlers;

    //......

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        initStrategies(applicationContext);
    }


    protected void initStrategies(ApplicationContext context) {
        Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerMapping.class, true, false);

        ArrayList<HandlerMapping> mappings = new ArrayList<>(mappingBeans.values());
        AnnotationAwareOrderComparator.sort(mappings);
        this.handlerMappings = Collections.unmodifiableList(mappings);

        Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerAdapter.class, true, false);

        this.handlerAdapters = new ArrayList<>(adapterBeans.values());
        AnnotationAwareOrderComparator.sort(this.handlerAdapters);

        Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
                context, HandlerResultHandler.class, true, false);

        this.resultHandlers = new ArrayList<>(beans.values());
        AnnotationAwareOrderComparator.sort(this.resultHandlers);
    }


    @Override
    public Mono<Void> handle(ServerWebExchange exchange) {
        if (logger.isDebugEnabled()) {
            ServerHttpRequest request = exchange.getRequest();
            logger.debug("Processing " + request.getMethodValue() + " request for [" + request.getURI() + "]");
        }
        if (this.handlerMappings == null) {
            return Mono.error(HANDLER_NOT_FOUND_EXCEPTION);
        }
        return Flux.fromIterable(this.handlerMappings)
                .concatMap(mapping -> mapping.getHandler(exchange))
                .next()
                .switchIfEmpty(Mono.error(HANDLER_NOT_FOUND_EXCEPTION))
                .flatMap(handler -> invokeHandler(exchange, handler))
                .flatMap(result -> handleResult(exchange, result));
    }
    //......
}
  • The initStrategies method was called when setApplicationContext was set here.
  • Beansoftypeincludingannocessors was used to obtain HandlerMapping registered in the container.
  • RoutePredicateHandlerMapping is registered in the container and will be retrieved here
  • HandlerMapping is used to obtain handlers based on exchange.
  • This uses concatMap. if it returns Mono.empty (), it will not be concatenated, and then next takes the first, that is, the first one sorted by handlerMappings

handlerMappings

According to the priority, there are the following:

  • WebFluxEndpointHandlerMapping(order=-100)
  • ControllerEndpointHandlerMapping(order=-100)
  • RouterFunctionMapping(order=-1)
  • RequestMappingHandlerMapping(order=0)
  • RoutePredicateHandlerMapping(order=1)
  • SimpleUrlHandlerMapping(Ordered.LOWEST_PRECEDENCE)

Summary

Spring cloud gateway’s GlobalFilter is adapted as GatewayFilter in FilteringWebHandler, and then merged with route-level gatewayFilters to act on the current route. RoutePredicatehandlermapping will be recognized by DispatcherHandler, sorted according to order priority, obtaining the Handler of the exchange according to Mapping, finding the first that is not Mono.empty (), and then invokeHandler and handleResult.

Therefore, it can be understood that GlobalFilter is the global GatewayFilter, which acts on all route. The GatewayFilter is route-level.

doc