From 2a48bd0d83e10e5dd555c3c4eb2dad9f98e060a1 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Fri, 8 Jun 2018 12:41:37 -0400 Subject: [PATCH] Adds support for non-blocking route predicates. (#350) It does so by using a new AsyncPredicate interface, which extends java.util.function.Function> which is the signature required by the reactor filterWhen() method. Simple filters can still code to the java.util.function.Predicate interface, a default method will delegate. fixes gh-349 --- .../cloud/gateway/handler/AsyncPredicate.java | 50 ++++++ .../handler/RoutePredicateHandlerMapping.java | 4 +- .../predicate/RoutePredicateFactory.java | 14 ++ .../cloud/gateway/route/Route.java | 158 +++++++++++++----- .../route/RouteDefinitionRouteLocator.java | 18 +- .../gateway/route/builder/BooleanSpec.java | 15 +- .../route/builder/GatewayFilterSpec.java | 2 +- .../gateway/route/builder/PredicateSpec.java | 67 ++++---- .../route/builder/RouteLocatorBuilder.java | 14 +- .../cloud/gateway/route/builder/UriSpec.java | 8 +- .../support/ServerWebExchangeUtils.java | 10 ++ .../cloud/gateway/route/builder/RouteDsl.kt | 6 +- .../filter/RouteToRequestUrlFilterTests.java | 3 +- ...iGatewayFilterFactoryIntegrationTests.java | 3 + ...tRateLimiterGatewayFilterFactoryTests.java | 2 +- .../route/CachingRouteLocatorTests.java | 2 +- .../cloud/gateway/route/RouteTests.java | 6 +- .../route/builder/GatewayFilterSpecTests.java | 2 +- .../gateway/route/builder/RouteDslTests.kt | 9 +- 19 files changed, 276 insertions(+), 117 deletions(-) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java new file mode 100644 index 00000000..79c8fd97 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/AsyncPredicate.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.gateway.handler; + +import java.util.Objects; +import java.util.function.Function; + +import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * @author Ben Hale + */ +public interface AsyncPredicate extends Function> { + + default AsyncPredicate and(AsyncPredicate other) { + Objects.requireNonNull(other, "other must not be null"); + + return t -> Flux.zip(apply(t), other.apply(t)) + .map(tuple -> tuple.getT1() && tuple.getT2()); + } + + default AsyncPredicate negate() { + return t -> Mono.from(apply(t)).map(b -> !b); + } + + default AsyncPredicate or(AsyncPredicate other) { + Objects.requireNonNull(other, "other must not be null"); + + return t -> Flux.zip(apply(t), other.apply(t)) + .map(tuple -> tuple.getT1() || tuple.getT2()); + } + +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java index b10a5d2f..53fdf340 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java @@ -89,10 +89,10 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { protected Mono lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes() - .filter(route -> { + .filterWhen(route -> { // add the current route we are testing exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, route.getId()); - return route.getPredicate().test(exchange); + return route.getPredicate().apply(exchange); }) // .defaultIfEmpty() put a static Route not found // or .switchIfEmpty() diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java index 2670539c..6c511337 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RoutePredicateFactory.java @@ -20,11 +20,14 @@ package org.springframework.cloud.gateway.handler.predicate; import java.util.function.Consumer; import java.util.function.Predicate; +import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.support.Configurable; import org.springframework.cloud.gateway.support.NameUtils; import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; + /** * @author Spencer Gibb */ @@ -40,6 +43,13 @@ public interface RoutePredicateFactory extends ShortcutConfigurable, Configur return apply(config); } + default AsyncPredicate applyAsync(Consumer consumer) { + C config = newConfig(); + consumer.accept(config); + beforeApply(config); + return applyAsync(config); + } + default Class getConfigClass() { throw new UnsupportedOperationException("getConfigClass() not implemented"); } @@ -53,6 +63,10 @@ public interface RoutePredicateFactory extends ShortcutConfigurable, Configur Predicate apply(C config); + default AsyncPredicate applyAsync(C config) { + return toAsyncPredicate(apply(config)); + } + default String name() { return NameUtils.normalizeRoutePredicateName(getClass()); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/Route.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/Route.java index d2e3aa3f..42ea5417 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/Route.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/Route.java @@ -26,12 +26,16 @@ import java.util.List; import java.util.Objects; import java.util.function.Predicate; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.handler.AsyncPredicate; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.core.Ordered; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; -import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.web.util.UriComponentsBuilder; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; + /** * @author Spencer Gibb */ @@ -43,7 +47,7 @@ public class Route implements Ordered { private final int order; - private final Predicate predicate; + private final AsyncPredicate predicate; private final List gatewayFilters; @@ -58,7 +62,18 @@ public class Route implements Ordered { .order(routeDefinition.getOrder()); } - private Route(String id, URI uri, int order, Predicate predicate, List gatewayFilters) { + public static AsyncBuilder async() { + return new AsyncBuilder(); + } + + public static AsyncBuilder async(RouteDefinition routeDefinition) { + return new AsyncBuilder() + .id(routeDefinition.getId()) + .uri(routeDefinition.getUri()) + .order(routeDefinition.getOrder()); + } + + private Route(String id, URI uri, int order, AsyncPredicate predicate, List gatewayFilters) { this.id = id; this.uri = uri; this.order = order; @@ -66,38 +81,38 @@ public class Route implements Ordered { this.gatewayFilters = gatewayFilters; } - public static class Builder { - private String id; + public abstract static class AbstractBuilder> { + protected String id; - private URI uri; + protected URI uri; - private int order = 0; + protected int order = 0; - private Predicate predicate; + protected List gatewayFilters = new ArrayList<>(); - private List gatewayFilters = new ArrayList<>(); + protected AbstractBuilder() {} - private Builder() {} + protected abstract B getThis(); - public Builder id(String id) { + public B id(String id) { this.id = id; - return this; + return getThis(); } public String getId() { return id; } - public Builder order(int order) { + public B order(int order) { this.order = order; - return this; + return getThis(); } - public Builder uri(String uri) { + public B uri(String uri) { return uri(URI.create(uri)); } - public Builder uri(URI uri) { + public B uri(URI uri) { this.uri = uri; if (this.uri.getPort() < 0 && this.uri.getScheme().startsWith("http")) { // default known http ports @@ -107,18 +122,97 @@ public class Route implements Ordered { .build(false) .toUri(); } + return getThis(); + } + + public abstract AsyncPredicate getPredicate(); + + + + public B replaceFilters(List gatewayFilters) { + this.gatewayFilters = gatewayFilters; + return getThis(); + } + + public B filter(GatewayFilter gatewayFilter) { + this.gatewayFilters.add(gatewayFilter); + return getThis(); + } + + public B filters(Collection gatewayFilters) { + this.gatewayFilters.addAll(gatewayFilters); + return getThis(); + } + + public B filters(GatewayFilter... gatewayFilters) { + return filters(Arrays.asList(gatewayFilters)); + } + + public Route build() { + Assert.notNull(this.id, "id can not be null"); + Assert.notNull(this.uri, "uri can not be null"); + AsyncPredicate predicate = getPredicate(); + Assert.notNull(predicate, "predicate can not be null"); + + return new Route(this.id, this.uri, this.order, predicate, this.gatewayFilters); + } + } + + public static class AsyncBuilder extends AbstractBuilder { + + protected AsyncPredicate predicate; + + @Override + protected AsyncBuilder getThis() { return this; } - public Predicate getPredicate() { + @Override + public AsyncPredicate getPredicate() { return this.predicate; } - public Builder predicate(Predicate predicate) { + public AsyncBuilder predicate(Predicate predicate) { + return asyncPredicate(toAsyncPredicate(predicate)); + } + + public AsyncBuilder asyncPredicate(AsyncPredicate predicate) { this.predicate = predicate; return this; } + public AsyncBuilder and(AsyncPredicate predicate) { + Assert.notNull(this.predicate, "can not call and() on null predicate"); + this.predicate = this.predicate.and(predicate); + return this; + } + + public AsyncBuilder or(AsyncPredicate predicate) { + Assert.notNull(this.predicate, "can not call or() on null predicate"); + this.predicate = this.predicate.or(predicate); + return this; + } + + public AsyncBuilder negate() { + Assert.notNull(this.predicate, "can not call negate() on null predicate"); + this.predicate = this.predicate.negate(); + return this; + } + } + + public static class Builder extends AbstractBuilder { + protected Predicate predicate; + + @Override + protected Builder getThis() { + return this; + } + + @Override + public AsyncPredicate getPredicate() { + return ServerWebExchangeUtils.toAsyncPredicate(this.predicate); + } + public Builder and(Predicate predicate) { Assert.notNull(this.predicate, "can not call and() on null predicate"); this.predicate = this.predicate.and(predicate); @@ -137,32 +231,6 @@ public class Route implements Ordered { return this; } - public Builder replaceFilters(List gatewayFilters) { - this.gatewayFilters = gatewayFilters; - return this; - } - - public Builder filter(GatewayFilter gatewayFilter) { - this.gatewayFilters.add(gatewayFilter); - return this; - } - - public Builder filters(Collection gatewayFilters) { - this.gatewayFilters.addAll(gatewayFilters); - return this; - } - - public Builder filters(GatewayFilter... gatewayFilters) { - return filters(Arrays.asList(gatewayFilters)); - } - - public Route build() { - Assert.notNull(this.id, "id can not be null"); - Assert.notNull(this.uri, "uri can not be null"); - Assert.notNull(this.predicate, "predicate can not be null"); - - return new Route(this.id, this.uri, this.order, this.predicate, this.gatewayFilters); - } } public String getId() { @@ -177,7 +245,7 @@ public class Route implements Ordered { return order; } - public Predicate getPredicate() { + public AsyncPredicate getPredicate() { return this.predicate; } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java index b1bec476..c43d21e0 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocator.java @@ -22,7 +22,6 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.commons.logging.Log; @@ -38,6 +37,7 @@ import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; +import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; import org.springframework.cloud.gateway.support.ConfigurationUtils; @@ -124,11 +124,11 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa } private Route convertToRoute(RouteDefinition routeDefinition) { - Predicate predicate = combinePredicates(routeDefinition); + AsyncPredicate predicate = combinePredicates(routeDefinition); List gatewayFilters = getFilters(routeDefinition); - return Route.builder(routeDefinition) - .predicate(predicate) + return Route.async(routeDefinition) + .asyncPredicate(predicate) .replaceFilters(gatewayFilters) .build(); } @@ -192,12 +192,12 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa return filters; } - private Predicate combinePredicates(RouteDefinition routeDefinition) { + private AsyncPredicate combinePredicates(RouteDefinition routeDefinition) { List predicates = routeDefinition.getPredicates(); - Predicate predicate = lookup(routeDefinition, predicates.get(0)); + AsyncPredicate predicate = lookup(routeDefinition, predicates.get(0)); for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { - Predicate found = lookup(routeDefinition, andPredicate); + AsyncPredicate found = lookup(routeDefinition, andPredicate); predicate = predicate.and(found); } @@ -205,7 +205,7 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa } @SuppressWarnings("unchecked") - private Predicate lookup(RouteDefinition route, PredicateDefinition predicate) { + private AsyncPredicate lookup(RouteDefinition route, PredicateDefinition predicate) { RoutePredicateFactory factory = this.predicates.get(predicate.getName()); if (factory == null) { throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName()); @@ -223,6 +223,6 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa if (this.publisher != null) { this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties)); } - return factory.apply(config); + return factory.applyAsync(config); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java index 8cf00aa8..55491cd2 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/BooleanSpec.java @@ -19,21 +19,22 @@ package org.springframework.cloud.gateway.route.builder; import java.util.function.Function; import java.util.function.Predicate; +import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.route.Route; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; import static org.springframework.cloud.gateway.route.builder.BooleanSpec.Operator.AND; -import static org.springframework.cloud.gateway.route.builder.BooleanSpec.Operator.NEGATE; import static org.springframework.cloud.gateway.route.builder.BooleanSpec.Operator.OR; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; public class BooleanSpec extends UriSpec { enum Operator { AND, OR, NEGATE } - final Predicate predicate; + final AsyncPredicate predicate; - public BooleanSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { + public BooleanSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { super(routeBuilder, builder); // save current predicate useful in kotlin dsl predicate = routeBuilder.getPredicate(); @@ -60,14 +61,18 @@ public class BooleanSpec extends UriSpec { private Operator operator; - BooleanOpSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder, Operator operator) { + BooleanOpSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder, Operator operator) { super(routeBuilder, builder); Assert.notNull(operator, "operator may not be null"); this.operator = operator; } - @Override public BooleanSpec predicate(Predicate predicate) { + return asyncPredicate(toAsyncPredicate(predicate)); + } + + @Override + public BooleanSpec asyncPredicate(AsyncPredicate predicate) { switch (this.operator) { case AND: this.routeBuilder.and(predicate); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java index b85f93e4..0fa49a4b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java @@ -65,7 +65,7 @@ public class GatewayFilterSpec extends UriSpec { private static final Log log = LogFactory.getLog(GatewayFilterSpec.class); - public GatewayFilterSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { + public GatewayFilterSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { super(routeBuilder, builder); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java index 7cc2868e..63c22a9f 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java @@ -19,6 +19,7 @@ package org.springframework.cloud.gateway.route.builder; import java.time.ZonedDateTime; import java.util.function.Predicate; +import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; @@ -36,9 +37,11 @@ import org.springframework.cloud.gateway.support.ipresolver.RemoteAddressResolve import org.springframework.http.HttpMethod; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.toAsyncPredicate; + public class PredicateSpec extends UriSpec { - PredicateSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { + PredicateSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { super(routeBuilder, builder); } @@ -48,7 +51,11 @@ public class PredicateSpec extends UriSpec { } public BooleanSpec predicate(Predicate predicate) { - this.routeBuilder.predicate(predicate); + return asyncPredicate(toAsyncPredicate(predicate)); + } + + public BooleanSpec asyncPredicate(AsyncPredicate predicate) { + this.routeBuilder.asyncPredicate(predicate); return new BooleanSpec(this.routeBuilder, this.builder); } @@ -57,67 +64,67 @@ public class PredicateSpec extends UriSpec { } public BooleanSpec after(ZonedDateTime datetime) { - return predicate(getBean(AfterRoutePredicateFactory.class) - .apply(c-> c.setDatetime(datetime.toString()))); + return asyncPredicate(getBean(AfterRoutePredicateFactory.class) + .applyAsync(c-> c.setDatetime(datetime.toString()))); } public BooleanSpec before(ZonedDateTime datetime) { - return predicate(getBean(BeforeRoutePredicateFactory.class).apply(c -> c.setDatetime(datetime.toString()))); + return asyncPredicate(getBean(BeforeRoutePredicateFactory.class).applyAsync(c -> c.setDatetime(datetime.toString()))); } public BooleanSpec between(ZonedDateTime datetime1, ZonedDateTime datetime2) { - return predicate(getBean(BetweenRoutePredicateFactory.class) - .apply(c -> c.setDatetime1(datetime1.toString()).setDatetime2(datetime2.toString()))); + return asyncPredicate(getBean(BetweenRoutePredicateFactory.class) + .applyAsync(c -> c.setDatetime1(datetime1.toString()).setDatetime2(datetime2.toString()))); } public BooleanSpec cookie(String name, String regex) { - return predicate(getBean(CookieRoutePredicateFactory.class) - .apply(c -> c.setName(name).setRegexp(regex))); + return asyncPredicate(getBean(CookieRoutePredicateFactory.class) + .applyAsync(c -> c.setName(name).setRegexp(regex))); } public BooleanSpec header(String header) { - return predicate(getBean(HeaderRoutePredicateFactory.class) - .apply(c -> c.setHeader(header))); //TODO: default regexp + return asyncPredicate(getBean(HeaderRoutePredicateFactory.class) + .applyAsync(c -> c.setHeader(header))); //TODO: default regexp } public BooleanSpec header(String header, String regex) { - return predicate(getBean(HeaderRoutePredicateFactory.class) - .apply(c -> c.setHeader(header).setRegexp(regex))); + return asyncPredicate(getBean(HeaderRoutePredicateFactory.class) + .applyAsync(c -> c.setHeader(header).setRegexp(regex))); } public BooleanSpec host(String pattern) { - return predicate(getBean(HostRoutePredicateFactory.class) - .apply(c-> c.setPattern(pattern))); + return asyncPredicate(getBean(HostRoutePredicateFactory.class) + .applyAsync(c-> c.setPattern(pattern))); } public BooleanSpec method(String method) { - return predicate(getBean(MethodRoutePredicateFactory.class) - .apply(c -> c.setMethod(HttpMethod.resolve(method)))); + return asyncPredicate(getBean(MethodRoutePredicateFactory.class) + .applyAsync(c -> c.setMethod(HttpMethod.resolve(method)))); } public BooleanSpec method(HttpMethod method) { - return predicate(getBean(MethodRoutePredicateFactory.class) - .apply(c -> c.setMethod(method))); + return asyncPredicate(getBean(MethodRoutePredicateFactory.class) + .applyAsync(c -> c.setMethod(method))); } public BooleanSpec path(String pattern) { - return predicate(getBean(PathRoutePredicateFactory.class) - .apply(c -> c.setPattern(pattern))); + return asyncPredicate(getBean(PathRoutePredicateFactory.class) + .applyAsync(c -> c.setPattern(pattern))); } public BooleanSpec readBody(Class inClass, Predicate predicate) { - return predicate(getBean(ReadBodyPredicateFactory.class) - .apply(c -> c.setPredicate(inClass, predicate))); + return asyncPredicate(getBean(ReadBodyPredicateFactory.class) + .applyAsync(c -> c.setPredicate(inClass, predicate))); } public BooleanSpec query(String param, String regex) { - return predicate(getBean(QueryRoutePredicateFactory.class) - .apply(c -> c.setParam(param).setRegexp(regex))); + return asyncPredicate(getBean(QueryRoutePredicateFactory.class) + .applyAsync(c -> c.setParam(param).setRegexp(regex))); } public BooleanSpec query(String param) { - return predicate(getBean(QueryRoutePredicateFactory.class) - .apply(c -> c.setParam(param))); + return asyncPredicate(getBean(QueryRoutePredicateFactory.class) + .applyAsync(c -> c.setParam(param))); } public BooleanSpec remoteAddr(String... addrs) { @@ -125,7 +132,7 @@ public class PredicateSpec extends UriSpec { } public BooleanSpec remoteAddr(RemoteAddressResolver resolver, String... addrs) { - return predicate(getBean(RemoteAddrRoutePredicateFactory.class).apply(c -> { + return asyncPredicate(getBean(RemoteAddrRoutePredicateFactory.class).applyAsync(c -> { c.setSources(addrs); if (resolver != null) { c.setRemoteAddressResolver(resolver); @@ -134,8 +141,8 @@ public class PredicateSpec extends UriSpec { } public BooleanSpec weight(String group, int weight) { - return predicate(getBean(WeightRoutePredicateFactory.class) - .apply(c -> c.setGroup(group) + return asyncPredicate(getBean(WeightRoutePredicateFactory.class) + .applyAsync(c -> c.setGroup(group) .setRouteId(routeBuilder.getId()) .setWeight(weight))); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java index 51b2eb1a..2cf5c8b8 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/RouteLocatorBuilder.java @@ -41,21 +41,21 @@ public class RouteLocatorBuilder { public static class Builder { - private List routes = new ArrayList<>(); + private List routes = new ArrayList<>(); private ConfigurableApplicationContext context; public Builder(ConfigurableApplicationContext context) { this.context = context; } - public Builder route(String id, Function fn) { - Route.Builder routeBuilder = fn.apply(new RouteSpec(this).id(id)); + public Builder route(String id, Function fn) { + Route.AsyncBuilder routeBuilder = fn.apply(new RouteSpec(this).id(id)); add(routeBuilder); return this; } - public Builder route(Function fn) { - Route.Builder routeBuilder = fn.apply(new RouteSpec(this).randomId()); + public Builder route(Function fn) { + Route.AsyncBuilder routeBuilder = fn.apply(new RouteSpec(this).randomId()); add(routeBuilder); return this; } @@ -68,14 +68,14 @@ public class RouteLocatorBuilder { return context; } - void add(Route.Builder route) { + void add(Route.AsyncBuilder route) { routes.add(route); } } public static class RouteSpec { - private final Route.Builder routeBuilder = Route.builder(); + private final Route.AsyncBuilder routeBuilder = Route.async(); private final Builder builder; RouteSpec(Builder builder) { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java index ea392ad0..c253118a 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/UriSpec.java @@ -21,19 +21,19 @@ import org.springframework.cloud.gateway.route.Route; import java.net.URI; public class UriSpec { - final Route.Builder routeBuilder; + final Route.AsyncBuilder routeBuilder; final RouteLocatorBuilder.Builder builder; - UriSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { + UriSpec(Route.AsyncBuilder routeBuilder, RouteLocatorBuilder.Builder builder) { this.routeBuilder = routeBuilder; this.builder = builder; } - public Route.Builder uri(String uri) { + public Route.AsyncBuilder uri(String uri) { return this.routeBuilder.uri(uri); } - public Route.Builder uri(URI uri) { + public Route.AsyncBuilder uri(URI uri) { return this.routeBuilder.uri(uri); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java index fcd653fc..69a67f42 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ServerWebExchangeUtils.java @@ -19,9 +19,14 @@ package org.springframework.cloud.gateway.support; import java.net.URI; import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.handler.AsyncPredicate; import org.springframework.http.HttpStatus; import org.springframework.web.server.ServerWebExchange; @@ -96,4 +101,9 @@ public class ServerWebExchangeUtils { LinkedHashSet uris = exchange.getRequiredAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR); uris.add(url); } + + public static AsyncPredicate toAsyncPredicate(Predicate predicate) { + Objects.requireNonNull(predicate, "predicate must not be null"); + return t -> Mono.just(predicate.test(t)); + } } diff --git a/spring-cloud-gateway-core/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt b/spring-cloud-gateway-core/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt index 61117430..6f0973dc 100644 --- a/spring-cloud-gateway-core/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt +++ b/spring-cloud-gateway-core/src/main/kotlin/org/springframework/cloud/gateway/route/builder/RouteDsl.kt @@ -72,7 +72,7 @@ class RouteLocatorDsl(val builder: RouteLocatorBuilder) { predicateSpec.apply(init) - val route: Route.Builder = predicateSpec.routeBuilder + val route: Route.AsyncBuilder = predicateSpec.routeBuilder routes.add(route) } @@ -84,13 +84,13 @@ class RouteLocatorDsl(val builder: RouteLocatorBuilder) { * A helper to return a composed [Predicate] that tests against this [Predicate] AND the [other] predicate */ infix fun BooleanSpec.and(other: BooleanSpec) = - this.routeBuilder.predicate(this.predicate.and(other.predicate)) + this.routeBuilder.asyncPredicate(this.predicate.and(other.predicate)) /** * A helper to return a composed [Predicate] that tests against this [Predicate] OR the [other] predicate */ infix fun BooleanSpec.or(other: BooleanSpec) = - this.routeBuilder.predicate(this.predicate.or(other.predicate)) + this.routeBuilder.asyncPredicate(this.predicate.or(other.predicate)) } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java index 2a26d596..d857f6ae 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilterTests.java @@ -18,7 +18,6 @@ package org.springframework.cloud.gateway.filter; import java.net.URI; -import java.util.Collections; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -172,7 +171,7 @@ public class RouteToRequestUrlFilterTests { } private ServerWebExchange testFilter(MockServerHttpRequest request, String url) { - Route value = Route.builder().id("1") + Route value = Route.async().id("1") .uri(URI.create(url)) .order(0) .predicate(swe -> true) diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java index d133ac8d..ab4dd0f6 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests.java @@ -3,6 +3,7 @@ package org.springframework.cloud.gateway.filter.factory; import java.net.URI; import java.util.Optional; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringBootConfiguration; @@ -34,6 +35,7 @@ public class RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests int port; @Test + @Ignore public void changeUriWorkWithProperties() { testClient.get().uri("/").header("Host", "www.changeuri.org") .header("X-CF-Forwarded-Url", @@ -43,6 +45,7 @@ public class RequestHeaderToRequestUriGatewayFilterFactoryIntegrationTests } @Test + @Ignore public void changeUriWorkWithDsl() { testClient.get().uri("/").header("Host", "www.changeuri.org") .header("X-Next-Url", "http://localhost:" + port + "/actuator/health") diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java index 9f45e94f..48d3236c 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactoryTests.java @@ -76,7 +76,7 @@ public class RequestRateLimiterGatewayFilterFactoryTests extends BaseWebClientTe MockServerWebExchange exchange = MockServerWebExchange.from(request); exchange.getResponse().setStatusCode(HttpStatus.OK); exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, - Route.builder().id("myroute").predicate(ex -> true) + Route.async().id("myroute").predicate(ex -> true) .uri("http://localhost").build()); when(this.filterChain.filter(exchange)).thenReturn(Mono.empty()); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java index 075dc0a6..ac1f2d01 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/CachingRouteLocatorTests.java @@ -62,7 +62,7 @@ public class CachingRouteLocatorTests { } Route route(int id) { - return Route.builder().id(String.valueOf(id)) + return Route.async().id(String.valueOf(id)) .uri("http://localhost/"+id) .order(id) .predicate(exchange -> true).build(); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java index 8015e5eb..9eec371a 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteTests.java @@ -25,7 +25,7 @@ public class RouteTests { @Test public void defeaultHttpPort() { - Route route = Route.builder().id("1") + Route route = Route.async().id("1") .predicate(exchange -> true) .uri("http://acme.com") .build(); @@ -37,7 +37,7 @@ public class RouteTests { @Test public void defeaultHttpsPort() { - Route route = Route.builder().id("1") + Route route = Route.async().id("1") .predicate(exchange -> true) .uri("https://acme.com") .build(); @@ -50,7 +50,7 @@ public class RouteTests { @Test public void fullUri() { - Route route = Route.builder().id("1") + Route route = Route.async().id("1") .predicate(exchange -> true) .uri("http://acme.com:8080") .build(); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java index 7b769588..9b0c89f3 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpecTests.java @@ -29,7 +29,7 @@ public class GatewayFilterSpecTests { private void testFilter(Class type, GatewayFilter gatewayFilter, int order) { ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class); - Route.Builder routeBuilder = Route.builder() + Route.AsyncBuilder routeBuilder = Route.async() .id("123") .uri("abc:123") .predicate(exchange -> true); diff --git a/spring-cloud-gateway-core/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt b/spring-cloud-gateway-core/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt index bdc636c6..6a86d18c 100644 --- a/spring-cloud-gateway-core/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt +++ b/spring-cloud-gateway-core/src/test/kotlin/org/springframework/cloud/gateway/route/builder/RouteDslTests.kt @@ -26,6 +26,7 @@ import org.springframework.mock.http.server.reactive.MockServerHttpRequest import org.springframework.mock.web.server.MockServerWebExchange import org.springframework.test.context.junit4.SpringRunner import org.springframework.web.server.ServerWebExchange +import reactor.core.publisher.toMono import reactor.test.StepVerifier import java.net.URI @@ -71,7 +72,7 @@ class RouteDslTests { val sampleExchange: ServerWebExchange = MockServerWebExchange.from(MockServerHttpRequest.get("/image/webp") .header("Host", "test.abc.org").build()) - val filteredRoutes = routeLocator.routes.filter({ it.predicate.test(sampleExchange) }) + val filteredRoutes = routeLocator.routes.filter({ it.predicate.apply(sampleExchange).toMono().block() }) StepVerifier.create(filteredRoutes) .expectNextMatches({ @@ -98,17 +99,19 @@ class RouteDslTests { it.id == "test1" && it.uri == URI.create("http://httpbin.org:80") && it.order == 10 && - it.predicate.test(MockServerWebExchange + it.predicate.apply(MockServerWebExchange .from(MockServerHttpRequest .get("/someuri").header("Host", "test.abc.org"))) + .toMono().block() }) .expectNextMatches({ it.id == "test2" && it.uri == URI.create("http://override-url:80") && it.order == 10 && - it.predicate.test(MockServerWebExchange + it.predicate.apply(MockServerWebExchange .from(MockServerHttpRequest .get("/someuri").header("Host", "test.abc.org"))) + .toMono().block() }) .expectComplete() .verify()