From 589ed9c4f3ca20a4cdd95e902f152d69ce7b9f3d Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Mon, 5 Mar 2018 16:36:48 -0500 Subject: [PATCH] Binds configuration to Configurable factory beans directly using Boot Binder. Deprecates the use of Tuple. Depracates ArgumentHints in favor of ShortcutConfigurable. Creates Configurable interface to allow factories to create a configuration object that can be bound to properties. Creates StatefulConfigurable interface that extends Configurable and allows singlton beans (like RedisRateLimiter) to store a map of configuration keyed on route id. These beans use events like FilterArgsEvent and PredicateArgsEvent to gather configuration as it is read for each route. --- .../actuate/GatewayControllerEndpoint.java | 2 +- .../config/GatewayAutoConfiguration.java | 12 +- .../config/GatewayRedisAutoConfiguration.java | 10 +- ...DiscoveryClientRouteDefinitionLocator.java | 12 +- .../cloud/gateway/event/FilterArgsEvent.java | 41 ++++++ .../gateway/event/PredicateArgsEvent.java | 41 ++++++ .../cloud/gateway/filter/GatewayFilter.java | 6 +- .../gateway/filter/OrderedGatewayFilter.java | 4 + .../factory/AbstractGatewayFilterFactory.java | 57 ++++++++ ...AbstractNameValueGatewayFilterFactory.java | 73 ++++++++++ .../AddRequestHeaderGatewayFilterFactory.java | 24 +--- ...dRequestParameterGatewayFilterFactory.java | 29 +--- ...AddResponseHeaderGatewayFilterFactory.java | 21 +-- .../filter/factory/GatewayFilterFactory.java | 39 +++++- .../factory/HystrixGatewayFilterFactory.java | 84 +++++++----- .../PrefixPathGatewayFilterFactory.java | 34 +++-- .../RedirectToGatewayFilterFactory.java | 38 ++++-- ...moveRequestHeaderGatewayFilterFactory.java | 24 ++-- ...oveResponseHeaderGatewayFilterFactory.java | 18 ++- ...equestRateLimiterGatewayFilterFactory.java | 80 +++++++---- .../RewritePathGatewayFilterFactory.java | 43 ++++-- .../SaveSessionGatewayFilterFactory.java | 5 +- .../SecureHeadersGatewayFilterFactory.java | 7 +- .../factory/SetPathGatewayFilterFactory.java | 32 +++-- .../SetRequestHeaderGatewayFilterFactory.java | 21 +-- ...SetResponseHeaderGatewayFilterFactory.java | 22 +-- .../SetStatusGatewayFilterFactory.java | 45 +++--- .../StripPrefixGatewayFilterFactory.java | 31 +++-- .../filter/ratelimit/AbstractRateLimiter.java | 76 +++++++++++ .../gateway/filter/ratelimit/RateLimiter.java | 7 +- .../filter/ratelimit/RedisRateLimiter.java | 75 +++++++--- .../AbstractRoutePredicateFactory.java | 42 ++++++ .../predicate/AfterRoutePredicateFactory.java | 34 +++-- .../BeforeRoutePredicateFactory.java | 33 +++-- .../BetweenRoutePredicateFactory.java | 47 +++++-- .../CookieRoutePredicateFactory.java | 51 +++++-- .../HeaderRoutePredicateFactory.java | 53 +++++--- .../predicate/HostRoutePredicateFactory.java | 41 ++++-- .../MethodRoutePredicateFactory.java | 35 +++-- .../predicate/PathRoutePredicateFactory.java | 54 +++++--- .../predicate/QueryRoutePredicateFactory.java | 64 +++++---- .../RemoteAddrRoutePredicateFactory.java | 74 +++++++--- .../predicate/RoutePredicateFactory.java | 38 +++++- .../cloud/gateway/route/Route.java | 4 + .../route/RouteDefinitionRouteLocator.java | 117 +++++++++------- .../route/builder/GatewayFilterSpec.java | 124 ++++++++++------- .../gateway/route/builder/PredicateSpec.java | 44 +++--- .../gateway/support/AbstractConfigurable.java | 45 ++++++ .../support/AbstractStatefulConfigurable.java | 44 ++++++ .../cloud/gateway/support/ArgumentHints.java | 11 +- .../cloud/gateway/support/Configurable.java | 23 ++++ .../gateway/support/ConfigurationUtils.java | 60 ++++++++ .../cloud/gateway/support/NameUtils.java | 4 +- .../gateway/support/ShortcutConfigurable.java | 128 ++++++++++++++++++ .../gateway/support/StatefulConfigurable.java | 25 ++++ ...equestHeaderGatewayFilterFactoryTests.java | 36 ++++- ...estParameterGatewayFilterFactoryTests.java | 32 ++++- .../HystrixGatewayFilterFactoryTests.java | 41 ++++++ .../RedirectToGatewayFilterFactoryTests.java | 23 +--- ...equestHeaderGatewayFilterFactoryTests.java | 21 +-- ...sponseHeaderGatewayFilterFactoryTests.java | 23 +--- ...tRateLimiterGatewayFilterFactoryTests.java | 19 ++- ...hGatewayFilterFactoryIntegrationTests.java | 19 +-- .../RewritePathGatewayFilterFactoryTests.java | 2 +- ...hGatewayFilterFactoryIntegrationTests.java | 19 +-- .../SetPathGatewayFilterFactoryTests.java | 2 +- ...ponseHeaderGatewayFilterFactoryTests.java} | 24 +--- ...xGatewayFilterFactoryIntegrationTests.java | 21 +-- .../StripPrefixGatewayFilterFactoryTests.java | 2 +- ...ncipalNameKeyResolverIntegrationTests.java | 34 ++++- .../RedisRateLimiterConfigTests.java | 101 ++++++++++++++ .../ratelimit/RedisRateLimiterTests.java | 17 ++- .../AfterRoutePredicateFactoryTests.java | 4 +- .../BeforeRoutePredicateFactoryTests.java | 4 +- .../BetweenRoutePredicateFactoryTests.java | 21 ++- .../HeaderRoutePredicateFactoryTests.java | 47 ++----- .../HostRoutePredicateFactoryTests.java | 28 +--- .../MethodRoutePredicateFactoryTests.java | 30 +--- .../PathRoutePredicateFactoryTests.java | 29 +--- .../RemoteAddrRoutePredicateFactoryTests.java | 4 +- .../RouteDefinitionRouteLocatorTests.java | 9 +- .../support/ShortcutConfigurableTests.java | 97 +++++++++++++ .../cloud/gateway/test/AdhocTestSuite.java | 4 +- .../gateway/test/BaseWebClientTests.java | 4 - .../application-redis-rate-limiter-config.yml | 20 +++ .../application-request-header-web-filter.yml | 2 +- ...plication-request-parameter-web-filter.yml | 2 +- .../src/test/resources/application.yml | 5 - .../sample/GatewaySampleApplication.java | 16 +-- .../gateway/sample/ThrottleGatewayFilter.java | 98 ++++++++++++++ .../sample/ThrottleGatewayFilterFactory.java | 65 --------- .../sample/GatewaySampleApplicationTests.java | 4 +- 92 files changed, 2202 insertions(+), 935 deletions(-) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/Configurable.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java rename spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/{SetResponseGatewayFilterFactoryTests.java => SetResponseHeaderGatewayFilterFactoryTests.java} (69%) create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java create mode 100644 spring-cloud-gateway-core/src/test/resources/application-redis-rate-limiter-config.yml create mode 100644 spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java delete mode 100644 spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilterFactory.java diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java index 812e6663..0892c94c 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java @@ -118,7 +118,7 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware return Mono.zip(routeDefs, routes).map(tuple -> { Map allRoutes = new HashMap<>(); allRoutes.put("routeDefinitions", tuple.getT1()); - allRoutes.put("routes", tuple.getT2()); + // allRoutes.put("routes", tuple.getT2()); return allRoutes; }); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 79a59a3f..3ac494af 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -20,7 +20,6 @@ package org.springframework.cloud.gateway.config; import java.util.List; import java.util.function.Consumer; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; @@ -37,11 +36,8 @@ import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint; import org.springframework.cloud.gateway.filter.ForwardRoutingFilter; import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; -import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; import org.springframework.cloud.gateway.filter.NettyRoutingFilter; import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter; -import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter; import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter; import org.springframework.cloud.gateway.filter.WebsocketRoutingFilter; import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; @@ -65,6 +61,9 @@ import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayF import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilter; +import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter; +import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilter; import org.springframework.cloud.gateway.filter.headers.XForwardedHeadersFilter; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.PrincipalNameKeyResolver; @@ -105,6 +104,9 @@ import org.springframework.web.reactive.socket.server.support.HandshakeWebSocket import com.netflix.hystrix.HystrixObservableCommand; +import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; + +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import reactor.core.publisher.Flux; import reactor.ipc.netty.http.client.HttpClient; import reactor.ipc.netty.http.client.HttpClientOptions; @@ -112,8 +114,6 @@ import reactor.ipc.netty.options.ClientProxyOptions; import reactor.ipc.netty.resources.PoolResources; import rx.RxReactiveStreams; -import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; - /** * @author Spencer Gibb */ diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java index a150d42b..0f8c3051 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java @@ -12,7 +12,6 @@ import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.ResourceLoader; import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.core.RedisTemplate; @@ -22,6 +21,7 @@ import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.scripting.support.ResourceScriptSource; +import org.springframework.validation.Validator; import org.springframework.web.reactive.DispatcherHandler; @Configuration @@ -43,8 +43,7 @@ class GatewayRedisAutoConfiguration { @Bean //TODO: replace with ReactiveStringRedisTemplate in future public ReactiveRedisTemplate stringReactiveRedisTemplate( - ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, - ResourceLoader resourceLoader) { + ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) { RedisSerializer serializer = new StringRedisSerializer(); RedisSerializationContext serializationContext = RedisSerializationContext .newSerializationContext() @@ -59,7 +58,8 @@ class GatewayRedisAutoConfiguration { @Bean public RedisRateLimiter redisRateLimiter(ReactiveRedisTemplate redisTemplate, - @Qualifier("redisRequestRateLimiterScript") RedisScript> redisScript) { - return new RedisRateLimiter(redisTemplate, redisScript); + @Qualifier("redisRequestRateLimiterScript") RedisScript> redisScript, + Validator validator) { + return new RedisRateLimiter(redisTemplate, redisScript, validator); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java index aa83df88..761a4a3b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java @@ -29,9 +29,9 @@ import org.springframework.cloud.gateway.route.RouteDefinitionLocator; import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY; import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY; -import static org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory.PATTERN_KEY; -import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterName; -import static org.springframework.cloud.gateway.support.NameUtils.normalizePredicateName; +import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY; +import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName; +import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName; import reactor.core.publisher.Flux; @@ -60,13 +60,13 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc // add a predicate that matches the url at /serviceId /*PredicateDefinition barePredicate = new PredicateDefinition(); - barePredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class)); + barePredicate.setName(normalizePredicateName(PathRoutePredicate.class)); barePredicate.addArg(PATTERN_KEY, "/" + serviceId); routeDefinition.getPredicates().add(barePredicate);*/ // add a predicate that matches the url at /serviceId/** PredicateDefinition subPredicate = new PredicateDefinition(); - subPredicate.setName(normalizePredicateName(PathRoutePredicateFactory.class)); + subPredicate.setName(normalizeRoutePredicateName(PathRoutePredicateFactory.class)); subPredicate.addArg(PATTERN_KEY, "/" + serviceId + "/**"); routeDefinition.getPredicates().add(subPredicate); @@ -74,7 +74,7 @@ public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLoc // add a filter that removes /serviceId by default FilterDefinition filter = new FilterDefinition(); - filter.setName(normalizeFilterName(RewritePathGatewayFilterFactory.class)); + filter.setName(normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class)); String regex = "/" + serviceId + "/(?.*)"; String replacement = "/${remaining}"; filter.addArg(REGEXP_KEY, regex); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java new file mode 100644 index 00000000..6fe2edd2 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/FilterArgsEvent.java @@ -0,0 +1,41 @@ +/* + * 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.event; + +import java.util.Map; + +import org.springframework.context.ApplicationEvent; + +public class FilterArgsEvent extends ApplicationEvent { + private String routeId; + private final Map args; + + public FilterArgsEvent(Object source, String routeId, Map args) { + super(source); + this.routeId = routeId; + this.args = args; + } + + public String getRouteId() { + return routeId; + } + + public Map getArgs() { + return args; + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java new file mode 100644 index 00000000..2859aba3 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/event/PredicateArgsEvent.java @@ -0,0 +1,41 @@ +/* + * 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.event; + +import java.util.Map; + +import org.springframework.context.ApplicationEvent; + +public class PredicateArgsEvent extends ApplicationEvent { + private String routeId; + private final Map args; + + public PredicateArgsEvent(Object source, String routeId, Map args) { + super(source); + this.routeId = routeId; + this.args = args; + } + + public String getRouteId() { + return routeId; + } + + public Map getArgs() { + return args; + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java index ef248320..87936593 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayFilter.java @@ -16,6 +16,7 @@ package org.springframework.cloud.gateway.filter; * limitations under the License. */ +import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @@ -30,7 +31,10 @@ import reactor.core.publisher.Mono; * @author Rossen Stoyanchev * @since 5.0 */ -public interface GatewayFilter { +public interface GatewayFilter extends ShortcutConfigurable { + + String NAME_KEY = "name"; + String VALUE_KEY = "value"; /** * Process the Web request and (optionally) delegate to the next diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java index f98a4550..f9f21079 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/OrderedGatewayFilter.java @@ -35,6 +35,10 @@ public class OrderedGatewayFilter implements GatewayFilter, Ordered { this.order = order; } + public GatewayFilter getDelegate() { + return delegate; + } + @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { return this.delegate.filter(exchange, chain); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java new file mode 100644 index 00000000..383062e4 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractGatewayFilterFactory.java @@ -0,0 +1,57 @@ +/* + * 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.filter.factory; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.support.AbstractConfigurable; +import org.springframework.tuple.Tuple; + +public abstract class AbstractGatewayFilterFactory + extends AbstractConfigurable implements GatewayFilterFactory { + + @SuppressWarnings("unchecked") + public AbstractGatewayFilterFactory() { + super((Class) Object.class); + } + + public AbstractGatewayFilterFactory(Class configClass) { + super(configClass); + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public GatewayFilter apply(Tuple args) { + throw new UnsupportedOperationException("apply(Tuple) not supported"); + } + + public static class NameConfig { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java new file mode 100644 index 00000000..597f6b90 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AbstractNameValueGatewayFilterFactory.java @@ -0,0 +1,73 @@ +/* + * 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.filter.factory; + +import java.util.Arrays; +import java.util.List; + +import javax.validation.constraints.NotEmpty; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.core.style.ToStringCreator; +import org.springframework.validation.annotation.Validated; + +public abstract class AbstractNameValueGatewayFilterFactory extends AbstractGatewayFilterFactory { + + public AbstractNameValueGatewayFilterFactory() { + super(NameValueConfig.class); + } + + public List shortcutFieldOrder() { + return Arrays.asList(GatewayFilter.NAME_KEY, GatewayFilter.VALUE_KEY); + } + + + @Validated + public static class NameValueConfig { + @NotEmpty + protected String name; + @NotEmpty + protected String value; + + public String getName() { + return name; + } + + public NameValueConfig setName(String name) { + this.name = name; + return this; + } + + public String getValue() { + return value; + } + + public NameValueConfig setValue(String value) { + this.value = value; + return this; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("name", name) + .append("value", value) + .toString(); + } + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java index 4c3c8476..9315df1f 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactory.java @@ -17,37 +17,23 @@ package org.springframework.cloud.gateway.filter.factory; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.http.server.reactive.ServerHttpRequest; -import java.util.Arrays; -import java.util.List; - /** * @author Spencer Gibb */ -public class AddRequestHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override - public List argNames() { - return Arrays.asList(NAME_KEY, VALUE_KEY); - } - - @Override - public GatewayFilter apply(Tuple args) { - String name = args.getString(NAME_KEY); - String value = args.getString(VALUE_KEY); - return apply(name, value); - } - - public GatewayFilter apply(String name, String value) { + public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() - .header(name, value) + .header(config.getName(), config.getValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); }; - } + } + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java index e9002d53..9aea7a62 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactory.java @@ -18,36 +18,20 @@ package org.springframework.cloud.gateway.filter.factory; import java.net.URI; -import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.List; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; -import org.springframework.util.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; /** * @author Spencer Gibb */ -public class AddRequestParameterGatewayFilterFactory implements GatewayFilterFactory { +public class AddRequestParameterGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override - public List argNames() { - return Arrays.asList(NAME_KEY, VALUE_KEY); - } - - @Override - public GatewayFilter apply(Tuple args) { - String parameter = args.getString(NAME_KEY); - String value = args.getString(VALUE_KEY); - return apply(parameter, value); - } - - public GatewayFilter apply(String parameter, String value) { + public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { - URI uri = exchange.getRequest().getURI(); StringBuilder query = new StringBuilder(); String originalQuery = uri.getRawQuery(); @@ -60,9 +44,9 @@ public class AddRequestParameterGatewayFilterFactory implements GatewayFilterFac } //TODO urlencode? - query.append(parameter); + query.append(config.getName()); query.append('='); - query.append(value); + query.append(config.getValue()); try { URI newUri = UriComponentsBuilder.fromUri(uri) @@ -78,4 +62,5 @@ public class AddRequestParameterGatewayFilterFactory implements GatewayFilterFac } }; } + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java index 4ab3190e..5692bf5b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/AddResponseHeaderGatewayFilterFactory.java @@ -17,32 +17,17 @@ package org.springframework.cloud.gateway.filter.factory; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; -import java.util.Arrays; -import java.util.List; - /** * @author Spencer Gibb */ -public class AddResponseHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class AddResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override - public List argNames() { - return Arrays.asList(NAME_KEY, VALUE_KEY); - } - - @Override - public GatewayFilter apply(Tuple args) { - final String header = args.getString(NAME_KEY); - final String value = args.getString(VALUE_KEY); - return apply(header, value); - } - - public GatewayFilter apply(String header, String value) { + public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { - exchange.getResponse().getHeaders().add(header, value); + exchange.getResponse().getHeaders().add(config.getName(), config.getValue()); return chain.filter(exchange); }; diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java index 73442645..29de7181 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/GatewayFilterFactory.java @@ -18,24 +18,57 @@ package org.springframework.cloud.gateway.filter.factory; import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.cloud.gateway.support.ArgumentHints; +import org.springframework.cloud.gateway.support.ShortcutConfigurable; +import org.springframework.cloud.gateway.support.Configurable; import org.springframework.cloud.gateway.support.NameUtils; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.tuple.Tuple; +import java.util.function.Consumer; + /** * @author Spencer Gibb */ @FunctionalInterface -public interface GatewayFilterFactory extends ArgumentHints { +public interface GatewayFilterFactory extends ShortcutConfigurable, Configurable { String NAME_KEY = "name"; String VALUE_KEY = "value"; + @Deprecated //TODO: remove when apply(Tuple) is removed + default boolean isConfigurable() { + return false; + } + + // useful for javadsl + default GatewayFilter apply(Consumer consumer) { + C config = newConfig(); + consumer.accept(config); + return apply(config); + } + + //TODO: remove after apply(Tuple) removed + @Override + default Class getConfigClass() { + throw new UnsupportedOperationException("getConfigClass() not implemented"); + } + + //TODO: remove after apply(Tuple) removed + @Override + default C newConfig() { + throw new UnsupportedOperationException("newConfig() not implemented"); + } + + //TODO: remove default impl after apply(Tuple) removed + default GatewayFilter apply(C config) { + throw new UnsupportedOperationException("apply(C config) not implemented"); + } + + @Deprecated GatewayFilter apply(Tuple args); default String name() { - return NameUtils.normalizeFilterName(getClass()); + return NameUtils.normalizeFilterFactoryName(getClass()); } @Deprecated diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java index 7351824c..ed1c0d25 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactory.java @@ -26,7 +26,6 @@ import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; @@ -50,60 +49,35 @@ import rx.Subscription; /** * @author Spencer Gibb */ -public class HystrixGatewayFilterFactory implements GatewayFilterFactory { +public class HystrixGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String FALLBACK_URI = "fallbackUri"; private final DispatcherHandler dispatcherHandler; public HystrixGatewayFilterFactory(DispatcherHandler dispatcherHandler) { + super(Config.class); this.dispatcherHandler = dispatcherHandler; } @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(NAME_KEY); } @Override - public boolean validateArgs() { - return false; - } - - @Override - public GatewayFilter apply(Tuple args) { + public GatewayFilter apply(Config config) { //TODO: if no name is supplied, generate one from command id (useful for default filter) - String commandName = args.getString(NAME_KEY); - if (args.hasFieldName(FALLBACK_URI)) { - URI fallbackUri = URI.create(args.getString(FALLBACK_URI)); - if (!"forward".equals(fallbackUri.getScheme())) { - throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found "+ fallbackUri); - } - return apply(commandName, fallbackUri); + if (config.setter == null) { + HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName()); + HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(config.name); + + config.setter = Setter.withGroupKey(groupKey) + .andCommandKey(commandKey); } - return apply(commandName, null); - } - public GatewayFilter apply(String commandName) { - return apply(commandName, null); - } - - public GatewayFilter apply(String commandName, URI fallbackUri) { - final HystrixCommandGroupKey groupKey = HystrixCommandGroupKey.Factory.asKey(getClass().getSimpleName()); - final HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(commandName); - - final Setter setter = Setter.withGroupKey(groupKey) - .andCommandKey(commandKey); - return apply(setter, fallbackUri); - } - - public GatewayFilter apply(Setter setter) { - return apply(setter, null); - } - - public GatewayFilter apply(Setter setter, URI fallbackUri) { return (exchange, chain) -> { - RouteHystrixCommand command = new RouteHystrixCommand(setter, fallbackUri, exchange, chain); + RouteHystrixCommand command = new RouteHystrixCommand(config.setter, config.fallback, exchange, chain); return Mono.create(s -> { Subscription sub = command.toObservable().subscribe(s::success, s::error, s::success); @@ -163,4 +137,40 @@ public class HystrixGatewayFilterFactory implements GatewayFilterFactory { return RxReactiveStreams.toObservable(HystrixGatewayFilterFactory.this.dispatcherHandler.handle(mutated)); } } + + public static class Config { + private String name; + private String fallbackUri; + private Setter setter; + private URI fallback; + + public String getName() { + return name; + } + + public Config setName(String name) { + this.name = name; + return this; + } + + public String getFallbackUri() { + return fallbackUri; + } + + public Config setFallbackUri(String fallbackUri) { + this.fallbackUri = fallbackUri; + if (this.fallbackUri != null) { + fallback = URI.create(fallbackUri); + if (!"forward".equals(fallback.getScheme())) { + throw new IllegalArgumentException("Hystrix Filter currently only supports 'forward' URIs, found " + fallbackUri); + } + } + return this; + } + + public Config setSetter(Setter setter) { + this.setter = setter; + return this; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java index 3045677f..050ceafc 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/PrefixPathGatewayFilterFactory.java @@ -22,9 +22,8 @@ import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.server.reactive.ServerHttpRequest; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; @@ -32,28 +31,27 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a /** * @author Spencer Gibb */ -public class PrefixPathGatewayFilterFactory implements GatewayFilterFactory { +public class PrefixPathGatewayFilterFactory extends AbstractGatewayFilterFactory { private static final Log log = LogFactory.getLog(PrefixPathGatewayFilterFactory.class); public static final String PREFIX_KEY = "prefix"; + public PrefixPathGatewayFilterFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(PREFIX_KEY); } @Override - public GatewayFilter apply(Tuple args) { - final String prefix = args.getString(PREFIX_KEY); - return apply(prefix); - } - - public GatewayFilter apply(String prefix) { + public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest req = exchange.getRequest(); addOriginalRequestUrl(exchange, req.getURI()); - String newPath = prefix + req.getURI().getPath(); + String newPath = config.prefix + req.getURI().getPath(); ServerHttpRequest request = req.mutate() .path(newPath) @@ -62,10 +60,22 @@ public class PrefixPathGatewayFilterFactory implements GatewayFilterFactory { exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, request.getURI()); if (log.isTraceEnabled()) { - log.trace("Prefixed URI with: "+prefix+" -> "+request.getURI()); + log.trace("Prefixed URI with: "+config.prefix+" -> "+request.getURI()); } return chain.filter(exchange.mutate().request(request).build()); }; } + + public static class Config { + private String prefix; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java index 769dc185..3d9254e4 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactory.java @@ -23,12 +23,11 @@ import java.net.URL; import java.util.Arrays; import java.util.List; +import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.tuple.Tuple; import org.springframework.util.Assert; -import org.springframework.cloud.gateway.filter.GatewayFilter; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.parse; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus; @@ -38,21 +37,23 @@ import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public class RedirectToGatewayFilterFactory implements GatewayFilterFactory { +public class RedirectToGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String STATUS_KEY = "status"; public static final String URL_KEY = "url"; + public RedirectToGatewayFilterFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(STATUS_KEY, URL_KEY); } @Override - public GatewayFilter apply(Tuple args) { - String statusString = args.getRawString(STATUS_KEY); - String urlString = args.getString(URL_KEY); - return apply(statusString, urlString); + public GatewayFilter apply(Config config) { + return apply(config.status, config.url); } public GatewayFilter apply(String statusString, String urlString) { @@ -82,4 +83,25 @@ public class RedirectToGatewayFilterFactory implements GatewayFilterFactory { })); } + public static class Config { + String status; + String url; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + } + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java index f732e72f..4e31d84e 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactory.java @@ -17,33 +17,31 @@ package org.springframework.cloud.gateway.filter.factory; -import org.springframework.tuple.Tuple; -import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.http.server.reactive.ServerHttpRequest; - import java.util.Arrays; import java.util.List; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.server.reactive.ServerHttpRequest; + /** * @author Spencer Gibb */ -public class RemoveRequestHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class RemoveRequestHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory { + + public RemoveRequestHeaderGatewayFilterFactory() { + super(NameConfig.class); + } @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(NAME_KEY); } @Override - public GatewayFilter apply(Tuple args) { - final String header = args.getString(NAME_KEY); - return apply(header); - } - - public GatewayFilter apply(String header) { + public GatewayFilter apply(NameConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.remove(header)) + .headers(httpHeaders -> httpHeaders.remove(config.getName())) .build(); return chain.filter(exchange.mutate().request(request).build()); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java index 932ee71e..e676c816 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactory.java @@ -20,7 +20,6 @@ package org.springframework.cloud.gateway.filter.factory; import java.util.Arrays; import java.util.List; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; import reactor.core.publisher.Mono; @@ -28,22 +27,21 @@ import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public class RemoveResponseHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class RemoveResponseHeaderGatewayFilterFactory extends AbstractGatewayFilterFactory { + + public RemoveResponseHeaderGatewayFilterFactory() { + super(NameConfig.class); + } @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(NAME_KEY); } @Override - public GatewayFilter apply(Tuple args) { - final String header = args.getString(NAME_KEY); - return apply(header); - } - - public GatewayFilter apply(String header) { + public GatewayFilter apply(NameConfig config) { return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> { - exchange.getResponse().getHeaders().remove(header); + exchange.getResponse().getHeaders().remove(config.getName()); })); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java index c41893bb..e4362de6 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestRateLimiterGatewayFilterFactory.java @@ -20,22 +20,24 @@ package org.springframework.cloud.gateway.filter.factory; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.http.HttpStatus; -import org.springframework.tuple.Tuple; /** * User Request Rate Limiter filter. See https://stripe.com/blog/rate-limiters and */ -public class RequestRateLimiterGatewayFilterFactory implements GatewayFilterFactory { +public class RequestRateLimiterGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String KEY_RESOLVER_KEY = "keyResolver"; - private final RateLimiter rateLimiter; + private final RateLimiter defaultRateLimiter; private final KeyResolver defaultKeyResolver; - public RequestRateLimiterGatewayFilterFactory(RateLimiter rateLimiter, - KeyResolver defaultKeyResolver) { - this.rateLimiter = rateLimiter; + public RequestRateLimiterGatewayFilterFactory(RateLimiter defaultRateLimiter, + KeyResolver defaultKeyResolver) { + super(Config.class); + this.defaultRateLimiter = defaultRateLimiter; this.defaultKeyResolver = defaultKeyResolver; } @@ -43,32 +45,54 @@ public class RequestRateLimiterGatewayFilterFactory implements GatewayFilterFact return defaultKeyResolver; } - @SuppressWarnings("unchecked") - @Override - public GatewayFilter apply(Tuple args) { - - KeyResolver keyResolver; - if (args.hasFieldName(KEY_RESOLVER_KEY)) { - keyResolver = args.getValue(KEY_RESOLVER_KEY, KeyResolver.class); - } else { - keyResolver = defaultKeyResolver; - } - return apply(keyResolver, args); + public RateLimiter getDefaultRateLimiter() { + return defaultRateLimiter; } - public GatewayFilter apply(KeyResolver keyResolver, Tuple args) { + @SuppressWarnings("unchecked") + @Override + public GatewayFilter apply(Config config) { + KeyResolver resolver = (config.keyResolver == null) ? defaultKeyResolver : config.keyResolver; + RateLimiter limiter = (config.rateLimiter == null) ? defaultRateLimiter : config.rateLimiter; - return (exchange, chain) -> keyResolver.resolve(exchange).flatMap(key -> - // TODO: if key is empty? - rateLimiter.isAllowed(key, args).flatMap(response -> { - // TODO: set some headers for rate, tokens left + return (exchange, chain) -> { + Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR); - if (response.isAllowed()) { - return chain.filter(exchange); - } - exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); - return exchange.getResponse().setComplete(); - })); + return resolver.resolve(exchange).flatMap(key -> + // TODO: if key is empty? + limiter.isAllowed(route.getId(), key).flatMap(response -> { + // TODO: set some headers for rate, tokens left + + if (response.isAllowed()) { + return chain.filter(exchange); + } + exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); + return exchange.getResponse().setComplete(); + })); + }; + } + + public static class Config { + private KeyResolver keyResolver; + private RateLimiter rateLimiter; + + + public KeyResolver getKeyResolver() { + return keyResolver; + } + + public Config setKeyResolver(KeyResolver keyResolver) { + this.keyResolver = keyResolver; + return this; + } + public RateLimiter getRateLimiter() { + return rateLimiter; + } + + public Config setRateLimiter(RateLimiter rateLimiter) { + this.rateLimiter = rateLimiter; + return this; + } } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java index 5d79bd0f..f8d6a867 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactory.java @@ -22,7 +22,6 @@ import java.util.List; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl; @@ -30,29 +29,28 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a /** * @author Spencer Gibb */ -public class RewritePathGatewayFilterFactory implements GatewayFilterFactory { +public class RewritePathGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String REGEXP_KEY = "regexp"; public static final String REPLACEMENT_KEY = "replacement"; + public RewritePathGatewayFilterFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(REGEXP_KEY, REPLACEMENT_KEY); } @Override - public GatewayFilter apply(Tuple args) { - final String regex = args.getString(REGEXP_KEY); - String replacement = args.getString(REPLACEMENT_KEY).replace("$\\", "$"); - return apply(regex, replacement); - } - - public GatewayFilter apply(String regex, String replacement) { + public GatewayFilter apply(Config config) { + String replacement = config.replacement.replace("$\\", "$"); return (exchange, chain) -> { ServerHttpRequest req = exchange.getRequest(); addOriginalRequestUrl(exchange, req.getURI()); String path = req.getURI().getPath(); - String newPath = path.replaceAll(regex, replacement); + String newPath = path.replaceAll(config.regexp, replacement); ServerHttpRequest request = req.mutate() .path(newPath) @@ -63,4 +61,27 @@ public class RewritePathGatewayFilterFactory implements GatewayFilterFactory { return chain.filter(exchange.mutate().request(request).build()); }; } + + public static class Config { + private String regexp; + private String replacement; + + public String getRegexp() { + return regexp; + } + + public Config setRegexp(String regexp) { + this.regexp = regexp; + return this; + } + + public String getReplacement() { + return replacement; + } + + public Config setReplacement(String replacement) { + this.replacement = replacement; + return this; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java index feef3c09..bda6731d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SaveSessionGatewayFilterFactory.java @@ -16,7 +16,6 @@ package org.springframework.cloud.gateway.filter.factory; import org.springframework.cloud.gateway.filter.GatewayFilter; -import org.springframework.tuple.Tuple; import org.springframework.web.server.WebSession; /** @@ -27,10 +26,10 @@ import org.springframework.web.server.WebSession; * * @author Greg Turnquist */ -public class SaveSessionGatewayFilterFactory implements GatewayFilterFactory { +public class SaveSessionGatewayFilterFactory extends AbstractGatewayFilterFactory { @Override - public GatewayFilter apply(Tuple args) { + public GatewayFilter apply(Object config) { return (exchange, chain) -> exchange.getSession() .map(WebSession::save) .then(chain.filter(exchange)); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java index 29c0edab..dc26be77 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SecureHeadersGatewayFilterFactory.java @@ -17,15 +17,14 @@ package org.springframework.cloud.gateway.filter.factory; -import org.springframework.http.HttpHeaders; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.HttpHeaders; /** * https://blog.appcanary.com/2017/http-security-headers.html * @author Spencer Gibb */ -public class SecureHeadersGatewayFilterFactory implements GatewayFilterFactory { +public class SecureHeadersGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String X_XSS_PROTECTION_HEADER = "X-Xss-Protection"; public static final String STRICT_TRANSPORT_SECURITY_HEADER = "Strict-Transport-Security"; @@ -43,7 +42,7 @@ public class SecureHeadersGatewayFilterFactory implements GatewayFilterFactory { } @Override - public GatewayFilter apply(Tuple args) { + public GatewayFilter apply(Object config) { //TODO: allow args to override properties return (exchange, chain) -> { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java index 0d5a8adb..e525fb8c 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactory.java @@ -23,9 +23,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.util.UriTemplate; import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; @@ -36,25 +35,22 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a /** * @author Spencer Gibb */ -public class SetPathGatewayFilterFactory implements GatewayFilterFactory { +public class SetPathGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String TEMPLATE_KEY = "template"; + public SetPathGatewayFilterFactory() { + super(Config.class); + } @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(TEMPLATE_KEY); } @Override - @SuppressWarnings("unchecked") - public GatewayFilter apply(Tuple args) { - String template = args.getString(TEMPLATE_KEY); - return apply(template); - } - - public GatewayFilter apply(String template) { - UriTemplate uriTemplate = new UriTemplate(template); + public GatewayFilter apply(Config config) { + UriTemplate uriTemplate = new UriTemplate(config.template); return (exchange, chain) -> { PathMatchInfo variables = exchange.getAttribute(URI_TEMPLATE_VARIABLES_ATTRIBUTE); @@ -80,4 +76,16 @@ public class SetPathGatewayFilterFactory implements GatewayFilterFactory { return chain.filter(exchange.mutate().request(request).build()); }; } + + public static class Config { + private String template; + + public String getTemplate() { + return template; + } + + public void setTemplate(String template) { + this.template = template; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java index e0becd71..dd7fc0ce 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetRequestHeaderGatewayFilterFactory.java @@ -17,34 +17,19 @@ package org.springframework.cloud.gateway.filter.factory; -import java.util.Arrays; -import java.util.List; - import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; /** * @author Spencer Gibb */ -public class SetRequestHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class SetRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override - public List argNames() { - return Arrays.asList(NAME_KEY, VALUE_KEY); - } - - @Override - public GatewayFilter apply(Tuple args) { - String name = args.getString(NAME_KEY); - String value = args.getString(VALUE_KEY); - return apply(name, value); - } - - public GatewayFilter apply(String name, String value) { + public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() - .headers(httpHeaders -> httpHeaders.set(name, value)) + .headers(httpHeaders -> httpHeaders.set(config.name, config.value)) .build(); return chain.filter(exchange.mutate().request(request).build()); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java index 25b896db..6c74d2df 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactory.java @@ -17,33 +17,19 @@ package org.springframework.cloud.gateway.filter.factory; -import org.springframework.tuple.Tuple; import org.springframework.cloud.gateway.filter.GatewayFilter; -import reactor.core.publisher.Mono; -import java.util.Arrays; -import java.util.List; +import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public class SetResponseHeaderGatewayFilterFactory implements GatewayFilterFactory { +public class SetResponseHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override - public List argNames() { - return Arrays.asList(NAME_KEY, VALUE_KEY); - } - - @Override - public GatewayFilter apply(Tuple args) { - final String header = args.getString(NAME_KEY); - final String value = args.getString(VALUE_KEY); - return apply(header, value); - } - - public GatewayFilter apply(String header, String value) { + public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> { - exchange.getResponse().getHeaders().set(header, value); + exchange.getResponse().getHeaders().set(config.name, config.value); })); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java index c44ee217..c1c22731 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/SetStatusGatewayFilterFactory.java @@ -17,42 +17,36 @@ package org.springframework.cloud.gateway.filter.factory; +import java.util.Arrays; +import java.util.List; + +import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.http.HttpStatus; -import org.springframework.tuple.Tuple; -import org.springframework.cloud.gateway.filter.GatewayFilter; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.setResponseStatus; import reactor.core.publisher.Mono; -import java.util.Arrays; -import java.util.List; - /** * @author Spencer Gibb */ -public class SetStatusGatewayFilterFactory implements GatewayFilterFactory { +public class SetStatusGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String STATUS_KEY = "status"; + public SetStatusGatewayFilterFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(STATUS_KEY); } @Override - public GatewayFilter apply(Tuple args) { - String status = args.getRawString(STATUS_KEY); - return apply(status); - } - - public GatewayFilter apply(String status) { - final HttpStatus httpStatus = ServerWebExchangeUtils.parse(status); - return apply(httpStatus); - } - - public GatewayFilter apply(HttpStatus httpStatus) { + public GatewayFilter apply(Config config) { + final HttpStatus status = ServerWebExchangeUtils.parse(config.status); return (exchange, chain) -> { // option 1 (runs in filter order) @@ -67,10 +61,23 @@ public class SetStatusGatewayFilterFactory implements GatewayFilterFactory { // check not really needed, since it is guarded in setStatusCode, // but it's a good example if (!exchange.getResponse().isCommitted()) { - setResponseStatus(exchange, httpStatus); + setResponseStatus(exchange, status); } })); }; } + public static class Config { + //TODO: relaxed HttpStatus converter + private String status; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + } + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java index 9b82a341..949ec238 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactory.java @@ -19,9 +19,9 @@ package org.springframework.cloud.gateway.filter.factory; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; + import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.tuple.Tuple; import org.springframework.util.StringUtils; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR; @@ -32,28 +32,27 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a * before sending it downstream * @author Ryan Baxter */ -public class StripPrefixGatewayFilterFactory implements GatewayFilterFactory { +public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory { public static final String PARTS_KEY = "parts"; + public StripPrefixGatewayFilterFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(PARTS_KEY); } @Override - public GatewayFilter apply(Tuple args) { - final int parts = args.getInt(PARTS_KEY); - return apply(parts); - } - - public GatewayFilter apply(int parts) { + public GatewayFilter apply(Config config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); addOriginalRequestUrl(exchange, request.getURI()); String path = request.getURI().getRawPath(); String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(path, "/")) - .skip(parts).collect(Collectors.joining("/")); + .skip(config.parts).collect(Collectors.joining("/")); ServerHttpRequest newRequest = request.mutate() .path(newPath) .build(); @@ -63,4 +62,16 @@ public class StripPrefixGatewayFilterFactory implements GatewayFilterFactory { return chain.filter(exchange.mutate().request(newRequest).build()); }; } + + public static class Config { + private int parts; + + public int getParts() { + return parts; + } + + public void setParts(int parts) { + this.parts = parts; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java new file mode 100644 index 00000000..5da0a5de --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/AbstractRateLimiter.java @@ -0,0 +1,76 @@ +/* + * 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.filter.ratelimit; + +import java.util.Map; + +import org.springframework.cloud.gateway.event.FilterArgsEvent; +import org.springframework.cloud.gateway.support.AbstractStatefulConfigurable; +import org.springframework.cloud.gateway.support.ConfigurationUtils; +import org.springframework.context.ApplicationListener; +import org.springframework.core.style.ToStringCreator; +import org.springframework.validation.Validator; + +public abstract class AbstractRateLimiter extends AbstractStatefulConfigurable implements RateLimiter, ApplicationListener { + private String configurationPropertyName; + private final Validator validator; + + protected AbstractRateLimiter(Class configClass, String configurationPropertyName, Validator validator) { + super(configClass); + this.configurationPropertyName = configurationPropertyName; + this.validator = validator; + } + + protected String getConfigurationPropertyName() { + return configurationPropertyName; + } + + protected Validator getValidator() { + return validator; + } + + @Override + public void onApplicationEvent(FilterArgsEvent event) { + Map args = event.getArgs(); + + if (args.isEmpty() || !hasRelevantKey(args)) { + return; + } + + String routeId = event.getRouteId(); + C routeConfig = newConfig(); + ConfigurationUtils.bind(routeConfig, args, + configurationPropertyName, configurationPropertyName, validator); + getConfig().put(routeId, routeConfig); + } + + private boolean hasRelevantKey(Map args) { + return args.keySet().stream() + .anyMatch(key -> key.startsWith(configurationPropertyName + ".")); + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("configurationPropertyName", configurationPropertyName) + .append("config", getConfig()) + .append("configClass", getConfigClass()) + .toString(); + } + +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java index 874b0737..025d44f7 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RateLimiter.java @@ -1,14 +1,15 @@ package org.springframework.cloud.gateway.filter.ratelimit; -import org.springframework.tuple.Tuple; +import org.springframework.cloud.gateway.support.StatefulConfigurable; + import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public interface RateLimiter { +public interface RateLimiter extends StatefulConfigurable { - Mono isAllowed(String id, Tuple args); + Mono isAllowed(String routeId, String id); class Response { private final boolean allowed; diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java index 299ba2b2..cd1a176b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java @@ -5,13 +5,14 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import javax.validation.constraints.Min; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.data.redis.core.ReactiveRedisTemplate; import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.tuple.Tuple; - -import static org.springframework.tuple.TupleBuilder.tuple; +import org.springframework.validation.Validator; +import org.springframework.validation.annotation.Validated; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -22,9 +23,12 @@ import reactor.core.publisher.Mono; * * @author Spencer Gibb */ -public class RedisRateLimiter implements RateLimiter { +public class RedisRateLimiter extends AbstractRateLimiter { + @Deprecated public static final String REPLENISH_RATE_KEY = "replenishRate"; + @Deprecated public static final String BURST_CAPACITY_KEY = "burstCapacity"; + public static final String CONFIGURATION_PROPERTY_NAME = "redis-rate-limiter"; private Log log = LogFactory.getLog(getClass()); @@ -32,36 +36,32 @@ public class RedisRateLimiter implements RateLimiter { private final RedisScript> script; public RedisRateLimiter(ReactiveRedisTemplate redisTemplate, - RedisScript> script) { + RedisScript> script, Validator validator) { + super(Config.class, CONFIGURATION_PROPERTY_NAME, validator); this.redisTemplate = redisTemplate; this.script = script; } - public static Tuple args(int replenishRate, int burstCapacity) { - return tuple().of(REPLENISH_RATE_KEY, replenishRate, BURST_CAPACITY_KEY, burstCapacity); - } - /** * This uses a basic token bucket algorithm and relies on the fact that Redis scripts * execute atomically. No other operations can run between fetching the count and * writing the new count. - * @param id - * @param args - * @return */ @Override @SuppressWarnings("unchecked") - public Mono isAllowed(String id, Tuple args) { + public Mono isAllowed(String routeId, String id) { + + Config routeConfig = getConfig().get(routeId); + + if (routeConfig == null) { + throw new IllegalArgumentException("No Configuration found for route "+ routeId); + } + // How many requests per second do you want a user to be allowed to do? - int replenishRate = args.getInt(REPLENISH_RATE_KEY); + int replenishRate = routeConfig.getReplenishRate(); // How much bursting do you want to allow? - int burstCapacity; - if (args.hasFieldName(BURST_CAPACITY_KEY)) { - burstCapacity = args.getInt(BURST_CAPACITY_KEY); - } else { - burstCapacity = 0; - } + int burstCapacity = routeConfig.getBurstCapacity(); try { // Make a unique key per user. @@ -102,4 +102,39 @@ public class RedisRateLimiter implements RateLimiter { } return Mono.just(new Response(true, -1)); } + + @Validated + public static class Config { + @Min(1) + private int replenishRate; + + @Min(0) + private int burstCapacity = 0; + + public int getReplenishRate() { + return replenishRate; + } + + public Config setReplenishRate(int replenishRate) { + this.replenishRate = replenishRate; + return this; + } + + public int getBurstCapacity() { + return burstCapacity; + } + + public Config setBurstCapacity(int burstCapacity) { + this.burstCapacity = burstCapacity; + return this; + } + + @Override + public String toString() { + return "Config{" + + "replenishRate=" + replenishRate + + ", burstCapacity=" + burstCapacity + + '}'; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java new file mode 100644 index 00000000..a2280394 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AbstractRoutePredicateFactory.java @@ -0,0 +1,42 @@ +/* + * 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.predicate; + +import org.springframework.cloud.gateway.support.AbstractConfigurable; +import org.springframework.tuple.Tuple; +import org.springframework.web.server.ServerWebExchange; + +import java.util.function.Predicate; + +public abstract class AbstractRoutePredicateFactory extends AbstractConfigurable + implements RoutePredicateFactory { + + public AbstractRoutePredicateFactory(Class configClass) { + super(configClass); + } + + @Override + public boolean isConfigurable() { + return true; + } + + @Override + public Predicate apply(Tuple args) { + throw new UnsupportedOperationException("apply(Tuple) not supported"); + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java index 4ca40569..83a6290a 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java @@ -22,33 +22,45 @@ import java.util.Collections; import java.util.List; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.getZonedDateTime; + /** * @author Spencer Gibb */ -public class AfterRoutePredicateFactory implements RoutePredicateFactory { +public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String DATETIME_KEY = "datetime"; + public AfterRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Collections.singletonList(DATETIME_KEY); } @Override - public Predicate apply(Tuple args) { - Object value = args.getValue(DATETIME_KEY); - final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value); - return apply(dateTime); - } - - public Predicate apply(ZonedDateTime dateTime) { + public Predicate apply(Config config) { + ZonedDateTime datetime = getZonedDateTime(config.getDatetime()); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); - return now.isAfter(dateTime); + return now.isAfter(datetime); }; } + public static class Config { + private String datetime; + + public String getDatetime() { + return datetime; + } + + public void setDatetime(String datetime) { + this.datetime = datetime; + } + } + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java index a2e5b87b..03a75c29 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactory.java @@ -22,35 +22,44 @@ import java.util.Collections; import java.util.List; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; import org.springframework.web.server.ServerWebExchange; -import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.parseZonedDateTime; +import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.getZonedDateTime; /** * @author Spencer Gibb */ -public class BeforeRoutePredicateFactory implements RoutePredicateFactory { +public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String DATETIME_KEY = "datetime"; + public BeforeRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Collections.singletonList(DATETIME_KEY); } @Override - public Predicate apply(Tuple args) { - Object value = args.getValue(DATETIME_KEY); - final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value); - return apply(dateTime); - } - - public Predicate apply(ZonedDateTime dateTime) { + public Predicate apply(Config config) { + ZonedDateTime datetime = getZonedDateTime(config.getDatetime()); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); - return now.isBefore(dateTime); + return now.isBefore(datetime); }; } + public static class Config { + private String datetime; + + public String getDatetime() { + return datetime; + } + + public void setDatetime(String datetime) { + this.datetime = datetime; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java index 52f5f057..6955e82d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactory.java @@ -22,36 +22,59 @@ import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; import org.springframework.util.Assert; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class BetweenRoutePredicateFactory implements RoutePredicateFactory { +public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String DATETIME1_KEY = "datetime1"; public static final String DATETIME2_KEY = "datetime2"; - @Override - public Predicate apply(Tuple args) { - //TODO: is ZonedDateTime the right thing to use? - final ZonedDateTime dateTime1 = getZonedDateTime(args.getValue(DATETIME1_KEY)); - final ZonedDateTime dateTime2 = getZonedDateTime(args.getValue(DATETIME2_KEY)); - return apply(dateTime1, dateTime2); + public BetweenRoutePredicateFactory() { + super(Config.class); } - public Predicate apply(ZonedDateTime dateTime1, ZonedDateTime dateTime2) { - Assert.isTrue(dateTime1.isBefore(dateTime2), dateTime1 + - " must be before " + dateTime2); + @Override + public Predicate apply(Config config) { + //TODO: figure out boot conversion + ZonedDateTime datetime1 = getZonedDateTime(config.datetime1); + ZonedDateTime datetime2 = getZonedDateTime(config.datetime2); + Assert.isTrue(datetime1.isBefore(datetime2), + config.datetime1 + + " must be before " + config.datetime2); return exchange -> { final ZonedDateTime now = ZonedDateTime.now(); - return now.isAfter(dateTime1) && now.isBefore(dateTime2); + return now.isAfter(datetime1) && now.isBefore(datetime2); }; } + public static class Config { + private String datetime1; + private String datetime2; + + public String getDatetime1() { + return datetime1; + } + + public Config setDatetime1(String datetime1) { + this.datetime1 = datetime1; + return this; + } + + public String getDatetime2() { + return datetime2; + } + + public Config setDatetime2(String datetime2) { + this.datetime2 = datetime2; + return this; + } + } + public static ZonedDateTime getZonedDateTime(Object value) { ZonedDateTime dateTime; if (value instanceof ZonedDateTime) { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java index b40c8dd3..e40f257d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/CookieRoutePredicateFactory.java @@ -21,39 +21,66 @@ import java.util.Arrays; import java.util.List; import java.util.function.Predicate; +import javax.validation.constraints.NotEmpty; + import org.springframework.http.HttpCookie; -import org.springframework.tuple.Tuple; +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class CookieRoutePredicateFactory implements RoutePredicateFactory { +public class CookieRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String NAME_KEY = "name"; public static final String REGEXP_KEY = "regexp"; + public CookieRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(NAME_KEY, REGEXP_KEY); } @Override - public Predicate apply(Tuple args) { - String name = args.getString(NAME_KEY); - String regexp = args.getString(REGEXP_KEY); - return apply(name, regexp); - } - - public Predicate apply(String name, String regexp) { + public Predicate apply(Config config) { return exchange -> { - List cookies = exchange.getRequest().getCookies().get(name); + List cookies = exchange.getRequest().getCookies().get(config.name); for (HttpCookie cookie : cookies) { - if (cookie.getValue().matches(regexp)) { + if (cookie.getValue().matches(config.regexp)) { return true; } } return false; }; } + + @Validated + public static class Config { + + @NotEmpty + private String name; + @NotEmpty + private String regexp; + + public String getName() { + return name; + } + + public Config setName(String name) { + this.name = name; + return this; + } + + public String getRegexp() { + return regexp; + } + + public Config setRegexp(String regexp) { + this.regexp = regexp; + return this; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java index 40943ab2..7ee02f4a 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactory.java @@ -21,39 +21,35 @@ import java.util.Arrays; import java.util.List; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; +import javax.validation.constraints.NotEmpty; + +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class HeaderRoutePredicateFactory implements RoutePredicateFactory { +public class HeaderRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String HEADER_KEY = "header"; public static final String REGEXP_KEY = "regexp"; + public HeaderRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(HEADER_KEY, REGEXP_KEY); } @Override - public Predicate apply(Tuple args) { - String header = args.getString(HEADER_KEY); - String regexp = args.getString(REGEXP_KEY); - return apply(header, regexp); - } - - public Predicate apply(String header) { - return apply(header, ".*"); - } - - public Predicate apply(String header, String regexp) { + public Predicate apply(Config config) { return exchange -> { - List values = exchange.getRequest().getHeaders().get(header); + List values = exchange.getRequest().getHeaders().get(config.header); if (values != null) { for (String value : values) { - if (value.matches(regexp)) { + if (value.matches(config.regexp)) { return true; } } @@ -61,4 +57,29 @@ public class HeaderRoutePredicateFactory implements RoutePredicateFactory { return false; }; } + + @Validated + public static class Config { + @NotEmpty + private String header; + private String regexp; + + public String getHeader() { + return header; + } + + public Config setHeader(String header) { + this.header = header; + return this; + } + + public String getRegexp() { + return regexp; + } + + public Config setRegexp(String regexp) { + this.regexp = regexp; + return this; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java index f97e6e10..df9a5971 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactory.java @@ -21,37 +21,58 @@ import java.util.Collections; import java.util.List; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; +import org.springframework.core.style.ToStringCreator; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class HostRoutePredicateFactory implements RoutePredicateFactory { +public class HostRoutePredicateFactory extends AbstractRoutePredicateFactory { private PathMatcher pathMatcher = new AntPathMatcher("."); + public HostRoutePredicateFactory() { + super(Config.class); + } + public void setPathMatcher(PathMatcher pathMatcher) { this.pathMatcher = pathMatcher; } @Override - public List argNames() { + public List shortcutFieldOrder() { return Collections.singletonList(PATTERN_KEY); } @Override - public Predicate apply(Tuple args) { - String pattern = args.getString(PATTERN_KEY); - return apply(pattern); - } - - public Predicate apply(String pattern) { + public Predicate apply(Config config) { return exchange -> { String host = exchange.getRequest().getHeaders().getFirst("Host"); - return this.pathMatcher.match(pattern, host); + return this.pathMatcher.match(config.getPattern(), host); }; } + + @Validated + public static class Config { + private String pattern; + + public String getPattern() { + return pattern; + } + + public Config setPattern(String pattern) { + this.pattern = pattern; + return this; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("pattern", pattern) + .toString(); + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java index 771214b2..0f1430fe 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactory.java @@ -22,36 +22,41 @@ import java.util.List; import java.util.function.Predicate; import org.springframework.http.HttpMethod; -import org.springframework.tuple.Tuple; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class MethodRoutePredicateFactory implements RoutePredicateFactory { +public class MethodRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String METHOD_KEY = "method"; + public MethodRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(METHOD_KEY); } @Override - public Predicate apply(Tuple args) { - String method = args.getString(METHOD_KEY); - return apply(method); - } - - public Predicate apply(String method) { - HttpMethod httpMethod = HttpMethod.resolve(method); - return apply(httpMethod); - } - - public Predicate apply(HttpMethod httpMethod) { + public Predicate apply(Config config) { return exchange -> { HttpMethod requestMethod = exchange.getRequest().getMethod(); - return requestMethod == httpMethod; + return requestMethod == config.getMethod(); }; } + + public static class Config { + private HttpMethod method; + + public HttpMethod getMethod() { + return method; + } + + public void setMethod(HttpMethod method) { + this.method = method; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java index 1f7419a7..507ec2d9 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactory.java @@ -24,8 +24,9 @@ import java.util.function.Predicate; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.core.style.ToStringCreator; import org.springframework.http.server.PathContainer; -import org.springframework.tuple.Tuple; +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.pattern.PathPattern; import org.springframework.web.util.pattern.PathPattern.PathMatchInfo; @@ -37,43 +38,39 @@ import static org.springframework.http.server.PathContainer.parsePath; /** * @author Spencer Gibb */ -public class PathRoutePredicateFactory implements RoutePredicateFactory { +public class PathRoutePredicateFactory extends AbstractRoutePredicateFactory { private static final Log log = LogFactory.getLog(RoutePredicateFactory.class); private PathPatternParser pathPatternParser = new PathPatternParser(); + public PathRoutePredicateFactory() { + super(Config.class); + } + public void setPathPatternParser(PathPatternParser pathPatternParser) { this.pathPatternParser = pathPatternParser; } @Override - public List argNames() { + public List shortcutFieldOrder() { return Collections.singletonList(PATTERN_KEY); } @Override - public Predicate apply(Tuple args) { - String unparsedPattern = args.getString(PATTERN_KEY); - return apply(unparsedPattern); - } - - public Predicate apply(String unparsedPattern) { - PathPattern pattern; + public Predicate apply(Config config) { synchronized (this.pathPatternParser) { - pattern = this.pathPatternParser.parse(unparsedPattern); + config.pathPattern = this.pathPatternParser.parse(config.pattern); } - return exchange -> { PathContainer path = parsePath(exchange.getRequest().getURI().getPath()); - boolean match = pattern.matches(path); - traceMatch("Pattern", pattern.getPatternString(), path, match); + boolean match = config.pathPattern.matches(path); + traceMatch("Pattern", config.pathPattern.getPatternString(), path, match); if (match) { - PathMatchInfo uriTemplateVariables = pattern.matchAndExtract(path); + PathMatchInfo uriTemplateVariables = config.pathPattern.matchAndExtract(path); exchange.getAttributes().put(URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables); return true; - } - else { + } else { return false; } }; @@ -87,4 +84,27 @@ public class PathRoutePredicateFactory implements RoutePredicateFactory { } } + @Validated + public static class Config { + private String pattern; + private PathPattern pathPattern; + + public String getPattern() { + return pattern; + } + + public Config setPattern(String pattern) { + this.pattern = pattern; + return this; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("pattern", pattern) + .toString(); + } + } + + } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java index cb5bbe19..ff2c5c69 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/QueryRoutePredicateFactory.java @@ -21,58 +21,76 @@ import java.util.Arrays; import java.util.List; import java.util.function.Predicate; -import org.springframework.tuple.Tuple; +import javax.validation.constraints.NotEmpty; + import org.springframework.util.StringUtils; +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; /** * @author Spencer Gibb */ -public class QueryRoutePredicateFactory implements RoutePredicateFactory { +public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory { public static final String PARAM_KEY = "param"; public static final String REGEXP_KEY = "regexp"; + public QueryRoutePredicateFactory() { + super(Config.class); + } + @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList(PARAM_KEY, REGEXP_KEY); } @Override - public boolean validateArgs() { + public boolean validateFieldsExist() { return false; } @Override - public Predicate apply(Tuple args) { - validateMin(1, args); - String param = args.getString(PARAM_KEY); - - final String regexp; - if (args.hasFieldName(REGEXP_KEY)) { - regexp = args.getString(REGEXP_KEY); - } else { - regexp = null; - } - return apply(param, regexp); - } - - public Predicate apply(String param, String regexp) { - + public Predicate apply(Config config) { return exchange -> { - if (!StringUtils.hasText(regexp)) { + if (!StringUtils.hasText(config.regexp)) { // check existence of header - return exchange.getRequest().getQueryParams().containsKey(param); + return exchange.getRequest().getQueryParams().containsKey(config.param); } - List values = exchange.getRequest().getQueryParams().get(param); + List values = exchange.getRequest().getQueryParams().get(config.param); for (String value : values) { - if (value.matches(regexp)) { + if (value.matches(config.regexp)) { return true; } } return false; }; } + + @Validated + public static class Config { + @NotEmpty + private String param; + + private String regexp; + + public String getParam() { + return param; + } + + public Config setParam(String param) { + this.param = param; + return this; + } + + public String getRegexp() { + return regexp; + } + + public Config setRegexp(String regexp) { + this.regexp = regexp; + return this; + } + } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java index 7d924f16..f8cd892f 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactory.java @@ -19,49 +19,58 @@ package org.springframework.cloud.gateway.handler.predicate; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Predicate; +import javax.validation.constraints.NotEmpty; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.tuple.Tuple; -import org.springframework.util.Assert; +import org.jetbrains.annotations.NotNull; +import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType.GATHER_LIST; + import io.netty.handler.ipfilter.IpFilterRuleType; import io.netty.handler.ipfilter.IpSubnetFilterRule; /** * @author Spencer Gibb */ -public class RemoteAddrRoutePredicateFactory implements RoutePredicateFactory { +public class RemoteAddrRoutePredicateFactory extends AbstractRoutePredicateFactory { private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class); + public RemoteAddrRoutePredicateFactory() { + super(Config.class); + } + @Override - public Predicate apply(Tuple args) { - validateMin(1, args); - - List sources = new ArrayList<>(); - if (args != null) { - for (Object arg : args.getValues()) { - addSource(sources, (String) arg); - } - } - return apply(sources); + public ShortcutType shortcutType() { + return GATHER_LIST; } - public Predicate apply(String... addrs) { - Assert.notEmpty(addrs, "addrs must not be empty"); - - List sources = new ArrayList<>(); - for (String addr : addrs) { - addSource(sources, addr); - } - return apply(sources); + @Override + public List shortcutFieldOrder() { + return Collections.singletonList("sources"); } - public Predicate apply(List sources) { + @NotNull + private List convert(List values) { + List sources = new ArrayList<>(); + for (String arg : values) { + addSource(sources, arg); + } + return sources; + } + + @Override + public Predicate apply(Config config) { + List sources = convert(config.sources); + return exchange -> { InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress(); if (remoteAddress != null) { @@ -94,4 +103,25 @@ public class RemoteAddrRoutePredicateFactory implements RoutePredicateFactory { sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT)); } + + @Validated + public static class Config { + @NotEmpty + private List sources = new ArrayList<>(); + + public List getSources() { + return sources; + } + + public Config setSources(List sources) { + this.sources = sources; + return this; + } + + public Config setSources(String... sources) { + this.sources = Arrays.asList(sources); + return this; + } + + } } 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 7fc39888..54eee0f1 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 @@ -17,10 +17,12 @@ package org.springframework.cloud.gateway.handler.predicate; +import java.util.function.Consumer; import java.util.function.Predicate; -import org.springframework.cloud.gateway.support.ArgumentHints; +import org.springframework.cloud.gateway.support.Configurable; import org.springframework.cloud.gateway.support.NameUtils; +import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.tuple.Tuple; import org.springframework.web.server.ServerWebExchange; @@ -28,13 +30,43 @@ import org.springframework.web.server.ServerWebExchange; * @author Spencer Gibb */ @FunctionalInterface -public interface RoutePredicateFactory extends ArgumentHints { +public interface RoutePredicateFactory extends ShortcutConfigurable, Configurable { String PATTERN_KEY = "pattern"; + @Deprecated //TODO: remove when apply(Tuple) is removed + default boolean isConfigurable() { + return false; + } + + // useful for javadsl + default Predicate apply(Consumer consumer) { + C config = newConfig(); + consumer.accept(config); + return apply(config); + } + + //TODO: remove after apply(Tuple) removed + @Override + default Class getConfigClass() { + throw new UnsupportedOperationException("getConfigClass() not implemented"); + } + + //TODO: remove after apply(Tuple) removed + @Override + default C newConfig() { + throw new UnsupportedOperationException("newConfig() not implemented"); + } + + //TODO: remove default impl after apply(Tuple) removed + default Predicate apply(C config) { + throw new UnsupportedOperationException("apply(C config) not implemented"); + } + + @Deprecated Predicate apply(Tuple args); default String name() { - return NameUtils.normalizePredicateName(getClass()); + 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 94d6e90d..d2e3aa3f 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 @@ -84,6 +84,10 @@ public class Route implements Ordered { return this; } + public String getId() { + return id; + } + public Builder order(int order) { this.order = order; return this; 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 5076b47f..0710f483 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 @@ -30,32 +30,37 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.event.FilterArgsEvent; +import org.springframework.cloud.gateway.event.PredicateArgsEvent; 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.predicate.PredicateDefinition; import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory; -import org.springframework.cloud.gateway.support.ArgumentHints; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.cloud.gateway.support.ConfigurationUtils; +import org.springframework.cloud.gateway.support.ShortcutConfigurable; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.expression.Expression; -import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.tuple.Tuple; import org.springframework.tuple.TupleBuilder; +import org.springframework.validation.Validator; import org.springframework.web.server.ServerWebExchange; +import static org.springframework.cloud.gateway.support.ShortcutConfigurable.getValue; +import static org.springframework.cloud.gateway.support.ShortcutConfigurable.normalizeKey; + import reactor.core.publisher.Flux; /** * {@link RouteLocator} that loads routes from a {@link RouteDefinitionLocator} * @author Spencer Gibb */ -public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware { +public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAware, ApplicationEventPublisherAware { protected final Log logger = LogFactory.getLog(getClass()); private final RouteDefinitionLocator routeDefinitionLocator; @@ -64,6 +69,7 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa private final GatewayProperties gatewayProperties; private final SpelExpressionParser parser = new SpelExpressionParser(); private BeanFactory beanFactory; + private ApplicationEventPublisher publisher; public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List predicates, @@ -75,11 +81,19 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa this.gatewayProperties = gatewayProperties; } + @Autowired + private Validator validator; + @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } + @Override + public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + private void initFactories(List predicates) { predicates.forEach(factory -> { String key = factory.name(); @@ -124,21 +138,36 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa .build(); } + @SuppressWarnings("unchecked") private List loadGatewayFilters(String id, List filterDefinitions) { List filters = filterDefinitions.stream() .map(definition -> { - GatewayFilterFactory filter = this.gatewayFilterFactories.get(definition.getName()); - if (filter == null) { - throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName()); + GatewayFilterFactory factory = this.gatewayFilterFactories.get(definition.getName()); + if (factory == null) { + throw new IllegalArgumentException("Unable to find GatewayFilterFactory with name " + definition.getName()); } Map args = definition.getArgs(); if (logger.isDebugEnabled()) { logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName()); } - Tuple tuple = getTuple(filter, args, this.parser, this.beanFactory); + if (factory.isConfigurable()) { + Map properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory); - return filter.apply(tuple); + Object configuration = factory.newConfig(); + + ConfigurationUtils.bind(configuration, properties, + "", definition.getName(), validator); + + GatewayFilter gatewayFilter = factory.apply(configuration); + if (this.publisher != null) { + this.publisher.publishEvent(new FilterArgsEvent(this, id, properties)); + } + return gatewayFilter; + } else { + Tuple tuple = getTuple(factory, args, this.parser, this.beanFactory); + return factory.apply(tuple); + } }) .collect(Collectors.toList()); @@ -150,14 +179,15 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa return ordered; } - //TODO: make argument resolving a strategy - /* for testing */ static Tuple getTuple(ArgumentHints hasArguments, Map args, SpelExpressionParser parser, BeanFactory beanFactory) { + @SuppressWarnings("Duplicates") + @Deprecated //TODO: remove after Tuple is removed + /* for testing */ static Tuple getTuple(ShortcutConfigurable shortcutConf, Map args, SpelExpressionParser parser, BeanFactory beanFactory) { TupleBuilder builder = TupleBuilder.tuple(); - List argNames = hasArguments.argNames(); + List argNames = shortcutConf.shortcutFieldOrder(); if (!argNames.isEmpty()) { // ensure size is the same for key replacement later - if (hasArguments.validateArgs() && args.size() != argNames.size()) { + if (shortcutConf.validateFieldsExist() && args.size() != argNames.size()) { throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames + " " + argNames + ". Found " + args.size() + " " + args + "'"); } @@ -165,29 +195,8 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa int entryIdx = 0; for (Map.Entry entry : args.entrySet()) { - String key = entry.getKey(); - - // RoutePredicateFactory has name hints and this has a fake key name - // replace with the matching key hint - if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argNames.isEmpty() - && entryIdx < args.size()) { - key = argNames.get(entryIdx); - } - - Object value; - String rawValue = entry.getValue(); - if (rawValue != null) { - rawValue = rawValue.trim(); - } - if (rawValue != null && rawValue.startsWith("#{") && entry.getValue().endsWith("}")) { - // assume it's spel - StandardEvaluationContext context = new StandardEvaluationContext(); - context.setBeanResolver(new BeanFactoryResolver(beanFactory)); - Expression expression = parser.parseExpression(entry.getValue(), new TemplateParserContext()); - value = expression.getValue(context); - } else { - value = entry.getValue(); - } + String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args); + Object value = getValue(parser, beanFactory, entry.getValue()); builder.put(key, value); entryIdx++; @@ -195,7 +204,7 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa Tuple tuple = builder.build(); - if (hasArguments.validateArgs()) { + if (shortcutConf.validateFieldsExist()) { for (String name : argNames) { if (!tuple.hasFieldName(name)) { throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple); @@ -234,20 +243,30 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa return predicate; } - private Predicate lookup(RouteDefinition routeDefinition, PredicateDefinition predicate) { - RoutePredicateFactory found = this.predicates.get(predicate.getName()); - if (found == null) { - throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName()); + @SuppressWarnings("unchecked") + private Predicate 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()); } Map args = predicate.getArgs(); if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition " + routeDefinition.getId() + " applying " + logger.debug("RouteDefinition " + route.getId() + " applying " + args + " to " + predicate.getName()); } - Tuple tuple = getTuple(found, args, this.parser, this.beanFactory); - - return found.apply(tuple); + if (!factory.isConfigurable()) { + Tuple tuple = getTuple(factory, args, this.parser, this.beanFactory); + return factory.apply(tuple); + } else { + Map properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory); + Object config = factory.newConfig(); + ConfigurationUtils.bind(config, properties, + "", predicate.getName(), validator); + if (this.publisher != null) { + this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties)); + } + return factory.apply(config); + } } - } 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 31382e72..ba5c894f 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 @@ -18,16 +18,11 @@ package org.springframework.cloud.gateway.route.builder; import java.net.URI; import java.net.URL; -import java.util.Arrays; import java.util.Collection; -import java.util.List; +import java.util.function.Consumer; -import com.netflix.hystrix.HystrixObservableCommand; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import reactor.retry.Repeat; - -import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.OrderedGatewayFilter; import org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory; @@ -49,26 +44,23 @@ import org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayF import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory; -import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; +import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.route.Route; import org.springframework.core.Ordered; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.springframework.tuple.Tuple; import org.springframework.web.server.ServerWebExchange; -import static org.springframework.tuple.TupleBuilder.tuple; +import reactor.retry.Repeat; public class GatewayFilterSpec extends UriSpec { private static final Log log = LogFactory.getLog(GatewayFilterSpec.class); - static final Tuple EMPTY_TUPLE = tuple().build(); - public GatewayFilterSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { super(routeBuilder, builder); } - + public GatewayFilterSpec filter(GatewayFilter gatewayFilter) { if (gatewayFilter instanceof Ordered) { this.routeBuilder.filter(gatewayFilter); @@ -99,35 +91,28 @@ public class GatewayFilterSpec extends UriSpec { } public GatewayFilterSpec addRequestHeader(String headerName, String headerValue) { - return filter(getBean(AddRequestHeaderGatewayFilterFactory.class).apply(headerName, headerValue)); + return filter(getBean(AddRequestHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName).setValue(headerValue))); } public GatewayFilterSpec addRequestParameter(String param, String value) { - return filter(getBean(AddRequestParameterGatewayFilterFactory.class).apply(param, value)); + return filter(getBean(AddRequestParameterGatewayFilterFactory.class) + .apply(c -> c.setName(param).setValue(value))); } public GatewayFilterSpec addResponseHeader(String headerName, String headerValue) { - return filter(getBean(AddResponseHeaderGatewayFilterFactory.class).apply(headerName, headerValue)); + return filter(getBean(AddResponseHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName).setValue(headerValue))); } - public GatewayFilterSpec hystrix(String commandName) { - return filter(getBean(HystrixGatewayFilterFactory.class).apply(commandName)); - } - - public GatewayFilterSpec hystrix(HystrixObservableCommand.Setter setter) { - return filter(getBean(HystrixGatewayFilterFactory.class).apply(setter)); - } - - public GatewayFilterSpec hystrix(String commandName, URI fallbackUri) { - return filter(getBean(HystrixGatewayFilterFactory.class).apply(commandName, fallbackUri)); - } - - public GatewayFilterSpec hystrix(HystrixObservableCommand.Setter setter, URI fallbackUri) { - return filter(getBean(HystrixGatewayFilterFactory.class).apply(setter, fallbackUri)); + public GatewayFilterSpec hystrix(Consumer configConsumer) { + return filter(getBean(HystrixGatewayFilterFactory.class) + .apply(configConsumer)); } public GatewayFilterSpec prefixPath(String prefix) { - return filter(getBean(PrefixPathGatewayFilterFactory.class).apply(prefix)); + return filter(getBean(PrefixPathGatewayFilterFactory.class) + .apply(c -> c.setPrefix(prefix))); } public GatewayFilterSpec preserveHostHeader() { @@ -155,26 +140,54 @@ public class GatewayFilterSpec extends UriSpec { } public GatewayFilterSpec removeRequestHeader(String headerName) { - return filter(getBean(RemoveRequestHeaderGatewayFilterFactory.class).apply(headerName)); + return filter(getBean(RemoveRequestHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName))); } public GatewayFilterSpec removeResponseHeader(String headerName) { - return filter(getBean(RemoveResponseHeaderGatewayFilterFactory.class).apply(headerName)); + return filter(getBean(RemoveResponseHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName))); } - public GatewayFilterSpec requestRateLimiter(Tuple args) { - RequestRateLimiterGatewayFilterFactory factory = getBean(RequestRateLimiterGatewayFilterFactory.class); - KeyResolver keyResolver; - try { - keyResolver = getBean(KeyResolver.class); - } catch (NoSuchBeanDefinitionException e) { - keyResolver = factory.getDefaultKeyResolver(); + // public GatewayFilterSpec requestRateLimiter() { + // return filter(getBean(RequestRateLimiterGatewayFilterFactory.class).apply(config -> {})); + // } + + public RequestRateLimiterSpec requestRateLimiter() { + return new RequestRateLimiterSpec(getBean(RequestRateLimiterGatewayFilterFactory.class)); + } + + public class RequestRateLimiterSpec { + private final RequestRateLimiterGatewayFilterFactory filter; + + public RequestRateLimiterSpec(RequestRateLimiterGatewayFilterFactory filter) { + this.filter = filter; } - return filter(factory.apply(keyResolver, args)); + + public > RequestRateLimiterSpec rateLimiter(Class rateLimiterType, + Consumer configConsumer) { + R rateLimiter = getBean(rateLimiterType); + C config = rateLimiter.newConfig(); + configConsumer.accept(config); + rateLimiter.getConfig().put(routeBuilder.getId(), config); + return this; + } + + public GatewayFilterSpec configure(Consumer configConsumer) { + filter(this.filter.apply(configConsumer)); + return GatewayFilterSpec.this; + } + + // useful when nothing to configure + public GatewayFilterSpec and() { + return configure(config -> {}); + } + } public GatewayFilterSpec rewritePath(String regex, String replacement) { - return filter(getBean(RewritePathGatewayFilterFactory.class).apply(regex, replacement)); + return filter(getBean(RewritePathGatewayFilterFactory.class) + .apply(c -> c.setRegexp(regex).setReplacement(replacement))); } /** @@ -208,38 +221,47 @@ public class GatewayFilterSpec extends UriSpec { } public GatewayFilterSpec secureHeaders() { - return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(EMPTY_TUPLE)); + return filter(getBean(SecureHeadersGatewayFilterFactory.class).apply(c -> {})); } public GatewayFilterSpec setPath(String template) { - return filter(getBean(SetPathGatewayFilterFactory.class).apply(template)); + return filter(getBean(SetPathGatewayFilterFactory.class) + .apply(c -> c.setTemplate(template))); } public GatewayFilterSpec setRequestHeader(String headerName, String headerValue) { - return filter(getBean(SetRequestHeaderGatewayFilterFactory.class).apply(headerName, headerValue)); + return filter(getBean(SetRequestHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName).setValue(headerValue))); } public GatewayFilterSpec setResponseHeader(String headerName, String headerValue) { - return filter(getBean(SetResponseHeaderGatewayFilterFactory.class).apply(headerName, headerValue)); + return filter(getBean(SetResponseHeaderGatewayFilterFactory.class) + .apply(c -> c.setName(headerName).setValue(headerValue))); } public GatewayFilterSpec setStatus(int status) { return setStatus(String.valueOf(status)); } - public GatewayFilterSpec setStatus(String status) { - return filter(getBean(SetStatusGatewayFilterFactory.class).apply(status)); + public GatewayFilterSpec setStatus(HttpStatus status) { + return setStatus(status.toString()); } - public GatewayFilterSpec setStatus(HttpStatus status) { - return filter(getBean(SetStatusGatewayFilterFactory.class).apply(status)); + public GatewayFilterSpec setStatus(String status) { + return filter(getBean(SetStatusGatewayFilterFactory.class) + .apply(c -> c.setStatus(status))); } public GatewayFilterSpec saveSession() { - return filter(getBean(SaveSessionGatewayFilterFactory.class).apply(EMPTY_TUPLE)); + return filter(getBean(SaveSessionGatewayFilterFactory.class).apply(c -> {})); } public GatewayFilterSpec stripPrefix(int parts) { - return filter(getBean(StripPrefixGatewayFilterFactory.class).apply(parts)); + return filter(getBean(StripPrefixGatewayFilterFactory.class) + .apply(c -> c.setParts(parts))); + } + + private String routeId() { + return routeBuilder.getId(); } } 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 e1d8f494..e404196e 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 @@ -16,6 +16,9 @@ package org.springframework.cloud.gateway.route.builder; +import java.time.ZonedDateTime; +import java.util.function.Predicate; + import org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory; @@ -30,9 +33,6 @@ import org.springframework.cloud.gateway.route.Route; import org.springframework.http.HttpMethod; import org.springframework.web.server.ServerWebExchange; -import java.time.ZonedDateTime; -import java.util.function.Predicate; - public class PredicateSpec extends UriSpec { PredicateSpec(Route.Builder routeBuilder, RouteLocatorBuilder.Builder builder) { @@ -54,55 +54,67 @@ public class PredicateSpec extends UriSpec { } public BooleanSpec after(ZonedDateTime datetime) { - return predicate(getBean(AfterRoutePredicateFactory.class).apply(datetime)); + return predicate(getBean(AfterRoutePredicateFactory.class) + .apply(c-> c.setDatetime(datetime.toString()))); } public BooleanSpec before(ZonedDateTime datetime) { - return predicate(getBean(BeforeRoutePredicateFactory.class).apply(datetime)); + return predicate(getBean(BeforeRoutePredicateFactory.class).apply(c -> c.setDatetime(datetime.toString()))); } public BooleanSpec between(ZonedDateTime datetime1, ZonedDateTime datetime2) { - return predicate(getBean(BetweenRoutePredicateFactory.class).apply(datetime1, datetime2)); + return predicate(getBean(BetweenRoutePredicateFactory.class) + .apply(c -> c.setDatetime1(datetime1.toString()).setDatetime2(datetime2.toString()))); } public BooleanSpec cookie(String name, String regex) { - return predicate(getBean(CookieRoutePredicateFactory.class).apply(name, regex)); + return predicate(getBean(CookieRoutePredicateFactory.class) + .apply(c -> c.setName(name).setRegexp(regex))); } public BooleanSpec header(String header) { - return predicate(getBean(HeaderRoutePredicateFactory.class).apply(header)); + return predicate(getBean(HeaderRoutePredicateFactory.class) + .apply(c -> c.setHeader(header))); //TODO: default regexp } public BooleanSpec header(String header, String regex) { - return predicate(getBean(HeaderRoutePredicateFactory.class).apply(header, regex)); + return predicate(getBean(HeaderRoutePredicateFactory.class) + .apply(c -> c.setHeader(header).setRegexp(regex))); } public BooleanSpec host(String pattern) { - return predicate(getBean(HostRoutePredicateFactory.class).apply(pattern)); + return predicate(getBean(HostRoutePredicateFactory.class) + .apply(c-> c.setPattern(pattern))); } public BooleanSpec method(String method) { - return predicate(getBean(MethodRoutePredicateFactory.class).apply(method)); + return predicate(getBean(MethodRoutePredicateFactory.class) + .apply(c -> c.setMethod(HttpMethod.resolve(method)))); } public BooleanSpec method(HttpMethod method) { - return predicate(getBean(MethodRoutePredicateFactory.class).apply(method)); + return predicate(getBean(MethodRoutePredicateFactory.class) + .apply(c -> c.setMethod(method))); } public BooleanSpec path(String pattern) { - return predicate(getBean(PathRoutePredicateFactory.class).apply(pattern)); + return predicate(getBean(PathRoutePredicateFactory.class) + .apply(c -> c.setPattern(pattern))); } public BooleanSpec query(String param, String regex) { - return predicate(getBean(QueryRoutePredicateFactory.class).apply(param, regex)); + return predicate(getBean(QueryRoutePredicateFactory.class) + .apply(c -> c.setParam(param).setRegexp(regex))); } public BooleanSpec query(String param) { - return predicate(getBean(QueryRoutePredicateFactory.class).apply(param, null)); + return predicate(getBean(QueryRoutePredicateFactory.class) + .apply(c -> c.setParam(param))); } public BooleanSpec remoteAddr(String... addrs) { - return predicate(getBean(RemoteAddrRoutePredicateFactory.class).apply(addrs)); + return predicate(getBean(RemoteAddrRoutePredicateFactory.class) + .apply(c -> c.setSources(addrs))); } public BooleanSpec alwaysTrue() { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java new file mode 100644 index 00000000..5a6b0c12 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractConfigurable.java @@ -0,0 +1,45 @@ +/* + * 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.support; + +import org.springframework.beans.BeanUtils; +import org.springframework.core.style.ToStringCreator; + +public abstract class AbstractConfigurable implements Configurable { + private Class configClass; + + protected AbstractConfigurable(Class configClass) { + this.configClass = configClass; + } + + public Class getConfigClass() { + return configClass; + } + + @Override + public C newConfig() { + return BeanUtils.instantiateClass(this.configClass); + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("configClass", configClass) + .toString(); + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java new file mode 100644 index 00000000..88c6348b --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/AbstractStatefulConfigurable.java @@ -0,0 +1,44 @@ +/* + * 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.support; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.core.style.ToStringCreator; + +public abstract class AbstractStatefulConfigurable extends AbstractConfigurable implements StatefulConfigurable { + private Map config = new HashMap<>(); + + protected AbstractStatefulConfigurable(Class configClass) { + super(configClass); + } + + @Override + public Map getConfig() { + return this.config; + } + + @Override + public String toString() { + return new ToStringCreator(this) + .append("config", config) + .append("configClass", getConfigClass()) + .toString(); + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ArgumentHints.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ArgumentHints.java index 852464c7..47df585b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ArgumentHints.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ArgumentHints.java @@ -20,20 +20,21 @@ package org.springframework.cloud.gateway.support; import org.springframework.tuple.Tuple; import org.springframework.util.Assert; -import java.util.Collections; import java.util.List; /** * @author Spencer Gibb + * @deprecated Use {@link ShortcutConfigurable} instead */ -public interface ArgumentHints { +@Deprecated +public interface ArgumentHints extends ShortcutConfigurable { /** * Returns hints about the number of args and the order for shortcut parsing. * @return */ default List argNames() { - return Collections.emptyList(); + return shortcutFieldOrder(); } /** @@ -42,14 +43,16 @@ public interface ArgumentHints { * @return */ default boolean validateArgs() { - return true; + return validateFieldsExist(); } + @Deprecated default void validate(int requiredSize, Tuple args) { Assert.isTrue(args != null && args.size() == requiredSize, "args must have "+ requiredSize +" entry(s)"); } + @Deprecated default void validateMin(int minSize, Tuple args) { Assert.isTrue(args != null && args.size() >= minSize, "args must have at least "+ minSize +" entry(s)"); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/Configurable.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/Configurable.java new file mode 100644 index 00000000..d4bf1eb9 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/Configurable.java @@ -0,0 +1,23 @@ +/* + * 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.support; + +public interface Configurable { + Class getConfigClass(); + C newConfig(); +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java new file mode 100644 index 00000000..8246cfbb --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java @@ -0,0 +1,60 @@ +/* + * 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.support; + +import java.util.Map; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.support.AopUtils; +import org.springframework.boot.context.properties.bind.Bindable; +import org.springframework.boot.context.properties.bind.Binder; +import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Validator; + +public abstract class ConfigurationUtils { + + public static void bind(Object o, Map properties, String configurationPropertyName, String bindingName, Validator validator) { + Object toBind = getTargetObject(o); + + new Binder(new MapConfigurationPropertySource(properties)) + .bind(configurationPropertyName, Bindable.ofInstance(toBind)); + + if (validator != null) { + BindingResult errors = new BeanPropertyBindingResult(toBind, bindingName); + validator.validate(toBind, errors); + if (errors.hasErrors()) { + throw new RuntimeException(new BindException(errors)); + } + } + } + + public static T getTargetObject(Object candidate) { + try { + if (AopUtils.isAopProxy(candidate) && (candidate instanceof Advised)) { + return (T) ((Advised) candidate).getTargetSource().getTarget(); + } + } + catch (Exception ex) { + throw new IllegalStateException("Failed to unwrap proxied object", ex); + } + return (T) candidate; + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java index 4c1368ed..064e61d0 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/NameUtils.java @@ -30,11 +30,11 @@ public class NameUtils { return GENERATED_NAME_PREFIX + i; } - public static String normalizePredicateName(Class clazz) { + public static String normalizeRoutePredicateName(Class clazz) { return clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), ""); } - public static String normalizeFilterName(Class clazz) { + public static String normalizeFilterFactoryName(Class clazz) { return clazz.getSimpleName().replace(GatewayFilterFactory.class.getSimpleName(), ""); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java new file mode 100644 index 00000000..3f3d7677 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java @@ -0,0 +1,128 @@ +/* + * Copyright 2013-2017 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.support; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.Expression; +import org.springframework.expression.common.TemplateParserContext; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Spencer Gibb + */ +public interface ShortcutConfigurable { + + enum ShortcutType { + DEFAULT { + @Override + public Map normalize(Map args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) { + Map map = new HashMap<>(); + int entryIdx = 0; + for (Map.Entry entry : args.entrySet()) { + String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args); + Object value = getValue(parser, beanFactory, entry.getValue()); + + map.put(key, value); + entryIdx++; + } + return map; + } + }, + + GATHER_LIST { + @Override + public Map normalize(Map args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) { + Map map = new HashMap<>(); + // field order should be of size 1 + List fieldOrder = shortcutConf.shortcutFieldOrder(); + Assert.isTrue(fieldOrder != null + && fieldOrder.size() == 1, + "Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1"); + String fieldName = fieldOrder.get(0); + map.put(fieldName, args.values().stream() + .map(value -> getValue(parser, beanFactory, value)) + .collect(Collectors.toList())); + return map; + } + }; + + public abstract Map normalize(Map args, ShortcutConfigurable shortcutConf, + SpelExpressionParser parser, BeanFactory beanFactory); + } + + static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints, Map args) { + // RoutePredicateFactory has name hints and this has a fake key name + // replace with the matching key hint + if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argHints.shortcutFieldOrder().isEmpty() + && entryIdx < args.size() && entryIdx < argHints.shortcutFieldOrder().size()) { + key = argHints.shortcutFieldOrder().get(entryIdx); + } + return key; + } + + static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) { + Object value; + String rawValue = entryValue; + if (rawValue != null) { + rawValue = rawValue.trim(); + } + if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { + // assume it's spel + StandardEvaluationContext context = new StandardEvaluationContext(); + context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); + value = expression.getValue(context); + } else { + value = entryValue; + } + return value; + } + + default ShortcutType shortcutType() { + return ShortcutType.DEFAULT; + } + + /** + * Returns hints about the number of args and the order for shortcut parsing. + * @return + */ + default List shortcutFieldOrder() { + return Collections.emptyList(); + } + + /** + * Validate supplied argument size against {@see #shortcutFieldOrder} size. + * Useful for variable arg predicates. + * @return + */ + @Deprecated + default boolean validateFieldsExist() { + return true; + } + +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java new file mode 100644 index 00000000..4d1d0cca --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StatefulConfigurable.java @@ -0,0 +1,25 @@ +/* + * 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.support; + +import java.util.Map; + +public interface StatefulConfigurable extends Configurable { + + Map getConfig(); +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java index 562d4622..f0704191 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestHeaderGatewayFilterFactoryTests.java @@ -19,10 +19,14 @@ package org.springframework.cloud.gateway.filter.factory; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; @@ -34,7 +38,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.springframework.cloud.gateway.test.TestUtils.getMap; - /** * @author Spencer Gibb * @author Biju Kunjummen @@ -54,13 +57,40 @@ public class AddRequestHeaderGatewayFilterFactoryTests extends BaseWebClientTest .expectBody(Map.class) .consumeWith(result -> { Map headers = getMap(result.getResponseBody(), "headers"); - assertThat(headers).containsEntry("X-Request-Foo", "Bar"); + assertThat(headers).containsEntry("X-Request-Example", "ValueA"); + }); + } + + @Test + public void addRequestHeaderFilterWorksJavaDsl() { + testClient.get() + .uri("/headers") + .header("Host", "www.addrequestheaderjava.org") + .exchange() + .expectBody(Map.class) + .consumeWith(result -> { + Map headers = getMap(result.getResponseBody(), "headers"); + assertThat(headers).containsEntry("X-Request-Acme", "ValueB"); }); } @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) - public static class TestConfig { } + public static class TestConfig { + + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("add_request_header_java_test", r -> + r.path("/headers").and().host("**.addrequestheaderjava.org") + .filters(f -> f.prefixPath("/httpbin").addRequestHeader("X-Request-Acme", "ValueB")) + .uri(uri)) + .build(); + } + } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java index 8f9c7e48..e3e70486 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/AddRequestParameterGatewayFilterFactoryTests.java @@ -24,10 +24,14 @@ import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; @@ -60,7 +64,16 @@ public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientT testRequestParameterFilter("name", "%E6%89%8E%E6%A0%B9"); } + @Test + public void addRequestParameterFilterWorksEncodedQueryJavaDsl() { + testRequestParameterFilter("www.addreqparamjava.org", "ValueB", "javaname", "%E6%89%8E%E6%A0%B9"); + } + private void testRequestParameterFilter(String name, String value) { + testRequestParameterFilter("www.addrequestparameter.org", "ValueA", name, value); + } + + private void testRequestParameterFilter(String host, String expectedValue, String name, String value) { String query; if (name != null) { query = "?" + name + "=" + value; @@ -71,12 +84,12 @@ public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientT boolean checkForEncodedValue = containsEncodedQuery(uri); testClient.get() .uri(uri) - .header("Host", "www.addrequestparameter.org") + .header("Host", host) .exchange() .expectBody(Map.class) .consumeWith(response -> { Map args = getMap(response.getResponseBody(), "args"); - assertThat(args).containsEntry("foo", "bar"); + assertThat(args).containsEntry("example", expectedValue); if (name != null) { if (checkForEncodedValue) { try { @@ -94,6 +107,19 @@ public class AddRequestParameterGatewayFilterFactoryTests extends BaseWebClientT @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) - public static class TestConfig { } + public static class TestConfig { + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("add_request_param_java_test", r -> + r.path("/get").and().host("**.addreqparamjava.org") + .filters(f -> f.prefixPath("/httpbin").addRequestParameter("example", "ValueB")) + .uri(uri)) + .build(); + } + } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java index 5c537f4c..9a00c09d 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/HystrixGatewayFilterFactoryTests.java @@ -22,10 +22,14 @@ import java.util.Map; import org.junit.Test; import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; @@ -67,15 +71,52 @@ public class HystrixGatewayFilterFactoryTests extends BaseWebClientTests { .expectBody().json("{\"from\":\"fallbackcontroller\"}"); } + @Test + public void hystrixFilterWorksJavaDsl() { + testClient.get().uri("/get") + .header("Host", "www.hystrixjava.org") + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(ROUTE_ID_HEADER, "hystrix_java"); + } + + @Test + public void hystrixFilterFallbackJavaDsl() { + testClient.get().uri("/delay/3") + .header("Host", "www.hystrixjava.org") + .exchange() + .expectStatus().isOk() + .expectBody().json("{\"from\":\"fallbackcontroller2\"}"); + } + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) @RestController public static class TestConfig { + + @Value("${test.uri}") + private String uri; + @RequestMapping("/fallbackcontroller") public Map fallbackcontroller(@RequestParam("a") String a) { return Collections.singletonMap("from", "fallbackcontroller"); } + + @RequestMapping("/fallbackcontroller2") + public Map fallbackcontroller2() { + return Collections.singletonMap("from", "fallbackcontroller2"); + } + + @Bean + public RouteLocator hystrixRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("hystrix_java", r -> r.host("**.hystrixjava.org") + .filters(f -> f.prefixPath("/httpbin") + .hystrix(config -> config.setName("javacmd").setFallbackUri("forward:/fallbackcontroller2"))) + .uri(uri)) + .build(); + } } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java index 1f5c5f39..fb7e4295 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RedirectToGatewayFilterFactoryTests.java @@ -28,14 +28,8 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -44,21 +38,12 @@ public class RedirectToGatewayFilterFactoryTests extends BaseWebClientTests { @Test public void redirectToFilterWorks() { - Mono result = webClient.get() + testClient.get() .uri("/") .header("Host", "www.redirectto.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.FOUND); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HttpHeaders.LOCATION)) - .isEqualTo("http://example.org"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isEqualTo(HttpStatus.FOUND) + .expectHeader().valueEquals(HttpHeaders.LOCATION, "http://example.org"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java index a390b52d..ebb187fb 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestHeaderGatewayFilterFactoryTests.java @@ -32,10 +32,6 @@ import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; import static org.springframework.cloud.gateway.test.TestUtils.getMap; -import static org.springframework.web.reactive.function.BodyExtractors.toMono; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -44,21 +40,16 @@ public class RemoveRequestHeaderGatewayFilterFactoryTests extends BaseWebClientT @Test public void removeRequestHeaderFilterWorks() { - Mono result = webClient.get() + testClient.get() .uri("/headers") .header("Host", "www.removerequestheader.org") .header("X-Request-Foo", "Bar") .exchange() - .flatMap(response -> response.body(toMono(Map.class))); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - Map headers = getMap(response, "headers"); - assertThat(headers).doesNotContainKey("X-Request-Foo"); - }) - .expectComplete() - .verify(DURATION); + .expectStatus().isOk() + .expectBody(Map.class).consumeWith(result -> { + Map headers = getMap(result.getResponseBody(), "headers"); + assertThat(headers).doesNotContainKey("X-Request-Foo"); + }); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java index 252fe707..2a15b8bb 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveResponseHeaderGatewayFilterFactoryTests.java @@ -23,17 +23,12 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.test.BaseWebClientTests; -import org.springframework.http.HttpHeaders; +import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext @@ -41,23 +36,17 @@ public class RemoveResponseHeaderGatewayFilterFactoryTests extends BaseWebClient @Test public void removeResponseHeaderFilterWorks() { - Mono result = webClient.get() + testClient.get() .uri("/headers") .header("Host", "www.removereresponseheader.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders).doesNotContainKey("X-Request-Foo"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().doesNotExist("X-Request-Foo"); } @EnableAutoConfiguration @SpringBootConfiguration + @Import(DefaultTestConfig.class) public static class TestConfig { } } 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 f9dd4871..ad62ee2d 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 @@ -8,11 +8,15 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter.Response; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.support.ServerWebExchangeUtils; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; @@ -25,8 +29,6 @@ import org.springframework.tuple.Tuple; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.when; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter.BURST_CAPACITY_KEY; -import static org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter.REPLENISH_RATE_KEY; import static org.springframework.tuple.TupleBuilder.tuple; import reactor.core.publisher.Mono; @@ -41,7 +43,7 @@ import reactor.core.publisher.Mono; public class RequestRateLimiterGatewayFilterFactoryTests extends BaseWebClientTests { @Autowired - private RequestRateLimiterGatewayFilterFactory filterFactory; + private ApplicationContext context; @MockBean private RateLimiter rateLimiter; @@ -70,17 +72,22 @@ public class RequestRateLimiterGatewayFilterFactoryTests extends BaseWebClientTe private void assertFilterFactory(KeyResolver keyResolver, String key, boolean allowed, HttpStatus expectedStatus) { Tuple args = tuple().build(); - when(rateLimiter.isAllowed(key, args)) + when(rateLimiter.isAllowed("myroute", key)) .thenReturn(Mono.just(new Response(allowed, 1))); - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); MockServerWebExchange exchange = MockServerWebExchange.from(request); exchange.getResponse().setStatusCode(HttpStatus.OK); + exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, + Route.builder().id("myroute").predicate(ex -> true) + .uri("http://localhost").build()); when(this.filterChain.filter(exchange)).thenReturn(Mono.empty()); - Mono response = filterFactory.apply(args).filter(exchange, this.filterChain); + RequestRateLimiterGatewayFilterFactory factory = this.context.getBean(RequestRateLimiterGatewayFilterFactory.class); + GatewayFilter filter = factory.apply(config -> config.setKeyResolver(keyResolver)); + + Mono response = filter.filter(exchange, this.filterChain); response.subscribe(aVoid -> assertThat(exchange.getResponse().getStatusCode()) .isEqualTo(expectedStatus)); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java index ef2a6616..5fb5b9b1 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryIntegrationTests.java @@ -24,16 +24,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -42,18 +36,11 @@ public class RewritePathGatewayFilterFactoryIntegrationTests extends BaseWebClie @Test public void rewritePathFilterWorks() { - Mono result = webClient.get() + testClient.get() .uri("/foo/get") .header("Host", "www.baz.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk(); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java index d0c66fce..34df64bf 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RewritePathGatewayFilterFactoryTests.java @@ -57,7 +57,7 @@ public class RewritePathGatewayFilterFactoryTests { } private ServerWebExchange testRewriteFilter(String regex, String replacement, String actualPath, String expectedPath) { - GatewayFilter filter = new RewritePathGatewayFilterFactory().apply(tuple().of(REGEXP_KEY, regex, REPLACEMENT_KEY, replacement)); + GatewayFilter filter = new RewritePathGatewayFilterFactory().apply(c -> c.setRegexp(regex).setReplacement(replacement)); URI url = UriComponentsBuilder.fromUriString("http://localhost"+ actualPath).build(true).toUri(); MockServerHttpRequest request = MockServerHttpRequest diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java index 07de2d7e..10b6c72e 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryIntegrationTests.java @@ -24,16 +24,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -42,18 +36,11 @@ public class SetPathGatewayFilterFactoryIntegrationTests extends BaseWebClientTe @Test public void setPathFilterDefaultValuesWork() { - Mono result = webClient.get() + testClient.get() .uri("/foo/get") .header("Host", "www.setpath.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk(); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java index a5aeea19..2300f0fc 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetPathGatewayFilterFactoryTests.java @@ -63,7 +63,7 @@ public class SetPathGatewayFilterFactoryTests { } private void testRewriteFilter(String template, String actualPath, String expectedPath, HashMap variables) { - GatewayFilter filter = new SetPathGatewayFilterFactory().apply(tuple().of(TEMPLATE_KEY, template)); + GatewayFilter filter = new SetPathGatewayFilterFactory().apply(c -> c.setTemplate(template)); MockServerHttpRequest request = MockServerHttpRequest .get("http://localhost"+ actualPath) diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java similarity index 69% rename from spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseGatewayFilterFactoryTests.java rename to spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java index 44587b06..a51c30f7 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/SetResponseHeaderGatewayFilterFactoryTests.java @@ -24,38 +24,24 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext -public class SetResponseGatewayFilterFactoryTests extends BaseWebClientTests { +public class SetResponseHeaderGatewayFilterFactoryTests extends BaseWebClientTests { @Test public void setResponseHeaderFilterWorks() { - Mono result = webClient.get() + testClient.get() .uri("/headers") .header("Host", "www.setreresponseheader.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders).containsKey("X-Request-Foo"); - assertThat(httpHeaders.get("X-Request-Foo")).containsExactly("Bar"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals("X-Request-Foo", "Bar"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java index 6efb1465..c3768a45 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryIntegrationTests.java @@ -16,9 +16,6 @@ */ package org.springframework.cloud.gateway.filter.factory; -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; - import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.SpringBootConfiguration; @@ -26,13 +23,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; /** * @author Ryan Baxter @@ -44,23 +38,16 @@ public class StripPrefixGatewayFilterFactoryIntegrationTests extends BaseWebClie @Test public void stripPrefixFilterDefaultValuesWork() { - Mono result = webClient.get() + testClient.get() .uri("/foo/bar/get") .header("Host", "www.stripprefix.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk(); } @EnableAutoConfiguration @SpringBootConfiguration - @Import(BaseWebClientTests.DefaultTestConfig.class) + @Import(DefaultTestConfig.class) public static class TestConfig { } } \ No newline at end of file diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java index 60cb0b06..72db572e 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/StripPrefixGatewayFilterFactoryTests.java @@ -56,7 +56,7 @@ public class StripPrefixGatewayFilterFactoryTests { private void testStripPrefixFilter(String actualPath, String expectedPath, int parts) { GatewayFilter filter = new StripPrefixGatewayFilterFactory().apply( - tuple().of(StripPrefixGatewayFilterFactory.PARTS_KEY, parts)); + c -> c.setParts(parts)); MockServerHttpRequest request = MockServerHttpRequest .get("http://localhost"+ actualPath) diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java index 3a2db211..cdf515b0 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/PrincipalNameKeyResolverIntegrationTests.java @@ -18,6 +18,7 @@ package org.springframework.cloud.gateway.filter.ratelimit; import java.security.Principal; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import org.junit.AfterClass; @@ -110,15 +111,17 @@ public class PrincipalNameKeyResolverIntegrationTests { public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() .route(r -> r.path("/myapi/**") - .filters(f -> f.requestRateLimiter(tuple().build()) + .filters(f -> f.requestRateLimiter() + .rateLimiter(MyRateLimiter.class, rl -> {}) + .and() .prefixPath("/downstream")) .uri("http://localhost:"+port)) .build(); } @Bean - RateLimiter rateLimiter() { - return (id, args) -> Mono.just(new RateLimiter.Response(true, Long.MAX_VALUE)); + MyRateLimiter rateLimiter() { + return new MyRateLimiter(); } @Bean @@ -136,5 +139,30 @@ public class PrincipalNameKeyResolverIntegrationTests { UserDetails user = User.withUsername("user").password("{noop}password").roles("USER").build(); return new MapReactiveUserDetailsService(user); } + + class MyRateLimiter implements RateLimiter { + + private HashMap map = new HashMap<>(); + + @Override + public Mono isAllowed(String routeId, String id) { + return Mono.just(new RateLimiter.Response(true, Long.MAX_VALUE)); + } + + @Override + public Class getConfigClass() { + return Object.class; + } + + @Override + public Map getConfig() { + return map; + } + + @Override + public Object newConfig() { + return null; + } + } } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java new file mode 100644 index 00000000..b6e17e09 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterConfigTests.java @@ -0,0 +1,101 @@ +/* + * 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.filter.ratelimit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter.Config; +import org.springframework.cloud.gateway.route.Route; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.server.ServerWebExchange; + +import static org.assertj.core.api.Assertions.assertThat; + +import reactor.core.publisher.Mono; + +/** + * @author Spencer Gibb + */ +@RunWith(SpringRunner.class) +@SpringBootTest +@DirtiesContext +@ActiveProfiles("redis-rate-limiter-config") +public class RedisRateLimiterConfigTests { + + @Autowired + private RedisRateLimiter rateLimiter; + + @Autowired + private RouteLocator routeLocator; + + @Test + public void redisRateConfiguredFromEnvironment() { + assertFilter("redis_rate_limiter_config_test", 10, 20, PrincipalNameKeyResolver.class); + } + + @Test + public void redisRateConfiguredFromJavaAPI() { + assertFilter("custom_redis_rate_limiter", 20, 40, MyKeyResolver.class); + } + + private void assertFilter(String key, int replenishRate, int burstCapacity, Class keyResolverClass) { + assertThat(rateLimiter.getConfig()).containsKey(key); + + Config config = rateLimiter.getConfig().get(key); + assertThat(config.getReplenishRate()).isEqualTo(replenishRate); + assertThat(config.getBurstCapacity()).isEqualTo(burstCapacity); + + Route route = routeLocator.getRoutes().filter(r -> r.getId().equals(key)).next().block(); + assertThat(route).isNotNull(); + assertThat(route.getFilters()).hasSize(1); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + public static class TestConfig { + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("custom_redis_rate_limiter", r -> r.path("/custom") + .filters(f -> f.requestRateLimiter() + .rateLimiter(RedisRateLimiter.class, + rl -> rl.setBurstCapacity(40).setReplenishRate(20)) + .and()) + .uri("http://localhost")) + .build(); + + } + } + + private static class MyKeyResolver implements KeyResolver { + @Override + public Mono resolve(ServerWebExchange exchange) { + return null; + } + } +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java index b5a0b706..a850713c 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java @@ -26,7 +26,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Spencer Gibb */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = RANDOM_PORT, properties = "true") +@SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext public class RedisRateLimiterTests extends BaseWebClientTests { @@ -43,17 +43,20 @@ public class RedisRateLimiterTests extends BaseWebClientTests { int replenishRate = 10; int burstCapacity = 2 * replenishRate; - Tuple args = RedisRateLimiter.args(replenishRate, burstCapacity); + String routeId = "myroute"; + rateLimiter.getConfig().put(routeId, new RedisRateLimiter.Config() + .setBurstCapacity(burstCapacity) + .setReplenishRate(replenishRate)); // Bursts work for (int i = 0; i < burstCapacity; i++) { - Response response = rateLimiter.isAllowed(id, args).block(); + Response response = rateLimiter.isAllowed(routeId, id).block(); assertThat(response.isAllowed()).as("Burst # %s is allowed", i).isTrue(); } - Response response = rateLimiter.isAllowed(id, args).block(); + Response response = rateLimiter.isAllowed(routeId, id).block(); if (response.isAllowed()) { //TODO: sometimes there is an off by one error - response = rateLimiter.isAllowed(id, args).block(); + response = rateLimiter.isAllowed(routeId, id).block(); } assertThat(response.isAllowed()).as("Burst # %s is not allowed", burstCapacity).isFalse(); @@ -61,11 +64,11 @@ public class RedisRateLimiterTests extends BaseWebClientTests { // # After the burst is done, check the steady state for (int i = 0; i < replenishRate; i++) { - response = rateLimiter.isAllowed(id, args).block(); + response = rateLimiter.isAllowed(routeId, id).block(); assertThat(response.isAllowed()).as("steady state # %s is allowed", i).isTrue(); } - response = rateLimiter.isAllowed(id, args).block(); + response = rateLimiter.isAllowed(routeId, id).block(); assertThat(response.isAllowed()).as("steady state # %s is allowed", replenishRate).isFalse(); } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java index 0c29bf64..7ed1e3b3 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java @@ -73,11 +73,11 @@ public class AfterRoutePredicateFactoryTests { @Test public void testPredicates() { - boolean result = new AfterRoutePredicateFactory().apply(ZonedDateTime.now().minusHours(2)).test(getExchange()); + boolean result = new AfterRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2).toString())).test(getExchange()); assertThat(result).isTrue(); } private boolean runPredicate(String dateString) { - return new AfterRoutePredicateFactory().apply(tuple().of(DATETIME_KEY, dateString)).test(getExchange()); + return new AfterRoutePredicateFactory().apply(c -> c.setDatetime(dateString)).test(getExchange()); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java index 53ccba20..a78a8f42 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java @@ -73,11 +73,11 @@ public class BeforeRoutePredicateFactoryTests { @Test public void testPredicates() { - boolean result = new BeforeRoutePredicateFactory().apply(ZonedDateTime.now().minusHours(2)).test(getExchange()); + boolean result = new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2).toString())).test(getExchange()); assertThat(result).isFalse(); } private boolean runPredicate(String dateString) { - return new BeforeRoutePredicateFactory().apply(tuple().of(DATETIME_KEY, dateString)).test(getExchange()); + return new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(dateString)).test(getExchange()); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java index a18c5d6b..97457ea8 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java @@ -19,8 +19,10 @@ package org.springframework.cloud.gateway.handler.predicate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import java.util.HashMap; import org.junit.Test; +import org.springframework.cloud.gateway.support.ConfigurationUtils; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.web.server.ServerWebExchange; @@ -28,7 +30,6 @@ import org.springframework.web.server.ServerWebExchange; import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.DATETIME1_KEY; import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.DATETIME2_KEY; -import static org.springframework.tuple.TupleBuilder.tuple; /** * @author Spencer Gibb @@ -50,6 +51,8 @@ public class BetweenRoutePredicateFactoryTests { String dateString1 = minusHours(1); String dateString2 = plusHours(1); + ZonedDateTime parse = ZonedDateTime.parse(dateString1); + final boolean result = runPredicate(dateString1, dateString2); assertThat(result).as("Now is not between %s and %s", dateString1, dateString2).isTrue(); @@ -98,14 +101,24 @@ public class BetweenRoutePredicateFactoryTests { @Test public void testPredicates() { boolean result = new BetweenRoutePredicateFactory() - .apply(ZonedDateTime.now().minusHours(2), ZonedDateTime.now().plusHours(1)) + .apply(c -> c.setDatetime1(ZonedDateTime.now().minusHours(2).toString()) + .setDatetime2(ZonedDateTime.now().plusHours(1).toString())) .test(getExchange()); assertThat(result).isTrue(); } boolean runPredicate(String dateString1, String dateString2) { - return new BetweenRoutePredicateFactory().apply(tuple() - .of(DATETIME1_KEY, dateString1, DATETIME2_KEY, dateString2)).test(getExchange()); + HashMap map = new HashMap<>(); + map.put(DATETIME1_KEY, dateString1); + map.put(DATETIME2_KEY, dateString2); + + BetweenRoutePredicateFactory factory = new BetweenRoutePredicateFactory(); + + BetweenRoutePredicateFactory.Config config = factory.newConfig(); + + ConfigurationUtils.bind(config, map, "", "myname", null); + + return factory.apply(config).test(getExchange()); } static String minusHoursMillis(int hours) { diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java index f61a985b..05a746d5 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HeaderRoutePredicateFactoryTests.java @@ -25,18 +25,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -45,42 +37,27 @@ public class HeaderRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void headerRouteWorks() { - Mono result = webClient.get() + testClient.get() .uri("/get") .header("Foo", "bar") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith(response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER)) - .isEqualTo(RoutePredicateHandlerMapping.class.getSimpleName()); - assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER)).isEqualTo("header_test"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, + RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader().valueEquals(ROUTE_ID_HEADER, "header_test"); } @Test @SuppressWarnings("Duplicates") public void headerRouteIgnoredWhenHeaderMissing() { - Mono result = webClient.get() + testClient.get() .uri("/get") // no headers set. Test used to throw a null pointer exception. - .exchange(); - - StepVerifier.create(result) - .consumeNextWith(response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER)) - .isEqualTo(RoutePredicateHandlerMapping.class.getSimpleName()); - assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER)) - .isEqualTo("default_path_to_httpbin"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, + RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java index 99298956..7c88da69 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateFactoryTests.java @@ -25,18 +25,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -45,23 +37,13 @@ public class HostRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void hostRouteWorks() { - Mono result = webClient.get() + testClient.get() .uri("/get") .header("Host", "www.example.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER)) - .isEqualTo(RoutePredicateHandlerMapping.class.getSimpleName()); - assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER)) - .isEqualTo("host_example_to_httpbin"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader().valueEquals(ROUTE_ID_HEADER, "host_example_to_httpbin"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java index b814ca0a..fd157719 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/MethodRoutePredicateFactoryTests.java @@ -25,18 +25,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -44,24 +36,14 @@ import reactor.test.StepVerifier; public class MethodRoutePredicateFactoryTests extends BaseWebClientTests { @Test - public void hostRouteWorks() { - Mono result = webClient.get() + public void methodRouteWorks() { + testClient.get() .uri("/get") .header("Host", "www.method.org") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER)) - .isEqualTo(RoutePredicateHandlerMapping.class.getSimpleName()); - assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER)) - .isEqualTo("method_test"); - }) - .expectComplete() - .verify(DURATION); + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader().valueEquals(ROUTE_ID_HEADER, "method_test"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java index 0945ca61..bd8a2715 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/PathRoutePredicateFactoryTests.java @@ -25,18 +25,10 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping; import org.springframework.cloud.gateway.test.BaseWebClientTests; import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.web.reactive.function.client.ClientResponse; -import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.cloud.gateway.test.TestUtils.assertStatus; - -import reactor.core.publisher.Mono; -import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @@ -45,22 +37,11 @@ public class PathRoutePredicateFactoryTests extends BaseWebClientTests { @Test public void pathRouteWorks() { - Mono result = webClient.get() - .uri("/get") - .exchange(); - - StepVerifier.create(result) - .consumeNextWith( - response -> { - assertStatus(response, HttpStatus.OK); - HttpHeaders httpHeaders = response.headers().asHttpHeaders(); - assertThat(httpHeaders.getFirst(HANDLER_MAPPER_HEADER)) - .isEqualTo(RoutePredicateHandlerMapping.class.getSimpleName()); - assertThat(httpHeaders.getFirst(ROUTE_ID_HEADER)) - .isEqualTo("default_path_to_httpbin"); - }) - .expectComplete() - .verify(DURATION); + testClient.get().uri("/get") + .exchange() + .expectStatus().isOk() + .expectHeader().valueEquals(HANDLER_MAPPER_HEADER, RoutePredicateHandlerMapping.class.getSimpleName()) + .expectHeader().valueEquals(ROUTE_ID_HEADER, "default_path_to_httpbin"); } @EnableAutoConfiguration diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java index 3523fb15..c1fbbc58 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/RemoteAddrRoutePredicateFactoryTests.java @@ -26,7 +26,7 @@ import reactor.test.StepVerifier; public class RemoteAddrRoutePredicateFactoryTests extends BaseWebClientTests { @Test - public void pathRouteWorks() { + public void remoteAddrWorks() { Mono result = webClient.get().uri("/ok/httpbin/").exchange(); StepVerifier.create(result) @@ -35,7 +35,7 @@ public class RemoteAddrRoutePredicateFactoryTests extends BaseWebClientTests { } @Test - public void pathRouteDoNotWork() { + public void remoteAddrRejects() { Mono result = webClient.get().uri("/nok/httpbin/").exchange(); StepVerifier diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java index 1358e571..d011f6cc 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java @@ -11,7 +11,7 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.cloud.gateway.support.ArgumentHints; +import org.springframework.cloud.gateway.support.ShortcutConfigurable; import org.springframework.context.annotation.Bean; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.junit4.SpringRunner; @@ -31,16 +31,17 @@ public class RouteDefinitionRouteLocatorTests { @Test public void testGetTupleWithSpel() { parser = new SpelExpressionParser(); - ArgumentHints argumentHints = new ArgumentHints() { + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { @Override - public List argNames() { + public List shortcutFieldOrder() { return Arrays.asList("bean", "arg1"); } }; Map args = new HashMap<>(); args.put("bean", "#{@foo}"); args.put("arg1", "val1"); - Tuple tuple = RouteDefinitionRouteLocator.getTuple(argumentHints, args, parser, this.beanFactory); + + Tuple tuple = RouteDefinitionRouteLocator.getTuple(shortcutConfigurable, args, parser, this.beanFactory); assertThat(tuple).isNotNull(); assertThat(tuple.getValue("bean", Integer.class)).isEqualTo(42); assertThat(tuple.getString("arg1")).isEqualTo("val1"); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java new file mode 100644 index 00000000..fbffc18b --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java @@ -0,0 +1,97 @@ +/* + * 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.support; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; +import org.springframework.context.annotation.Bean; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ShortcutConfigurableTests { + + private SpelExpressionParser parser; + + @Autowired + BeanFactory beanFactory; + + @Test + public void testNormalizeDefaultTypeWithSpel() { + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("bean", "#{@foo}"); + args.put("arg1", "val1"); + Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull() + .containsEntry("bean", 42) + .containsEntry("arg1", "val1"); + } + + @Test + @SuppressWarnings("unchecked") + public void testNormalizeGatherListTypeWithSpel() { + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("values"); + } + + @Override + public ShortcutType shortcutType() { + return ShortcutType.GATHER_LIST; + } + }; + Map args = new HashMap<>(); + args.put("1", "#{@foo}"); + args.put("2", "val1"); + args.put("3", "val2"); + Map map = ShortcutType.GATHER_LIST.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull().containsKey("values"); + assertThat((List)map.get("values")) + .containsExactly(42, "val1", "val2"); + } + + @SpringBootConfiguration + protected static class TestConfig { + @Bean + public Integer foo() { + return 42; + } + } +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java index ebb13536..a6607146 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java @@ -38,7 +38,7 @@ import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilter import org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactoryTests; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryIntegrationTests; import org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactoryTests; -import org.springframework.cloud.gateway.filter.factory.SetResponseGatewayFilterFactoryTests; +import org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactoryTests; import org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactoryTests; import org.springframework.cloud.gateway.filter.headers.ForwardedHeadersFilterTests; import org.springframework.cloud.gateway.filter.headers.RemoveHopByHopHeadersFilterTests; @@ -88,7 +88,7 @@ import static org.junit.Assume.assumeThat; RequestRateLimiterGatewayFilterFactoryTests.class, SetPathGatewayFilterFactoryIntegrationTests.class, AddRequestParameterGatewayFilterFactoryTests.class, - SetResponseGatewayFilterFactoryTests.class, + SetResponseHeaderGatewayFilterFactoryTests.class, PrincipalNameKeyResolverIntegrationTests.class, RedisRateLimiterTests.class, RouteDefinitionRouteLocatorTests.class, diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java index 7e1b690e..1473f00c 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java @@ -104,10 +104,6 @@ public class BaseWebClientTests { @Import(DefaultTestConfig.class) public static class MainConfig { } - public static void main(String[] args) { - new SpringApplication(MainConfig.class).run(args); - } - protected static class TestRibbonConfig { @LocalServerPort diff --git a/spring-cloud-gateway-core/src/test/resources/application-redis-rate-limiter-config.yml b/spring-cloud-gateway-core/src/test/resources/application-redis-rate-limiter-config.yml new file mode 100644 index 00000000..2143adbf --- /dev/null +++ b/spring-cloud-gateway-core/src/test/resources/application-redis-rate-limiter-config.yml @@ -0,0 +1,20 @@ +test: + uri: lb://myservice + +spring: + cloud: + gateway: + default-filters: + routes: + # ===================================== + - id: redis_rate_limiter_config_test + uri: ${test.uri} + predicates: + - Path=/ + filters: + - name: RequestRateLimiter + args: + redis-rate-limiter: + replenish-rate: 10 + burst-capacity: 20 + diff --git a/spring-cloud-gateway-core/src/test/resources/application-request-header-web-filter.yml b/spring-cloud-gateway-core/src/test/resources/application-request-header-web-filter.yml index e76ddf47..73cd8ad3 100644 --- a/spring-cloud-gateway-core/src/test/resources/application-request-header-web-filter.yml +++ b/spring-cloud-gateway-core/src/test/resources/application-request-header-web-filter.yml @@ -11,4 +11,4 @@ spring: predicates: - Path=/headers filters: - - AddRequestHeader=X-Request-Foo, Bar + - AddRequestHeader=X-Request-Example, ValueA diff --git a/spring-cloud-gateway-core/src/test/resources/application-request-parameter-web-filter.yml b/spring-cloud-gateway-core/src/test/resources/application-request-parameter-web-filter.yml index f3d3c4c0..2a23b506 100644 --- a/spring-cloud-gateway-core/src/test/resources/application-request-parameter-web-filter.yml +++ b/spring-cloud-gateway-core/src/test/resources/application-request-parameter-web-filter.yml @@ -12,4 +12,4 @@ spring: - Host=**.addrequestparameter.org - Path=/get filters: - - AddRequestParameter=foo, bar + - AddRequestParameter=example, ValueA diff --git a/spring-cloud-gateway-core/src/test/resources/application.yml b/spring-cloud-gateway-core/src/test/resources/application.yml index 7003eb13..3360a9d5 100644 --- a/spring-cloud-gateway-core/src/test/resources/application.yml +++ b/spring-cloud-gateway-core/src/test/resources/application.yml @@ -229,11 +229,6 @@ spring: args: pattern: /** -testservice: - ribbon: - NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList - listOfServers: ${test.hostport} - hystrix.command.successcmd.execution.isolation.thread.timeoutInMilliseconds: 5000 logging: diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java index 2e52e557..5999b9db 100644 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java @@ -41,7 +41,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; public class GatewaySampleApplication { @Bean - public RouteLocator customRouteLocator(RouteLocatorBuilder builder, ThrottleGatewayFilterFactory throttle) { + public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { //@formatter:off return builder.routes() .route(r -> r.host("**.abc.org").and().path("/image/png") @@ -56,21 +56,17 @@ public class GatewaySampleApplication { ) .route(r -> r.order(-1) .host("**.throttle.org").and().path("/get") - .filters(f -> f.filter(throttle.apply(1, - 1, - 10, - TimeUnit.SECONDS))) + .filters(f -> f.filter(new ThrottleGatewayFilter() + .setCapacity(1) + .setRefillTokens(1) + .setRefillPeriod(10) + .setRefillUnit(TimeUnit.SECONDS))) .uri("http://httpbin.org:80") ) .build(); //@formatter:on } - @Bean - public ThrottleGatewayFilterFactory throttleWebFilterFactory() { - return new ThrottleGatewayFilterFactory(); - } - @Bean public RouterFunction testFunRouterFunction() { RouterFunction route = RouterFunctions.route( diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java new file mode 100644 index 00000000..19efe6b0 --- /dev/null +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilter.java @@ -0,0 +1,98 @@ +/* + * Copyright 2013-2017 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.sample; + +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.isomorphism.util.TokenBucket; +import org.isomorphism.util.TokenBuckets; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ServerWebExchange; + +import reactor.core.publisher.Mono; + +/** + * Sample throttling filter. + * See https://github.com/bbeck/token-bucket + */ +public class ThrottleGatewayFilter implements GatewayFilter { + private static final Log log = LogFactory.getLog(ThrottleGatewayFilter.class); + + int capacity; + int refillTokens; + int refillPeriod; + TimeUnit refillUnit; + + public int getCapacity() { + return capacity; + } + + public ThrottleGatewayFilter setCapacity(int capacity) { + this.capacity = capacity; + return this; + } + + public int getRefillTokens() { + return refillTokens; + } + + public ThrottleGatewayFilter setRefillTokens(int refillTokens) { + this.refillTokens = refillTokens; + return this; + } + + public int getRefillPeriod() { + return refillPeriod; + } + + public ThrottleGatewayFilter setRefillPeriod(int refillPeriod) { + this.refillPeriod = refillPeriod; + return this; + } + + public TimeUnit getRefillUnit() { + return refillUnit; + } + + public ThrottleGatewayFilter setRefillUnit(TimeUnit refillUnit) { + this.refillUnit = refillUnit; + return this; + } + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + + TokenBucket tokenBucket = TokenBuckets.builder() + .withCapacity(capacity) + .withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit) + .build(); + + //TODO: get a token bucket for a key + log.debug("TokenBucket capacity: " + tokenBucket.getCapacity()); + boolean consumed = tokenBucket.tryConsume(); + if (consumed) { + return chain.filter(exchange); + } + exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); + return exchange.getResponse().setComplete(); + } +} diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilterFactory.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilterFactory.java deleted file mode 100644 index 382dad1f..00000000 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/ThrottleGatewayFilterFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2017 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.sample; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.isomorphism.util.TokenBucket; -import org.isomorphism.util.TokenBuckets; -import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory; -import org.springframework.http.HttpStatus; -import org.springframework.tuple.Tuple; -import org.springframework.cloud.gateway.filter.GatewayFilter; - -import java.util.concurrent.TimeUnit; - -/** - * Sample throttling filter. - * See https://github.com/bbeck/token-bucket - */ -public class ThrottleGatewayFilterFactory implements GatewayFilterFactory { - private Log log = LogFactory.getLog(getClass()); - - @Override - public GatewayFilter apply(Tuple args) { - int capacity = args.getInt("capacity"); - int refillTokens = args.getInt("refillTokens"); - int refillPeriod = args.getInt("refillPeriod"); - TimeUnit refillUnit = TimeUnit.valueOf(args.getString("refillUnit")); - return apply(capacity, refillTokens, refillPeriod, refillUnit); - } - - public GatewayFilter apply(int capacity, int refillTokens, int refillPeriod, TimeUnit refillUnit) { - - final TokenBucket tokenBucket = TokenBuckets.builder() - .withCapacity(capacity) - .withFixedIntervalRefillStrategy(refillTokens, refillPeriod, refillUnit) - .build(); - - return (exchange, chain) -> { - //TODO: get a token bucket for a key - log.debug("TokenBucket capacity: " + tokenBucket.getCapacity()); - boolean consumed = tokenBucket.tryConsume(); - if (consumed) { - return chain.filter(exchange); - } - exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS); - return exchange.getResponse().setComplete(); - }; - } -} diff --git a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java index b298462e..24e71dac 100644 --- a/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java +++ b/spring-cloud-gateway-sample/src/test/java/org/springframework/cloud/gateway/sample/GatewaySampleApplicationTests.java @@ -28,6 +28,8 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.util.SocketUtils; +import java.time.Duration; + import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; /** @@ -59,7 +61,7 @@ public class GatewaySampleApplicationTests { @Before public void setup() { baseUri = "http://localhost:" + port; - this.webClient = WebTestClient.bindToServer().baseUrl(baseUri).build(); + this.webClient = WebTestClient.bindToServer().responseTimeout(Duration.ofSeconds(10)).baseUrl(baseUri).build(); } @Test