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

huangapple go评论72阅读模式
英文:

Is there a way to apply a filter or code BEFORE routing (Spring Cloud Gateway)

问题

我正在编写一个API网关,必须根据MAC地址路由请求。以下是端点的示例:

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

我编写了一个自定义断言工厂,用于提取MAC地址,执行必要的逻辑以确定MAC地址应该路由到哪个URL,然后将该信息存储在ServerWebExchange的属性中。

public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
    // 省略字段/构造函数

    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));
        };
    }
    // 配置类和实用方法被省略
}

注意:出于简洁性考虑,此实现已大大简化。

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

这个方法是有效的,但不是特别优雅。如果我可以在应用程序尝试匹配路由之前在每个进入网关的请求上设置交换属性,那就会更容易和简单。然后,过滤器可以是一个简单的谓词,用于检查交换属性的相等性。

我已经多次阅读了文档,但似乎没有什么方法可以做到这一点。过滤器始终仅适用于特定路由,并且仅在路由匹配后才运行。可能可以使第一个路由成为另一个执行必要代码、设置预期属性并始终返回false的谓词,但我如何保证该谓词总是首先运行?似乎应该支持这种用例,但无论如何我都找不到一种看起来不像是一种巧妙解决方案的方法。有什么想法吗?

英文:

I'm writing an API Gateway that must route requests based on a MAC address. Example of endpoints:

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

I've written a Custom Predicate Factory that extracts the MAC address, performs the necessary logic to determine what URL the MAC address should be routed to, then stores that information on the ServerWebExchange attributes.

public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory&lt;CustomRoutePredicateFactory.Config&gt; {
    // Fields/Constructors Omitted

    private static final String IP_ATTRIBUTE = &quot;assignedIp&quot;;
    private static final String MAC_ATTRIBUTE = &quot;mac&quot;;

    @Override
    public Predicate&lt;ServerWebExchange&gt; apply(Config config) {
        return (ServerWebExchange exchange) -&gt; {
            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 &amp; utility methods omitted
}

NOTE: This implementation is greatly simplified for brevity

With this implementation I'm able to guarantee that the MAC is extracted only once and the logic determining what URL the request belongs to is performed only once. The first call to the predicate factory will extract and set that information on ServerWebExchange Attributes and any further calls to the predicate factory will detect those attributes and use them to determine if they match.

This works, but it isn't particularly neat. It would be much easier and simpler if I could somehow set the Exchange Attributes on every single request entering the gateway BEFORE the application attempts to match routes. Then the filter could be a simple predicate that checks for equality on the Exchange Attributes.

I've read through the documentation several times, but nothing seems to be possible. Filters are always scoped to a particular route and run only after a route matches. It might be possible to make the first route be another Predicate that executes the necessary code, sets the expected attributes and always returns false, but can I guarantee that this predicate is always run first? It seems like there should be support for this kind of use case, but I cannot for the life of me find a way that doesn't seem like a hack. Any ideas?

答案1

得分: 2

@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;
    }
    
}
英文:

Use a WebFilter instead of a GatewayFilter or a GlobalFilter. They are applied only after the predicates chain. Whereas WebFilter works as an interceptor.

@Component
public class CustomRoutePredicateFactory implements WebFilter, Ordered {

    private static final String IP_ATTRIBUTE = &quot;assignedIp&quot;;
    private static final String MAC_ATTRIBUTE = &quot;mac&quot;;

    @Override
    public Mono&lt;Void&gt; filter(ServerWebExchange exchange, WebFilterChain chain) {
        String mac = (String) exchange.getAttributes().computeIfAbsent(MAC_ATTRIBUTE, key -&gt; extractMacAddress(exchange));
        exchange.getAttributes().computeIfAbsent(IP_ATTRIBUTE, key -&gt; findAssignedIp(mac));
        return chain.filter(exchange);
    }

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

答案2

得分: 0

我认为你的方法很有道理,因为你希望它在过滤器之前运行。

你考虑过使用具有设置顺序的GlobalFilter吗?你可以确保它始终是第一个要运行的过滤器。你还可以通过改变请求并在交换中设置GATEWAY_REQUEST_URL_ATTR属性来修改ServerWebExchange中的URL。

可以参考PrefixPathGatewayFilterFactory,了解如何改变要路由到的URI的示例。

通过实现org.springframework.core.Ordered接口,你可以为全局过滤器设置顺序。

话虽如此,仍然感觉有点像是一种曲线方式,但这是一种替代方法。

英文:

I think your approach makes sense since you want it to run before filters.

Have you considered using a GlobalFilter with an order set on it? You can ensure it's always the first filter to run. You can also modify the URL in the ServerWebExchange by mutating the request and setting the GATEWAY_REQUEST_URL_ATTR attribute on the exchange.

Take a look at the PrefixPathGatewayFilterFactory for an example of how to change the URI being routed to.

You can set an order on the Global filter by implementing the org.springframework.core.Ordered interface.

That being said, it still feels a little like a hack but it's an alternative approach.

答案3

得分: 0

我认为重写类RoutePredicateHandlerMapping可能对你有帮助。
参考:org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

英文:

i think it may help you that overriding the class RoutePredicateHandlerMapping.
see: org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

huangapple
  • 本文由 发表于 2020年10月9日 03:38:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/64269519.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定