有没有办法在路由之前应用过滤器或代码(Spring Cloud Gateway)

Posted

技术标签:

【中文标题】有没有办法在路由之前应用过滤器或代码(Spring Cloud Gateway)【英文标题】:Is there a way to apply a filter or code BEFORE routing (Spring Cloud Gateway) 【发布时间】:2021-01-23 21:55:55 【问题描述】:

我正在编写一个 API 网关,它必须根据 MA​​C 地址路由请求。端点示例:

/api/v2/device/AABBCCDDEEFF
/api/v2/device/AABBCCDDEEFF/metadata
/api/v2/device/search?deviceId=AABBCCDDEEFF

我编写了一个Custom Predicate Factory,它提取 MAC 地址,执行必要的逻辑来确定 MAC 地址应该路由到哪个 URL,然后将该信息存储在 ServerWebExchange 属性中。

public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> 
    // Fields/Constructors Omitted

    private static final String IP_ATTRIBUTE = "assignedIp";
    private static final String MAC_ATTRIBUTE = "mac";

    @Override
    public Predicate<ServerWebExchange> apply(Config config) 
        return (ServerWebExchange exchange) -> 
            String mac = exchange.getAttributes().get(MAC_ATTRIBUTE);
            if(mac == null)
                mac = extractMacAddress(exchange);
            

            if(!exchange.getAttributes().contains(IP_ATTRIBUTE))
                exchange.getAttributes().put(IP_ATTRIBUTE, findAssignedIp(mac);
            

            return config.getRouteIp().equals(exchange.getAttribute(IP_ATTRIBUTE));
        );
    
    // Config Class & utility methods omitted

注意:为简洁起见,此实现已大大简化

通过这个实现,我能够保证只提取一次 MAC,并且确定请求属于哪个 URL 的逻辑只执行一次。对谓词工厂的第一次调用将提取并设置有关 ServerWebExchange 属性的信息,对谓词工厂的任何进一步调用都将检测这些属性并使用它们来确定它们是否匹配。

这可行,但不是特别整洁。如果我能以某种方式在每个进入网关的请求上设置交换属性在应用程序尝试匹配路由之前,这将变得更加容易和简单。那么过滤器可以是一个简单的谓词,用于检查交换属性是否相等。

我已经多次阅读文档,但似乎没有任何可能。过滤器始终限定在特定路由范围内,并且仅在路由匹配后运行。有可能使第一个路由成为另一个执行必要代码、设置预期属性并始终返回 false 的谓词,但我可以保证这个谓词 always run first强>?似乎应该支持这种用例,但我一辈子都找不到一种看起来不像黑客的方法。有什么想法吗?

【问题讨论】:

【参考方案1】:

使用WebFilter 而不是GatewayFilterGlobalFilter。它们仅在谓词链之后应用。而WebFilter 充当拦截器。

@Component
public class CustomRoutePredicateFactory implements WebFilter, Ordered 

    private static final String IP_ATTRIBUTE = "assignedIp";
    private static final String MAC_ATTRIBUTE = "mac";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) 
        String mac = (String) exchange.getAttributes().computeIfAbsent(MAC_ATTRIBUTE, key -> extractMacAddress(exchange));
        exchange.getAttributes().computeIfAbsent(IP_ATTRIBUTE, key -> findAssignedIp(mac));
        return chain.filter(exchange);
    

    @Override
    public int getOrder() 
        return Ordered.HIGHEST_PRECEDENCE;
    
    

【讨论】:

这对我很有用【参考方案2】:

我认为您的方法很有意义,因为您希望它在过滤器之前运行。

您是否考虑过使用带有订单的GlobalFilter?您可以确保它始终是第一个运行的过滤器。您还可以通过改变请求并在交换机上设置GATEWAY_REQUEST_URL_ATTR 属性来修改ServerWebExchange 中的URL。

查看PrefixPathGatewayFilterFactory,了解如何更改路由到的 URI 的示例。

您可以通过实现org.springframework.core.Ordered 接口在全局过滤器上设置顺序。

话虽如此,它仍然感觉有点像 hack,但它是一种替代方法。

【讨论】:

您是否考虑过将您提到的类链接到它们各自的 Javadocs?【参考方案3】:

我认为重写类 RoutePredicateHandlerMapping 可能会对您有所帮助。 见:org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

【讨论】:

以上是关于有没有办法在路由之前应用过滤器或代码(Spring Cloud Gateway)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Spring(Boot)中装饰 REST 响应?

有没有办法在没有弹簧安全的情况下使用弹簧过滤器链?

spring cloud gateway 某些路由中跳过全局过滤器

在 Spring Security 过滤器链之前记录请求标头

有没有办法跟踪或获取 JPA 在由于 BatchUpdateException 而失败之前完成的批迭代总数?

Spring Cloud Gateway - 快速开始