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.
This commit is contained in:
@@ -118,7 +118,7 @@ public class GatewayControllerEndpoint implements ApplicationEventPublisherAware
|
||||
return Mono.zip(routeDefs, routes).map(tuple -> {
|
||||
Map<String, List> allRoutes = new HashMap<>();
|
||||
allRoutes.put("routeDefinitions", tuple.getT1());
|
||||
allRoutes.put("routes", tuple.getT2());
|
||||
// allRoutes.put("routes", tuple.getT2());
|
||||
return allRoutes;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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<String, String> stringReactiveRedisTemplate(
|
||||
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory,
|
||||
ResourceLoader resourceLoader) {
|
||||
ReactiveRedisConnectionFactory reactiveRedisConnectionFactory) {
|
||||
RedisSerializer<String> serializer = new StringRedisSerializer();
|
||||
RedisSerializationContext<String , String> serializationContext = RedisSerializationContext
|
||||
.<String, String>newSerializationContext()
|
||||
@@ -59,7 +58,8 @@ class GatewayRedisAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public RedisRateLimiter redisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
|
||||
@Qualifier("redisRequestRateLimiterScript") RedisScript<List<Long>> redisScript) {
|
||||
return new RedisRateLimiter(redisTemplate, redisScript);
|
||||
@Qualifier("redisRequestRateLimiterScript") RedisScript<List<Long>> redisScript,
|
||||
Validator validator) {
|
||||
return new RedisRateLimiter(redisTemplate, redisScript, validator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + "/(?<remaining>.*)";
|
||||
String replacement = "/${remaining}";
|
||||
filter.addArg(REGEXP_KEY, regex);
|
||||
|
||||
@@ -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<String, Object> args;
|
||||
|
||||
public FilterArgsEvent(Object source, String routeId, Map<String, Object> args) {
|
||||
super(source);
|
||||
this.routeId = routeId;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public String getRouteId() {
|
||||
return routeId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -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<String, Object> args;
|
||||
|
||||
public PredicateArgsEvent(Object source, String routeId, Map<String, Object> args) {
|
||||
super(source);
|
||||
this.routeId = routeId;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public String getRouteId() {
|
||||
return routeId;
|
||||
}
|
||||
|
||||
public Map<String, Object> getArgs() {
|
||||
return args;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,10 @@ public class OrderedGatewayFilter implements GatewayFilter, Ordered {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public GatewayFilter getDelegate() {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
return this.delegate.filter(exchange, chain);
|
||||
|
||||
@@ -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<C>
|
||||
extends AbstractConfigurable<C> implements GatewayFilterFactory<C> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AbstractGatewayFilterFactory() {
|
||||
super((Class<C>) Object.class);
|
||||
}
|
||||
|
||||
public AbstractGatewayFilterFactory(Class<C> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<AbstractNameValueGatewayFilterFactory.NameValueConfig> {
|
||||
|
||||
public AbstractNameValueGatewayFilterFactory() {
|
||||
super(NameValueConfig.class);
|
||||
}
|
||||
|
||||
public List<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> 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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> 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);
|
||||
};
|
||||
|
||||
@@ -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<C> extends ShortcutConfigurable, Configurable<C> {
|
||||
|
||||
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<C> consumer) {
|
||||
C config = newConfig();
|
||||
consumer.accept(config);
|
||||
return apply(config);
|
||||
}
|
||||
|
||||
//TODO: remove after apply(Tuple) removed
|
||||
@Override
|
||||
default Class<C> 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
|
||||
|
||||
@@ -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<HystrixGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String FALLBACK_URI = "fallbackUri";
|
||||
|
||||
private final DispatcherHandler dispatcherHandler;
|
||||
|
||||
public HystrixGatewayFilterFactory(DispatcherHandler dispatcherHandler) {
|
||||
super(Config.class);
|
||||
this.dispatcherHandler = dispatcherHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PrefixPathGatewayFilterFactory.Config> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(PrefixPathGatewayFilterFactory.class);
|
||||
|
||||
public static final String PREFIX_KEY = "prefix";
|
||||
|
||||
public PrefixPathGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RedirectToGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String STATUS_KEY = "status";
|
||||
public static final String URL_KEY = "url";
|
||||
|
||||
public RedirectToGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<AbstractGatewayFilterFactory.NameConfig> {
|
||||
|
||||
public RemoveRequestHeaderGatewayFilterFactory() {
|
||||
super(NameConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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());
|
||||
|
||||
@@ -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<AbstractGatewayFilterFactory.NameConfig> {
|
||||
|
||||
public RemoveResponseHeaderGatewayFilterFactory() {
|
||||
super(NameConfig.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RequestRateLimiterGatewayFilterFactory.Config> {
|
||||
|
||||
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<Object> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<RewritePathGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String REGEXP_KEY = "regexp";
|
||||
public static final String REPLACEMENT_KEY = "replacement";
|
||||
|
||||
public RewritePathGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) -> {
|
||||
|
||||
@@ -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<SetPathGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String TEMPLATE_KEY = "template";
|
||||
|
||||
public SetPathGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String> 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());
|
||||
|
||||
@@ -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<String> 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);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SetStatusGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String STATUS_KEY = "status";
|
||||
|
||||
public SetStatusGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<StripPrefixGatewayFilterFactory.Config> {
|
||||
|
||||
public static final String PARTS_KEY = "parts";
|
||||
|
||||
public StripPrefixGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<C> extends AbstractStatefulConfigurable<C> implements RateLimiter<C>, ApplicationListener<FilterArgsEvent> {
|
||||
private String configurationPropertyName;
|
||||
private final Validator validator;
|
||||
|
||||
protected AbstractRateLimiter(Class<C> 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<String, Object> 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<String, Object> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<C> extends StatefulConfigurable<C> {
|
||||
|
||||
Mono<Response> isAllowed(String id, Tuple args);
|
||||
Mono<Response> isAllowed(String routeId, String id);
|
||||
|
||||
class Response {
|
||||
private final boolean allowed;
|
||||
|
||||
@@ -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<RedisRateLimiter.Config> {
|
||||
@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<List<Long>> script;
|
||||
|
||||
public RedisRateLimiter(ReactiveRedisTemplate<String, String> redisTemplate,
|
||||
RedisScript<List<Long>> script) {
|
||||
RedisScript<List<Long>> 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<Response> isAllowed(String id, Tuple args) {
|
||||
public Mono<Response> 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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<C> extends AbstractConfigurable<C>
|
||||
implements RoutePredicateFactory<C> {
|
||||
|
||||
public AbstractRoutePredicateFactory(Class<C> configClass) {
|
||||
super(configClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfigurable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
throw new UnsupportedOperationException("apply(Tuple) not supported");
|
||||
}
|
||||
}
|
||||
@@ -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<AfterRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String DATETIME_KEY = "datetime";
|
||||
|
||||
public AfterRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Collections.singletonList(DATETIME_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
Object value = args.getValue(DATETIME_KEY);
|
||||
final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
|
||||
return apply(dateTime);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(ZonedDateTime dateTime) {
|
||||
public Predicate<ServerWebExchange> 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<BeforeRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String DATETIME_KEY = "datetime";
|
||||
|
||||
public BeforeRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Collections.singletonList(DATETIME_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
Object value = args.getValue(DATETIME_KEY);
|
||||
final ZonedDateTime dateTime = BetweenRoutePredicateFactory.getZonedDateTime(value);
|
||||
return apply(dateTime);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(ZonedDateTime dateTime) {
|
||||
public Predicate<ServerWebExchange> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<BetweenRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String DATETIME1_KEY = "datetime1";
|
||||
public static final String DATETIME2_KEY = "datetime2";
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> 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<ServerWebExchange> apply(ZonedDateTime dateTime1, ZonedDateTime dateTime2) {
|
||||
Assert.isTrue(dateTime1.isBefore(dateTime2), dateTime1 +
|
||||
" must be before " + dateTime2);
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> 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) {
|
||||
|
||||
@@ -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<CookieRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String NAME_KEY = "name";
|
||||
public static final String REGEXP_KEY = "regexp";
|
||||
|
||||
public CookieRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(NAME_KEY, REGEXP_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
String name = args.getString(NAME_KEY);
|
||||
String regexp = args.getString(REGEXP_KEY);
|
||||
return apply(name, regexp);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String name, String regexp) {
|
||||
public Predicate<ServerWebExchange> apply(Config config) {
|
||||
return exchange -> {
|
||||
List<HttpCookie> cookies = exchange.getRequest().getCookies().get(name);
|
||||
List<HttpCookie> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HeaderRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String HEADER_KEY = "header";
|
||||
public static final String REGEXP_KEY = "regexp";
|
||||
|
||||
public HeaderRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(HEADER_KEY, REGEXP_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
String header = args.getString(HEADER_KEY);
|
||||
String regexp = args.getString(REGEXP_KEY);
|
||||
return apply(header, regexp);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String header) {
|
||||
return apply(header, ".*");
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String header, String regexp) {
|
||||
public Predicate<ServerWebExchange> apply(Config config) {
|
||||
return exchange -> {
|
||||
List<String> values = exchange.getRequest().getHeaders().get(header);
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<HostRoutePredicateFactory.Config> {
|
||||
|
||||
private PathMatcher pathMatcher = new AntPathMatcher(".");
|
||||
|
||||
public HostRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
public void setPathMatcher(PathMatcher pathMatcher) {
|
||||
this.pathMatcher = pathMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Collections.singletonList(PATTERN_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
String pattern = args.getString(PATTERN_KEY);
|
||||
return apply(pattern);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String pattern) {
|
||||
public Predicate<ServerWebExchange> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MethodRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String METHOD_KEY = "method";
|
||||
|
||||
public MethodRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(METHOD_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
String method = args.getString(METHOD_KEY);
|
||||
return apply(method);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String method) {
|
||||
HttpMethod httpMethod = HttpMethod.resolve(method);
|
||||
return apply(httpMethod);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(HttpMethod httpMethod) {
|
||||
public Predicate<ServerWebExchange> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<PathRoutePredicateFactory.Config> {
|
||||
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<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Collections.singletonList(PATTERN_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
String unparsedPattern = args.getString(PATTERN_KEY);
|
||||
return apply(unparsedPattern);
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(String unparsedPattern) {
|
||||
PathPattern pattern;
|
||||
public Predicate<ServerWebExchange> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<QueryRoutePredicateFactory.Config> {
|
||||
|
||||
public static final String PARAM_KEY = "param";
|
||||
public static final String REGEXP_KEY = "regexp";
|
||||
|
||||
public QueryRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList(PARAM_KEY, REGEXP_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validateArgs() {
|
||||
public boolean validateFieldsExist() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> 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<ServerWebExchange> apply(String param, String regexp) {
|
||||
|
||||
public Predicate<ServerWebExchange> 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<String> values = exchange.getRequest().getQueryParams().get(param);
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<RemoteAddrRoutePredicateFactory.Config> {
|
||||
|
||||
private static final Log log = LogFactory.getLog(RemoteAddrRoutePredicateFactory.class);
|
||||
|
||||
public RemoteAddrRoutePredicateFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Tuple args) {
|
||||
validateMin(1, args);
|
||||
|
||||
List<IpSubnetFilterRule> 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<ServerWebExchange> apply(String... addrs) {
|
||||
Assert.notEmpty(addrs, "addrs must not be empty");
|
||||
|
||||
List<IpSubnetFilterRule> sources = new ArrayList<>();
|
||||
for (String addr : addrs) {
|
||||
addSource(sources, addr);
|
||||
}
|
||||
return apply(sources);
|
||||
@Override
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Collections.singletonList("sources");
|
||||
}
|
||||
|
||||
public Predicate<ServerWebExchange> apply(List<IpSubnetFilterRule> sources) {
|
||||
@NotNull
|
||||
private List<IpSubnetFilterRule> convert(List<String> values) {
|
||||
List<IpSubnetFilterRule> sources = new ArrayList<>();
|
||||
for (String arg : values) {
|
||||
addSource(sources, arg);
|
||||
}
|
||||
return sources;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ServerWebExchange> apply(Config config) {
|
||||
List<IpSubnetFilterRule> 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<String> sources = new ArrayList<>();
|
||||
|
||||
public List<String> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
public Config setSources(List<String> sources) {
|
||||
this.sources = sources;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Config setSources(String... sources) {
|
||||
this.sources = Arrays.asList(sources);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<C> extends ShortcutConfigurable, Configurable<C> {
|
||||
String PATTERN_KEY = "pattern";
|
||||
|
||||
@Deprecated //TODO: remove when apply(Tuple) is removed
|
||||
default boolean isConfigurable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// useful for javadsl
|
||||
default Predicate<ServerWebExchange> apply(Consumer<C> consumer) {
|
||||
C config = newConfig();
|
||||
consumer.accept(config);
|
||||
return apply(config);
|
||||
}
|
||||
|
||||
//TODO: remove after apply(Tuple) removed
|
||||
@Override
|
||||
default Class<C> 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<ServerWebExchange> apply(C config) {
|
||||
throw new UnsupportedOperationException("apply(C config) not implemented");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
Predicate<ServerWebExchange> apply(Tuple args);
|
||||
|
||||
default String name() {
|
||||
return NameUtils.normalizePredicateName(getClass());
|
||||
return NameUtils.normalizeRoutePredicateName(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<RoutePredicateFactory> 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<RoutePredicateFactory> predicates) {
|
||||
predicates.forEach(factory -> {
|
||||
String key = factory.name();
|
||||
@@ -124,21 +138,36 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa
|
||||
.build();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<GatewayFilter> loadGatewayFilters(String id, List<FilterDefinition> filterDefinitions) {
|
||||
List<GatewayFilter> 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<String, String> 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<String, Object> 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<String, String> args, SpelExpressionParser parser, BeanFactory beanFactory) {
|
||||
@SuppressWarnings("Duplicates")
|
||||
@Deprecated //TODO: remove after Tuple is removed
|
||||
/* for testing */ static Tuple getTuple(ShortcutConfigurable shortcutConf, Map<String, String> args, SpelExpressionParser parser, BeanFactory beanFactory) {
|
||||
TupleBuilder builder = TupleBuilder.tuple();
|
||||
|
||||
List<String> argNames = hasArguments.argNames();
|
||||
List<String> 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<String, String> 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<ServerWebExchange> 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<ServerWebExchange> lookup(RouteDefinition route, PredicateDefinition predicate) {
|
||||
RoutePredicateFactory<Object> factory = this.predicates.get(predicate.getName());
|
||||
if (factory == null) {
|
||||
throw new IllegalArgumentException("Unable to find RoutePredicateFactory with name " + predicate.getName());
|
||||
}
|
||||
Map<String, String> 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<String, Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<HystrixGatewayFilterFactory.Config> 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 <C, R extends RateLimiter<C>> RequestRateLimiterSpec rateLimiter(Class<R> rateLimiterType,
|
||||
Consumer<C> configConsumer) {
|
||||
R rateLimiter = getBean(rateLimiterType);
|
||||
C config = rateLimiter.newConfig();
|
||||
configConsumer.accept(config);
|
||||
rateLimiter.getConfig().put(routeBuilder.getId(), config);
|
||||
return this;
|
||||
}
|
||||
|
||||
public GatewayFilterSpec configure(Consumer<RequestRateLimiterGatewayFilterFactory.Config> 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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<C> implements Configurable<C> {
|
||||
private Class<C> configClass;
|
||||
|
||||
protected AbstractConfigurable(Class<C> configClass) {
|
||||
this.configClass = configClass;
|
||||
}
|
||||
|
||||
public Class<C> getConfigClass() {
|
||||
return configClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public C newConfig() {
|
||||
return BeanUtils.instantiateClass(this.configClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("configClass", configClass)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -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<C> extends AbstractConfigurable<C> implements StatefulConfigurable<C> {
|
||||
private Map<String, C> config = new HashMap<>();
|
||||
|
||||
protected AbstractStatefulConfigurable(Class<C> configClass) {
|
||||
super(configClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, C> getConfig() {
|
||||
return this.config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringCreator(this)
|
||||
.append("config", config)
|
||||
.append("configClass", getConfigClass())
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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)");
|
||||
|
||||
@@ -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<C> {
|
||||
Class<C> getConfigClass();
|
||||
C newConfig();
|
||||
}
|
||||
@@ -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<String, Object> 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> 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;
|
||||
}
|
||||
}
|
||||
@@ -30,11 +30,11 @@ public class NameUtils {
|
||||
return GENERATED_NAME_PREFIX + i;
|
||||
}
|
||||
|
||||
public static String normalizePredicateName(Class<? extends RoutePredicateFactory> clazz) {
|
||||
public static String normalizeRoutePredicateName(Class<? extends RoutePredicateFactory> clazz) {
|
||||
return clazz.getSimpleName().replace(RoutePredicateFactory.class.getSimpleName(), "");
|
||||
}
|
||||
|
||||
public static String normalizeFilterName(Class<? extends GatewayFilterFactory> clazz) {
|
||||
public static String normalizeFilterFactoryName(Class<? extends GatewayFilterFactory> clazz) {
|
||||
return clazz.getSimpleName().replace(GatewayFilterFactory.class.getSimpleName(), "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
int entryIdx = 0;
|
||||
for (Map.Entry<String, String> 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<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
// field order should be of size 1
|
||||
List<String> 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<String, Object> normalize(Map<String, String> args, ShortcutConfigurable shortcutConf,
|
||||
SpelExpressionParser parser, BeanFactory beanFactory);
|
||||
}
|
||||
|
||||
static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints, Map<String, String> 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<String> shortcutFieldOrder() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate supplied argument size against {@see #shortcutFieldOrder} size.
|
||||
* Useful for variable arg predicates.
|
||||
* @return
|
||||
*/
|
||||
@Deprecated
|
||||
default boolean validateFieldsExist() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<C> extends Configurable<C> {
|
||||
|
||||
Map<String, C> getConfig();
|
||||
}
|
||||
@@ -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<String, Object> 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<String, Object> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, String> fallbackcontroller(@RequestParam("a") String a) {
|
||||
return Collections.singletonMap("from", "fallbackcontroller");
|
||||
}
|
||||
|
||||
@RequestMapping("/fallbackcontroller2")
|
||||
public Map<String, String> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -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<Map> 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<String, Object> headers = getMap(response, "headers");
|
||||
assertThat(headers).doesNotContainKey("X-Request-Foo");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify(DURATION);
|
||||
.expectStatus().isOk()
|
||||
.expectBody(Map.class).consumeWith(result -> {
|
||||
Map<String, Object> headers = getMap(result.getResponseBody(), "headers");
|
||||
assertThat(headers).doesNotContainKey("X-Request-Foo");
|
||||
});
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
|
||||
@@ -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<ClientResponse> 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 { }
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Void> response = filterFactory.apply(args).filter(exchange, this.filterChain);
|
||||
RequestRateLimiterGatewayFilterFactory factory = this.context.getBean(RequestRateLimiterGatewayFilterFactory.class);
|
||||
GatewayFilter filter = factory.apply(config -> config.setKeyResolver(keyResolver));
|
||||
|
||||
Mono<Void> response = filter.filter(exchange, this.filterChain);
|
||||
response.subscribe(aVoid -> assertThat(exchange.getResponse().getStatusCode())
|
||||
.isEqualTo(expectedStatus));
|
||||
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -63,7 +63,7 @@ public class SetPathGatewayFilterFactoryTests {
|
||||
}
|
||||
|
||||
private void testRewriteFilter(String template, String actualPath, String expectedPath, HashMap<String, String> 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)
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
@@ -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<ClientResponse> 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 { }
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
private HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public Mono<Response> isAllowed(String routeId, String id) {
|
||||
return Mono.just(new RateLimiter.Response(true, Long.MAX_VALUE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<Object> getConfigClass() {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getConfig() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object newConfig() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<? extends KeyResolver> 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<String> resolve(ServerWebExchange exchange) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<String, Object> 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) {
|
||||
|
||||
@@ -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<ClientResponse> 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<ClientResponse> 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
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -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<ClientResponse> 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
|
||||
|
||||
@@ -26,7 +26,7 @@ import reactor.test.StepVerifier;
|
||||
public class RemoteAddrRoutePredicateFactoryTests extends BaseWebClientTests {
|
||||
|
||||
@Test
|
||||
public void pathRouteWorks() {
|
||||
public void remoteAddrWorks() {
|
||||
Mono<ClientResponse> 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<ClientResponse> result = webClient.get().uri("/nok/httpbin/").exchange();
|
||||
|
||||
StepVerifier
|
||||
|
||||
@@ -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<String> argNames() {
|
||||
public List<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("bean", "arg1");
|
||||
}
|
||||
};
|
||||
Map<String, String> 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");
|
||||
|
||||
@@ -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<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("bean", "arg1");
|
||||
}
|
||||
};
|
||||
Map<String, String> args = new HashMap<>();
|
||||
args.put("bean", "#{@foo}");
|
||||
args.put("arg1", "val1");
|
||||
Map<String, Object> 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<String> shortcutFieldOrder() {
|
||||
return Arrays.asList("values");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShortcutType shortcutType() {
|
||||
return ShortcutType.GATHER_LIST;
|
||||
}
|
||||
};
|
||||
Map<String, String> args = new HashMap<>();
|
||||
args.put("1", "#{@foo}");
|
||||
args.put("2", "val1");
|
||||
args.put("3", "val2");
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -11,4 +11,4 @@ spring:
|
||||
predicates:
|
||||
- Path=/headers
|
||||
filters:
|
||||
- AddRequestHeader=X-Request-Foo, Bar
|
||||
- AddRequestHeader=X-Request-Example, ValueA
|
||||
|
||||
@@ -12,4 +12,4 @@ spring:
|
||||
- Host=**.addrequestparameter.org
|
||||
- Path=/get
|
||||
filters:
|
||||
- AddRequestParameter=foo, bar
|
||||
- AddRequestParameter=example, ValueA
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<ServerResponse> testFunRouterFunction() {
|
||||
RouterFunction<ServerResponse> route = RouterFunctions.route(
|
||||
|
||||
@@ -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<Void> 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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user