I won't go into much detail (it's not hugely relevant, and it's a lot of code), but here's an interface I wrote:
@FunctionalInterface
public interface RouteProcessor {
// DocumentedEndpoint is my custom class that encapsulates an exposed endpoint of a service
Route.Builder process(Route.Builder routeInConstruction, DocumentedEndpoint endpoint);
}
It's used like this (it's a basic idea, not final code):
private void buildAndAddRoutesFrom(List<? extends DocumentedEndpoint> endpoints) {
for (DocumentedEndpoint endpoint : endpoints) {
Route.Builder routeBuilder = Route.builder();
for (RouteProcessor routeProcessor : routeProcessors) {
routeBuilder = routeProcessor.process(routeBuilder, endpoint);
}
routeFlux = routeFlux.concatWith(Mono.just(routeBuilder.build())); // questionable, but it's beyond the point for now
}
}
Again, it's not really important. What's important is that Spring Cloud Gateway's sources have, to put it mildly, room for improvement (in my view). In particular, I don't know how I do such a simple thing as adding a predicate to a Route
in construction. Like, suppose I construct it. Where do I pass it now?
@Bean
public RouteProcessor authenticationRouteProcessor() {
return (routeInConstruction, endpoint) -> {
AsyncPredicate<ServerWebExchange> newPredicate = AsyncPredicate.from(serverWebExchange -> serverWebExchange
.getRequest()
.getPath()
.toString()
.startsWith("/api/v1"));
AsyncPredicate<ServerWebExchange> compoundPredicate = routeInConstruction.getPredicate().and(predicate);
// what's next?
How do I set a predicate for a Route.Builder
in Spring Cloud Gateway?
Like, if you're saying that it's the only way I can pass it, by invoking this constructor (which is private, btw):
// from Route.AbstractBuilder
public Route build() {
Assert.notNull(this.id, "id can not be null");
Assert.notNull(this.uri, "uri can not be null");
AsyncPredicate<ServerWebExchange> predicate = getPredicate();
Assert.notNull(predicate, "predicate can not be null");
// Route has predicate, but Route.Builder doesn't!
return new Route(this.id, this.uri, this.order, predicate, this.gatewayFilters, this.metadata);
}
Gateway's design surprises me
I mean we have this nice and()
method that we can use as a setter
public Builder and(Predicate<ServerWebExchange> predicate) {
Assert.notNull(this.predicate, "can not call and() on null predicate");
this.predicate = this.predicate.and(predicate);
return this;
}
but that setting is basically the method's side effect so it feels more like a workaround rather than something that I should actually do as the library's user
But more importantly, how exactly do I add a predicate to an existing one? It should already be initialized due to the null assert. So this won't work:
@Bean
public RouteProcessor basicPredicateProcessor() {
return (routeInConstruction, endpoint) -> {
routeInConstruction.and(serverWebExchange -> serverWebExchange
.getRequest()
.getPath()
.toString()
.startsWith("/api/v1"));
return routeInConstruction;
};
}
Please let me know that it's not that far gone and I missed some crucial snippets of the source code
Like, before they start to write good code, here's your best bet:
@FunctionalInterface
public interface RouteProcessor {
Route.AsyncBuilder process(Route.AsyncBuilder routeInConstruction, DocumentedEndpoint endpoint);
}
private void buildAndAddRoutesFrom(List<? extends DocumentedEndpoint> endpoints) {
for (DocumentedEndpoint endpoint : endpoints) {
Route.AsyncBuilder routeBuilder = Route.async();
for (RouteProcessor routeProcessor : routeProcessors) {
routeBuilder = routeProcessor.process(routeBuilder, endpoint);
}
routeFlux = routeFlux.concatWith(Mono.just(routeBuilder.build()));
}
}
@Bean
// probably you'd want to set it first thing
// @Order(value = Ordered.HIGHEST_PRECEDENCE)
public RouteProcessor basicPredicateProcessor() {
return (routeInConstruction, endpoint) -> {
routeInConstruction.asyncPredicate(AsyncPredicate.from(serverWebExchange -> serverWebExchange
.getRequest()
.getPath()
.toString()
.startsWith("/api/v1")
));
return routeInConstruction;
};
}
It's because Route.AsyncBuilder
has a method that doesn't include that null check so you can safely initialize predicate
:
public AsyncBuilder asyncPredicate(AsyncPredicate<ServerWebExchange> predicate) {
this.predicate = predicate;
return this;
}