简体   繁体   English

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

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

I'm writing an API Gateway that must route requests based on a MAC address.我正在编写一个 API 网关,它必须根据 MA​​C 地址路由请求。 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.我编写了一个自定义谓词工厂,它提取 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
}

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

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.如果我可以在应用程序尝试匹配路由之前在每个进入网关的请求上以某种方式设置 Exchange 属性,那将会更加容易和简单。 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 ?有可能使第一个路由成为另一个执行必要代码、设置预期属性并始终返回 false 的 Predicate,但是我可以保证这个 predicate始终首先运行吗? 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?有任何想法吗?

Use a WebFilter instead of a GatewayFilter or a GlobalFilter .使用WebFilter ,而不是GatewayFilterGlobalFilter They are applied only after the predicates chain.它们仅在谓词链之后应用。 Whereas WebFilter works as an interceptor.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;
    }
    
}

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?您是否考虑过使用带有顺序设置的GlobalFilter 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.您还可以通过更改请求并在 Exchange 上设置GATEWAY_REQUEST_URL_ATTR属性来修改ServerWebExchange的 URL。

Take a look at the PrefixPathGatewayFilterFactory for an example of how to change the URI being routed to.查看PrefixPathGatewayFilterFactory以了解如何更改路由到的 URI 的示例。

You can set an order on the Global filter by implementing the org.springframework.core.Ordered interface.您可以通过实现org.springframework.core.Ordered接口在全局过滤器上设置顺序。

That being said, it still feels a little like a hack but it's an alternative approach.话虽如此,它仍然感觉有点像黑客,但它是一种替代方法。

i think it may help you that overriding the class RoutePredicateHandlerMapping.我认为它可以帮助你覆盖类 RoutePredicateHandlerMapping。 see: org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler见: org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM