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<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. 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?
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 theServerWebExchange
by mutating the request and setting theGATEWAY_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.