diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java index 61d54bec9..b95721c36 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java @@ -25,8 +25,8 @@ import java.util.Optional; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.gateway.api.RouteLocator; -import org.springframework.cloud.gateway.api.RouteWriter; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionWriter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.factory.WebFilterFactory; import org.springframework.cloud.gateway.handler.FilteringWebHandler; @@ -58,22 +58,22 @@ public class GatewayEndpoint implements ApplicationEventPublisherAware {/*extend private static final Log log = LogFactory.getLog(GatewayEndpoint.class); - private RouteLocator routeLocator; + private RouteDefinitionLocator routeDefinitionLocator; private List globalFilters; private List webFilterFactories; private FilteringWebHandler filteringWebHandler; - private RouteWriter routeWriter; + private RouteDefinitionWriter routeDefinitionWriter; private ApplicationEventPublisher publisher; - public GatewayEndpoint(RouteLocator routeLocator, List globalFilters, + public GatewayEndpoint(RouteDefinitionLocator routeDefinitionLocator, List globalFilters, List webFilterFactories, FilteringWebHandler filteringWebHandler, - RouteWriter routeWriter) { + RouteDefinitionWriter routeDefinitionWriter) { //super("gateway"); - this.routeLocator = routeLocator; + this.routeDefinitionLocator = routeDefinitionLocator; this.globalFilters = globalFilters; this.webFilterFactories = webFilterFactories; this.filteringWebHandler = filteringWebHandler; - this.routeWriter = routeWriter; + this.routeDefinitionWriter = routeDefinitionWriter; } @Override @@ -119,7 +119,7 @@ public class GatewayEndpoint implements ApplicationEventPublisherAware {/*extend @GetMapping("/routes") public Mono> routes() { - return this.routeLocator.getRoutes().collectList(); + return this.routeDefinitionLocator.getRouteDefinitions().collectList(); } /* @@ -127,7 +127,7 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre */ @PostMapping("/routes/{id}") public Mono> save(@PathVariable String id, @RequestBody Mono route) { - return this.routeWriter.save(route.map(r -> { + return this.routeDefinitionWriter.save(route.map(r -> { r.setId(id); log.debug("Saving route: " + route); return r; @@ -138,14 +138,14 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre @DeleteMapping("/routes/{id}") public Mono> delete(@PathVariable String id) { - return this.routeWriter .delete(Mono.just(id)) + return this.routeDefinitionWriter.delete(Mono.just(id)) .then(() -> Mono.just(ResponseEntity.ok().build())) .otherwise(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build())); } @GetMapping("/routes/{id}") public Mono> route(@PathVariable String id) { - return this.routeLocator.getRoutes() + return this.routeDefinitionLocator.getRouteDefinitions() .filter(route -> route.getId().equals(id)) .singleOrEmpty() .map(route -> ResponseEntity.ok(route)) @@ -154,10 +154,11 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre @GetMapping("/routes/{id}/combinedfilters") public Map combinedfilters(@PathVariable String id) { - Mono route = this.routeLocator.getRoutes() + Mono route = this.routeDefinitionLocator.getRouteDefinitions() .filter(r -> r.getId().equals(id)) .singleOrEmpty(); Optional optional = Optional.ofNullable(route.block()); //TODO: remove block(); - return getNamesToOrders(this.filteringWebHandler.combineFiltersForRoute(optional)); + //FIXME: return getNamesToOrders(this.filteringWebHandler.combineFiltersForRoute(optional)); + return null; } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionLocator.java new file mode 100644 index 000000000..91ec3d495 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionLocator.java @@ -0,0 +1,29 @@ +/* + * 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.api; + +import org.springframework.cloud.gateway.model.RouteDefinition; +import reactor.core.publisher.Flux; + +/** + * @author Spencer Gibb + */ +public interface RouteDefinitionLocator { + + Flux getRouteDefinitions(); +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteWriter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionWriter.java similarity index 95% rename from spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteWriter.java rename to spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionWriter.java index 74afc843f..9e95316dc 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteWriter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionWriter.java @@ -23,7 +23,7 @@ import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public interface RouteWriter { +public interface RouteDefinitionWriter { Mono save(Mono route); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java index b98422984..ae5642e1d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java @@ -17,7 +17,7 @@ package org.springframework.cloud.gateway.api; -import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.cloud.gateway.model.Route; import reactor.core.publisher.Flux; /** @@ -25,5 +25,5 @@ import reactor.core.publisher.Flux; */ public interface RouteLocator { - Flux getRoutes(); + Flux getRoutes(); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index e6d054805..973bc0ce6 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -26,8 +26,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.cloud.gateway.actuate.GatewayEndpoint; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionWriter; import org.springframework.cloud.gateway.api.RouteLocator; -import org.springframework.cloud.gateway.api.RouteWriter; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter; import org.springframework.cloud.gateway.filter.NettyRoutingFilter; @@ -64,7 +65,8 @@ import org.springframework.cloud.gateway.handler.predicate.QueryRequestPredicate import org.springframework.cloud.gateway.handler.predicate.RemoteAddrRequestPredicateFactory; import org.springframework.cloud.gateway.handler.predicate.RequestPredicateFactory; import org.springframework.cloud.gateway.support.CachingRouteLocator; -import org.springframework.cloud.gateway.support.InMemoryRouteRepository; +import org.springframework.cloud.gateway.support.DefaultRouteLocator; +import org.springframework.cloud.gateway.support.InMemoryRouteDefinitionRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -105,23 +107,31 @@ public class GatewayAutoConfiguration { } @Bean - @ConditionalOnMissingBean(RouteLocator.class) - public RouteLocator routeLocator(GatewayProperties properties) { - //TODO: how to automatically apply CachingRouteLocator - return new CachingRouteLocator(new PropertiesRouteLocator(properties)); + // @ConditionalOnMissingBean(RouteDefinitionLocator.class) + public PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator(GatewayProperties properties) { + return new PropertiesRouteDefinitionLocator(properties); } @Bean - public FilteringWebHandler filteringWebHandler(GatewayProperties properties, List globalFilters, - List webFilterFactories) { - return new FilteringWebHandler(properties, globalFilters, webFilterFactories); + @ConditionalOnMissingBean + public RouteLocator defaultRouteLocator(GatewayProperties properties, List globalFilters, + List webFilterFactories, + List predicates, + RouteDefinitionLocator routeDefinitionLocator) { + return new CachingRouteLocator(new DefaultRouteLocator(properties, routeDefinitionLocator, + predicates, globalFilters, webFilterFactories)); + } + + @Bean + public FilteringWebHandler filteringWebHandler(GatewayProperties properties, + RouteLocator routeLocator) { + return new FilteringWebHandler(properties, routeLocator); } @Bean public RequestPredicateHandlerMapping requestPredicateHandlerMapping(FilteringWebHandler webHandler, - List predicates, RouteLocator routeLocator) { - return new RequestPredicateHandlerMapping(webHandler, predicates, routeLocator); + return new RequestPredicateHandlerMapping(webHandler, routeLocator); } // ConfigurationProperty beans @@ -288,8 +298,8 @@ public class GatewayAutoConfiguration { //TODO: control creation @Bean - public InMemoryRouteRepository inMemoryRouteRepository() { - return new InMemoryRouteRepository(); + public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() { + return new InMemoryRouteDefinitionRepository(); } @Configuration @@ -297,10 +307,10 @@ public class GatewayAutoConfiguration { protected static class GatewayActuatorConfiguration { @Bean - public GatewayEndpoint gatewayEndpoint(RouteLocator routeLocator, List globalFilters, + public GatewayEndpoint gatewayEndpoint(RouteDefinitionLocator routeDefinitionLocator, List globalFilters, List webFilterFactories, FilteringWebHandler filteringWebHandler, - RouteWriter routeWriter) { - return new GatewayEndpoint(routeLocator, globalFilters, webFilterFactories, filteringWebHandler, routeWriter); + RouteDefinitionWriter routeDefinitionWriter) { + return new GatewayEndpoint(routeDefinitionLocator, globalFilters, webFilterFactories, filteringWebHandler, routeDefinitionWriter); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java similarity index 77% rename from spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteLocator.java rename to spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java index 670bd1504..cb50662fb 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java @@ -18,23 +18,23 @@ package org.springframework.cloud.gateway.config; import org.springframework.cloud.gateway.model.RouteDefinition; -import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import reactor.core.publisher.Flux; /** * @author Spencer Gibb */ -public class PropertiesRouteLocator implements RouteLocator { +public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator { private final GatewayProperties properties; - public PropertiesRouteLocator(GatewayProperties properties) { + public PropertiesRouteDefinitionLocator(GatewayProperties properties) { this.properties = properties; } @Override - public Flux getRoutes() { + public Flux getRouteDefinitions() { return Flux.fromIterable(this.properties.getRoutes()); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java similarity index 91% rename from spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteLocator.java rename to spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java index ed212e801..4473ccd9f 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java @@ -20,7 +20,7 @@ package org.springframework.cloud.gateway.discovery; import java.net.URI; import org.springframework.cloud.client.discovery.DiscoveryClient; -import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.filter.factory.RewritePathWebFilterFactory; import org.springframework.cloud.gateway.handler.predicate.PathRequestPredicateFactory; import org.springframework.cloud.gateway.model.FilterDefinition; @@ -39,18 +39,18 @@ import reactor.core.publisher.Flux; * TODO: developer configuration, in zuul, this was opt out, should be opt in * @author Spencer Gibb */ -public class DiscoveryClientRouteLocator implements RouteLocator { +public class DiscoveryClientRouteDefinitionLocator implements RouteDefinitionLocator { private final DiscoveryClient discoveryClient; private final String routeIdPrefix; - public DiscoveryClientRouteLocator(DiscoveryClient discoveryClient) { + public DiscoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient) { this.discoveryClient = discoveryClient; this.routeIdPrefix = this.discoveryClient.getClass().getSimpleName() + "_"; } @Override - public Flux getRoutes() { + public Flux getRouteDefinitions() { return Flux.fromIterable(discoveryClient.getServices()) .map(serviceId -> { RouteDefinition routeDefinition = new RouteDefinition(); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java index 5bb0c7f46..bfe1b549a 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java @@ -21,6 +21,7 @@ import java.net.URI; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.gateway.model.Route; import org.springframework.cloud.gateway.model.RouteDefinition; import org.springframework.core.Ordered; import org.springframework.web.server.ServerWebExchange; @@ -48,13 +49,13 @@ public class RouteToRequestUrlFilter implements GlobalFilter, Ordered { @Override public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - RouteDefinition routeDefinition = getAttribute(exchange, GATEWAY_ROUTE_ATTR, RouteDefinition.class); - if (routeDefinition == null) { + Route route = getAttribute(exchange, GATEWAY_ROUTE_ATTR, Route.class); + if (route == null) { return chain.filter(exchange); } log.info("RouteToRequestUrlFilter start"); URI requestUrl = UriComponentsBuilder.fromHttpRequest(exchange.getRequest()) - .uri(routeDefinition.getUri()) + .uri(route.getUri()) .build(true) .toUri(); exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java index c1132d98a..d7e758cb4 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java @@ -17,38 +17,22 @@ package org.springframework.cloud.gateway.handler; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.stream.Collectors; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.cloud.gateway.filter.factory.WebFilterFactory; -import org.springframework.cloud.gateway.model.FilterDefinition; -import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.cloud.gateway.api.RouteLocator; import org.springframework.cloud.gateway.config.GatewayProperties; import org.springframework.cloud.gateway.filter.GlobalFilter; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.cloud.gateway.support.RefreshRoutesEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; -import org.springframework.tuple.Tuple; -import org.springframework.tuple.TupleBuilder; +import org.springframework.cloud.gateway.filter.factory.WebFilterFactory; +import org.springframework.cloud.gateway.model.Route; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import org.springframework.web.server.WebHandler; import org.springframework.web.server.handler.WebHandlerDecorator; -import static java.util.Collections.emptyList; import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; import reactor.core.publisher.Mono; @@ -65,202 +49,34 @@ public class FilteringWebHandler extends WebHandlerDecorator { protected final Log logger = LogFactory.getLog(getClass()); private final GatewayProperties gatewayProperties; - private final List globalFilters; - private final Map webFilterFactories = new HashMap<>(); + private final RouteLocator routeLocator; - private final ConcurrentMap> combinedFiltersForRoute = new ConcurrentHashMap<>(); - - public FilteringWebHandler(GatewayProperties gatewayProperties, List globalFilters, - List webFilterFactories) { - this(new EmptyWebHandler(), gatewayProperties, globalFilters, webFilterFactories); + public FilteringWebHandler(GatewayProperties gatewayProperties, RouteLocator routeLocator) { + this(new EmptyWebHandler(), gatewayProperties, routeLocator); } - public FilteringWebHandler(WebHandler targetHandler, GatewayProperties gatewayProperties, List globalFilters, - List webFilterFactories) { + public FilteringWebHandler(WebHandler targetHandler, GatewayProperties gatewayProperties, + RouteLocator routeLocator) { super(targetHandler); this.gatewayProperties = gatewayProperties; - this.globalFilters = initList(globalFilters); - webFilterFactories.forEach(factory -> this.webFilterFactories.put(factory.name(), factory)); - } - - private static List initList(List list) { - return (list != null ? list : emptyList()); - } - - /** - * Return a read-only list of the configured globalFilters. - */ - public List getGlobalFilters() { - return this.globalFilters; + this.routeLocator = routeLocator; } - - @EventListener(RefreshRoutesEvent.class) - /* for testing */ void handleRefresh() { + /* TODO: relocate @EventListener(RefreshRoutesEvent.class) + void handleRefresh() { this.combinedFiltersForRoute.clear(); - } + }*/ @Override public Mono handle(ServerWebExchange exchange) { - Optional route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); - List webFilters = combineFiltersForRoute(route); + Optional route = exchange.getAttribute(GATEWAY_ROUTE_ATTR); + List webFilters = route.get().getWebFilters(); logger.debug("Sorted webFilterFactories: "+ webFilters); return new DefaultWebFilterChain(webFilters, getDelegate()).filter(exchange); } - public List combineFiltersForRoute(Optional route) { - if (!route.isPresent()) { - return Collections.emptyList(); - } - List combinedFilters = this.combinedFiltersForRoute.computeIfAbsent( - route.get().getId(), s -> { - - //TODO: probably a java 8 stream way of doing this - List combined = new ArrayList<>(loadFilters(this.globalFilters)); - - //TODO: support option to apply defaults after route specific filters? - if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { - combined.addAll(loadWebFilters("defaultFilters", - this.gatewayProperties.getDefaultFilters())); - } - - if (route.isPresent() && !route.get().getFilters().isEmpty()) { - combined.addAll(loadWebFilters(route.get().getId(), route.get().getFilters())); - } - - AnnotationAwareOrderComparator.sort(combined); - return combined; - }); - return combinedFilters; - } - - private Collection loadFilters(List filters) { - return filters.stream() - .map(filter -> { - WebFilterAdapter webFilter = new WebFilterAdapter(filter); - if (filter instanceof Ordered) { - int order = ((Ordered) filter).getOrder(); - return new OrderedWebFilter(webFilter, order); - } - return webFilter; - }).collect(Collectors.toList()); - } - - private List loadWebFilters(String id, List filterDefinitions) { - List filters = filterDefinitions.stream() - .map(definition -> { - WebFilterFactory filter = this.webFilterFactories.get(definition.getName()); - if (filter == null) { - throw new IllegalArgumentException("Unable to find WebFilterFactory with name " + definition.getName()); - } - Map args = definition.getArgs(); - if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName()); - } - - //TODO: move Tuple building to common class, see RequestPredicateFactory.lookup - TupleBuilder builder = TupleBuilder.tuple(); - - List argNames = filter.argNames(); - if (!argNames.isEmpty()) { - // ensure size is the same for key replacement later - if (filter.validateArgs() && args.size() != argNames.size()) { - throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames - + " " + argNames + ". Found " + args.size() + " " + args + "'"); - } - } - - int entryIdx = 0; - for (Map.Entry entry : args.entrySet()) { - String key = entry.getKey(); - - // RequestPredicateFactory 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); - } - - builder.put(key, entry.getValue()); - entryIdx++; - } - - Tuple tuple = builder.build(); - - if (filter.validateArgs()) { - for (String name : argNames) { - if (!tuple.hasFieldName(name)) { - throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple); - } - } - } - - return filter.apply(tuple); - }) - .collect(Collectors.toList()); - - ArrayList ordered = new ArrayList<>(filters.size()); - for (int i = 0; i < filters.size(); i++) { - ordered.add(new OrderedWebFilter(filters.get(i), i+1)); - } - - return ordered; - } - - private static class WebFilterAdapter implements WebFilter { - - private final GlobalFilter delegate; - - public WebFilterAdapter(GlobalFilter delegate) { - this.delegate = delegate; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return this.delegate.filter(exchange, chain); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("WebFilterAdapter{"); - sb.append("delegate=").append(delegate); - sb.append('}'); - return sb.toString(); - } - } - - public class OrderedWebFilter implements WebFilter, Ordered { - - private final WebFilter delegate; - private final int order; - - public OrderedWebFilter(WebFilter delegate, int order) { - this.delegate = delegate; - this.order = order; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - return this.delegate.filter(exchange, chain); - } - - @Override - public int getOrder() { - return this.order; - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("OrderedWebFilter{"); - sb.append("delegate=").append(delegate); - sb.append(", order=").append(order); - sb.append('}'); - return sb.toString(); - } - } - private static class DefaultWebFilterChain implements WebFilterChain { private int index; diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java index b39faaf5b..bd46d8e86 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java @@ -17,21 +17,11 @@ package org.springframework.cloud.gateway.handler; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.springframework.cloud.gateway.api.RouteLocator; -import org.springframework.cloud.gateway.handler.predicate.RequestPredicateFactory; -import org.springframework.cloud.gateway.model.PredicateDefinition; -import org.springframework.cloud.gateway.model.RouteDefinition; -import org.springframework.cloud.gateway.support.NameUtils; -import org.springframework.tuple.Tuple; -import org.springframework.tuple.TupleBuilder; import org.springframework.cloud.gateway.handler.support.ExchangeServerRequest; -import org.springframework.web.reactive.function.server.RequestPredicate; +import org.springframework.cloud.gateway.model.Route; import org.springframework.web.reactive.handler.AbstractHandlerMapping; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebHandler; @@ -46,30 +36,13 @@ import reactor.core.publisher.Mono; */ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping { - private final RouteLocator routeLocator; private final WebHandler webHandler; - private final Map requestPredicates = new LinkedHashMap<>(); - //TODO: define semeantics for refresh (ie clearing and recalculating combinedPredicates) - private final Map combinedPredicates = new ConcurrentHashMap<>(); + private final RouteLocator routeLocator; - public RequestPredicateHandlerMapping(WebHandler webHandler, List requestPredicates, - RouteLocator routeLocator) { + public RequestPredicateHandlerMapping(WebHandler webHandler, RouteLocator routeLocator) { this.webHandler = webHandler; this.routeLocator = routeLocator; - requestPredicates.forEach(factory -> { - String key = factory.name(); - if (this.requestPredicates.containsKey(key)) { - this.logger.warn("A RequestPredicateFactory named "+ key - + " already exists, class: " + this.requestPredicates.get(key) - + ". It will be overwritten."); - } - this.requestPredicates.put(key, factory); - if (logger.isInfoEnabled()) { - logger.info("Loaded RequestPredicateFactory [" + key + "]"); - } - }); - setOrder(1); } @@ -79,7 +52,7 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping { return lookupRoute(exchange) .log("TRACE") - .then((Function>) r -> { + .then((Function>) r -> { if (logger.isDebugEnabled()) { logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r); } @@ -105,18 +78,17 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping { } - protected Mono lookupRoute(ServerWebExchange exchange) { + protected Mono lookupRoute(ServerWebExchange exchange) { return this.routeLocator.getRoutes() - .map(this::getRouteCombinedPredicates) - .filter(rcp -> rcp.combinedPredicate.test(new ExchangeServerRequest(exchange))) + .filter(route -> route.getRequestPredicate().test(new ExchangeServerRequest(exchange))) .next() //TODO: error handling - .map(rcp -> { + .map(route -> { if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition matched: " + rcp.routeDefinition.getId()); + logger.debug("RouteDefinition matched: " + route.getId()); } - validateRoute(rcp.routeDefinition, exchange); - return rcp.routeDefinition; + validateRoute(route, exchange); + return route; }); /* TODO: trace logging @@ -125,98 +97,16 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping { }*/ } - private RouteCombinedPredicates getRouteCombinedPredicates(RouteDefinition routeDefinition) { - RequestPredicate predicate = this.combinedPredicates - .computeIfAbsent(routeDefinition.getId(), k -> combinePredicates(routeDefinition)); - - return new RouteCombinedPredicates(routeDefinition, predicate); - } - - private class RouteCombinedPredicates { - private RouteDefinition routeDefinition; - private RequestPredicate combinedPredicate; - - public RouteCombinedPredicates(RouteDefinition routeDefinition, RequestPredicate combinedPredicate) { - this.routeDefinition = routeDefinition; - this.combinedPredicate = combinedPredicate; - } - } - - private RequestPredicate combinePredicates(RouteDefinition routeDefinition) { - List predicates = routeDefinition.getPredicates(); - RequestPredicate predicate = lookup(routeDefinition, predicates.get(0)); - - for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { - RequestPredicate found = lookup(routeDefinition, andPredicate); - predicate = predicate.and(found); - } - - return predicate; - } - - //TODO: decouple from HandlerMapping? - private RequestPredicate lookup(RouteDefinition routeDefinition, PredicateDefinition predicate) { - RequestPredicateFactory found = this.requestPredicates.get(predicate.getName()); - if (found == null) { - throw new IllegalArgumentException("Unable to find RequestPredicateFactory with name " + predicate.getName()); - } - Map args = predicate.getArgs(); - if (logger.isDebugEnabled()) { - logger.debug("RouteDefinition " + routeDefinition.getId() + " applying " - + args + " to " + predicate.getName()); - } - - TupleBuilder builder = TupleBuilder.tuple(); - - List argNames = found.argNames(); - if (!argNames.isEmpty()) { - if (!argNames.isEmpty()) { - // ensure size is the same for key replacement later - if (found.validateArgs() && args.size() != argNames.size()) { - throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames - + " " + argNames + ". Found " + args.size() + " " + args + "'"); - } - } - } - - int entryIdx = 0; - for (Map.Entry entry : args.entrySet()) { - String key = entry.getKey(); - - // RequestPredicateFactory 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); - } - - builder.put(key, entry.getValue()); - entryIdx++; - } - - Tuple tuple = builder.build(); - - if (found.validateArgs()) { - for (String name : argNames) { - if (!tuple.hasFieldName(name)) { - throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple); - } - } - } - - return found.apply(tuple); - } - /** * Validate the given handler against the current request. *

The default implementation is empty. Can be overridden in subclasses, * for example to enforce specific preconditions expressed in URL mappings. - * @param routeDefinition the RouteDefinition object to validate + * @param route the Route object to validate * @param exchange current exchange * @throws Exception if validation failed */ @SuppressWarnings("UnusedParameters") - protected void validateRoute(RouteDefinition routeDefinition, ServerWebExchange exchange) { + protected void validateRoute(Route route, ServerWebExchange exchange) { } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/model/Route.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/model/Route.java new file mode 100644 index 000000000..a654a6614 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/model/Route.java @@ -0,0 +1,147 @@ +/* + * 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.model; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.springframework.util.Assert; +import org.springframework.web.reactive.function.server.RequestPredicate; +import org.springframework.web.server.WebFilter; + +/** + * @author Spencer Gibb + */ +public class Route { + + private final String id; + + private final URI uri; + + private final RequestPredicate requestPredicate; + + private final List webFilters; + + public static Builder builder() { + return new Builder(); + } + + public static Builder builder(RouteDefinition routeDefinition) { + return new Builder() + .id(routeDefinition.getId()) + .uri(routeDefinition.getUri()); + } + + public Route(String id, URI uri, RequestPredicate requestPredicate, List webFilters) { + this.id = id; + this.uri = uri; + this.requestPredicate = requestPredicate; + this.webFilters = webFilters; + } + + public static class Builder { + private String id; + + private URI uri; + + private RequestPredicate requestPredicate; + + private List webFilters = new ArrayList<>(); + + private Builder() {} + + public Builder id(String id) { + this.id = id; + return this; + } + + public Builder uri(URI uri) { + this.uri = uri; + return this; + } + + public Builder requestPredicate(RequestPredicate requestPredicate) { + this.requestPredicate = requestPredicate; + return this; + } + + public Builder webFilters(List webFilters) { + this.webFilters = webFilters; + return this; + } + + public Builder add(WebFilter webFilter) { + this.webFilters.add(webFilter); + return this; + } + + public Route build() { + Assert.notNull(this.id, "id can not be null"); + Assert.notNull(this.uri, "uri can not be null"); + //TODO: Assert.notNull(this.requestPredicate, "requestPredicates can not be null"); + + return new Route(this.id, this.uri, this.requestPredicate, this.webFilters); + } + } + + public String getId() { + return this.id; + } + + public URI getUri() { + return this.uri; + } + + public RequestPredicate getRequestPredicate() { + return this.requestPredicate; + } + + public List getWebFilters() { + return Collections.unmodifiableList(this.webFilters); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Route route = (Route) o; + return Objects.equals(id, route.id) && + Objects.equals(uri, route.uri) && + Objects.equals(requestPredicate, route.requestPredicate) && + Objects.equals(webFilters, route.webFilters); + } + + @Override + public int hashCode() { + return Objects.hash(id, uri, requestPredicate, webFilters); + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("Route{"); + sb.append("id='").append(id).append('\''); + sb.append(", uri=").append(uri); + sb.append(", requestPredicates=").append(requestPredicate); + sb.append(", webFilters=").append(webFilters); + sb.append('}'); + return sb.toString(); + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteDefinitionLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteDefinitionLocator.java new file mode 100644 index 000000000..a6c4b9d65 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteDefinitionLocator.java @@ -0,0 +1,63 @@ +/* + * 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 java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; +import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.context.event.EventListener; +import reactor.core.publisher.Flux; + +/** + * @author Spencer Gibb + */ +public class CachingRouteDefinitionLocator implements RouteDefinitionLocator { + + private final RouteDefinitionLocator delegate; + private final AtomicReference> cachedRoutes = new AtomicReference<>(); + + public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) { + this.delegate = delegate; + this.cachedRoutes.compareAndSet(null, collectRoutes()); + } + + @Override + public Flux getRouteDefinitions() { + return Flux.fromIterable(this.cachedRoutes.get()); + } + + /** + * Sets the new routes + * @return old routes + */ + public Flux refresh() { + return Flux.fromIterable(this.cachedRoutes.getAndUpdate( + routes -> CachingRouteDefinitionLocator.this.collectRoutes())); + } + + private List collectRoutes() { + return this.delegate.getRouteDefinitions().collectList().block(); + } + + @EventListener(RefreshRoutesEvent.class) + /* for testing */ void handleRefresh() { + refresh(); + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java index ca45fc82b..453028769 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java @@ -20,9 +20,12 @@ package org.springframework.cloud.gateway.support; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.model.Route; +import org.springframework.cloud.gateway.model.RouteDefinition; import org.springframework.context.event.EventListener; + import reactor.core.publisher.Flux; /** @@ -31,7 +34,7 @@ import reactor.core.publisher.Flux; public class CachingRouteLocator implements RouteLocator { private final RouteLocator delegate; - private final AtomicReference> cachedRoutes = new AtomicReference<>(); + private final AtomicReference> cachedRoutes = new AtomicReference<>(); public CachingRouteLocator(RouteLocator delegate) { this.delegate = delegate; @@ -39,7 +42,7 @@ public class CachingRouteLocator implements RouteLocator { } @Override - public Flux getRoutes() { + public Flux getRoutes() { return Flux.fromIterable(this.cachedRoutes.get()); } @@ -47,12 +50,12 @@ public class CachingRouteLocator implements RouteLocator { * Sets the new routes * @return old routes */ - public Flux refresh() { + public Flux refresh() { return Flux.fromIterable(this.cachedRoutes.getAndUpdate( routes -> CachingRouteLocator.this.collectRoutes())); } - private List collectRoutes() { + private List collectRoutes() { return this.delegate.getRoutes().collectList().block(); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteDefinitionLocator.java similarity index 67% rename from spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteLocator.java rename to spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteDefinitionLocator.java index b4e378d72..af5a4ca3f 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteLocator.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteDefinitionLocator.java @@ -17,23 +17,23 @@ package org.springframework.cloud.gateway.support; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.model.RouteDefinition; -import org.springframework.cloud.gateway.api.RouteLocator; import reactor.core.publisher.Flux; /** * @author Spencer Gibb */ -public class CompositeRouteLocator implements RouteLocator { +public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator { - private final Flux delegates; + private final Flux delegates; - public CompositeRouteLocator(Flux delegates) { + public CompositeRouteDefinitionLocator(Flux delegates) { this.delegates = delegates; } @Override - public Flux getRoutes() { - return this.delegates.flatMap(RouteLocator::getRoutes); + public Flux getRouteDefinitions() { + return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/DefaultRouteLocator.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/DefaultRouteLocator.java new file mode 100644 index 000000000..4580686e8 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/DefaultRouteLocator.java @@ -0,0 +1,334 @@ +/* + * 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.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; +import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.config.GatewayProperties; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.filter.factory.WebFilterFactory; +import org.springframework.cloud.gateway.handler.FilteringWebHandler; +import org.springframework.cloud.gateway.handler.predicate.RequestPredicateFactory; +import org.springframework.cloud.gateway.model.FilterDefinition; +import org.springframework.cloud.gateway.model.PredicateDefinition; +import org.springframework.cloud.gateway.model.Route; +import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.tuple.Tuple; +import org.springframework.tuple.TupleBuilder; +import org.springframework.web.reactive.function.server.RequestPredicate; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; + +/** + * @author Spencer Gibb + */ +public class DefaultRouteLocator implements RouteLocator { + protected final Log logger = LogFactory.getLog(getClass()); + + private final GatewayProperties gatewayProperties; + private final RouteDefinitionLocator routeDefinitionLocator; + private final Map requestPredicates = new LinkedHashMap<>(); + private final List globalFilters; + private final Map webFilterFactories = new HashMap<>(); + + public DefaultRouteLocator(GatewayProperties gatewayProperties, + RouteDefinitionLocator routeDefinitionLocator, + List requestPredicates, + List globalFilters, + List webFilterFactories) { + this.gatewayProperties = gatewayProperties; + this.routeDefinitionLocator = routeDefinitionLocator; + this.globalFilters = initList(globalFilters); + initFactories(requestPredicates); + webFilterFactories.forEach(factory -> this.webFilterFactories.put(factory.name(), factory)); + } + + private static List initList(List list) { + return (list != null ? list : emptyList()); + } + + private void initFactories(List requestPredicates) { + requestPredicates.forEach(factory -> { + String key = factory.name(); + if (this.requestPredicates.containsKey(key)) { + this.logger.warn("A RequestPredicateFactory named "+ key + + " already exists, class: " + this.requestPredicates.get(key) + + ". It will be overwritten."); + } + this.requestPredicates.put(key, factory); + if (logger.isInfoEnabled()) { + logger.info("Loaded RequestPredicateFactory [" + key + "]"); + } + }); + } + + @Override + public Flux getRoutes() { + return this.routeDefinitionLocator.getRouteDefinitions() + .map(this::convertToRoute) + //TODO: error handling + .map(route -> { + if (logger.isDebugEnabled()) { + logger.debug("RouteDefinition matched: " + route.getId()); + } + return route; + }); + + + /* TODO: trace logging + if (logger.isTraceEnabled()) { + logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); + }*/ + } + + + private Route convertToRoute(RouteDefinition routeDefinition) { + RequestPredicate requestPredicate = combinePredicates(routeDefinition); + List webFilters = getFilters(routeDefinition); + + return Route.builder(routeDefinition) + .requestPredicate(requestPredicate) + .webFilters(webFilters) + .build(); + } + + private Collection loadFilters(List filters) { + return filters.stream() + .map(filter -> { + WebFilterAdapter webFilter = new WebFilterAdapter(filter); + if (filter instanceof Ordered) { + int order = ((Ordered) filter).getOrder(); + return new OrderedWebFilter(webFilter, order); + } + return webFilter; + }).collect(Collectors.toList()); + } + + private List loadWebFilters(String id, List filterDefinitions) { + List filters = filterDefinitions.stream() + .map(definition -> { + WebFilterFactory filter = this.webFilterFactories.get(definition.getName()); + if (filter == null) { + throw new IllegalArgumentException("Unable to find WebFilterFactory with name " + definition.getName()); + } + Map args = definition.getArgs(); + if (logger.isDebugEnabled()) { + logger.debug("RouteDefinition " + id + " applying filter " + args + " to " + definition.getName()); + } + + //TODO: move Tuple building to common class, see RequestPredicateFactory.lookup + TupleBuilder builder = TupleBuilder.tuple(); + + List argNames = filter.argNames(); + if (!argNames.isEmpty()) { + // ensure size is the same for key replacement later + if (filter.validateArgs() && args.size() != argNames.size()) { + throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames + + " " + argNames + ". Found " + args.size() + " " + args + "'"); + } + } + + int entryIdx = 0; + for (Map.Entry entry : args.entrySet()) { + String key = entry.getKey(); + + // RequestPredicateFactory 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); + } + + builder.put(key, entry.getValue()); + entryIdx++; + } + + Tuple tuple = builder.build(); + + if (filter.validateArgs()) { + for (String name : argNames) { + if (!tuple.hasFieldName(name)) { + throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple); + } + } + } + + return filter.apply(tuple); + }) + .collect(Collectors.toList()); + + ArrayList ordered = new ArrayList<>(filters.size()); + for (int i = 0; i < filters.size(); i++) { + ordered.add(new OrderedWebFilter(filters.get(i), i+1)); + } + + return ordered; + } + + private List getFilters(RouteDefinition routeDefinition) { + //TODO: probably a java 8 stream way of doing this + List combined = new ArrayList<>(loadFilters(this.globalFilters)); + + //TODO: support option to apply defaults after route specific filters? + if (!this.gatewayProperties.getDefaultFilters().isEmpty()) { + combined.addAll(loadWebFilters("defaultFilters", + this.gatewayProperties.getDefaultFilters())); + } + + if (!routeDefinition.getFilters().isEmpty()) { + combined.addAll(loadWebFilters(routeDefinition.getId(), routeDefinition.getFilters())); + } + + AnnotationAwareOrderComparator.sort(combined); + return combined; + } + + private RequestPredicate combinePredicates(RouteDefinition routeDefinition) { + List predicates = routeDefinition.getPredicates(); + RequestPredicate predicate = lookup(routeDefinition, predicates.get(0)); + + for (PredicateDefinition andPredicate : predicates.subList(1, predicates.size())) { + RequestPredicate found = lookup(routeDefinition, andPredicate); + predicate = predicate.and(found); + } + + return predicate; + } + + private RequestPredicate lookup(RouteDefinition routeDefinition, PredicateDefinition predicate) { + RequestPredicateFactory found = this.requestPredicates.get(predicate.getName()); + if (found == null) { + throw new IllegalArgumentException("Unable to find RequestPredicateFactory with name " + predicate.getName()); + } + Map args = predicate.getArgs(); + if (logger.isDebugEnabled()) { + logger.debug("RouteDefinition " + routeDefinition.getId() + " applying " + + args + " to " + predicate.getName()); + } + + TupleBuilder builder = TupleBuilder.tuple(); + + List argNames = found.argNames(); + if (!argNames.isEmpty()) { + if (!argNames.isEmpty()) { + // ensure size is the same for key replacement later + if (found.validateArgs() && args.size() != argNames.size()) { + throw new IllegalArgumentException("Wrong number of arguments. Expected " + argNames + + " " + argNames + ". Found " + args.size() + " " + args + "'"); + } + } + } + + int entryIdx = 0; + for (Map.Entry entry : args.entrySet()) { + String key = entry.getKey(); + + // RequestPredicateFactory 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); + } + + builder.put(key, entry.getValue()); + entryIdx++; + } + + Tuple tuple = builder.build(); + + if (found.validateArgs()) { + for (String name : argNames) { + if (!tuple.hasFieldName(name)) { + throw new IllegalArgumentException("Missing argument '" + name + "'. Given " + tuple); + } + } + } + + return found.apply(tuple); + } + + + private static class WebFilterAdapter implements WebFilter { + + private final GlobalFilter delegate; + + public WebFilterAdapter(GlobalFilter delegate) { + this.delegate = delegate; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + return this.delegate.filter(exchange, chain); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("WebFilterAdapter{"); + sb.append("delegate=").append(delegate); + sb.append('}'); + return sb.toString(); + } + } + + public class OrderedWebFilter implements WebFilter, Ordered { + + private final WebFilter delegate; + private final int order; + + public OrderedWebFilter(WebFilter delegate, int order) { + this.delegate = delegate; + this.order = order; + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + return this.delegate.filter(exchange, chain); + } + + @Override + public int getOrder() { + return this.order; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("OrderedWebFilter{"); + sb.append("delegate=").append(delegate); + sb.append(", order=").append(order); + sb.append('}'); + return sb.toString(); + } + } +} diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteRepository.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteDefinitionRepository.java similarity index 84% rename from spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteRepository.java rename to spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteDefinitionRepository.java index 90bbf7a7b..d3da4b881 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteRepository.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteDefinitionRepository.java @@ -20,9 +20,9 @@ package org.springframework.cloud.gateway.support; import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.model.RouteDefinition; -import org.springframework.cloud.gateway.api.RouteLocator; -import org.springframework.cloud.gateway.api.RouteWriter; +import org.springframework.cloud.gateway.api.RouteDefinitionWriter; import static java.util.Collections.synchronizedMap; @@ -32,7 +32,7 @@ import reactor.core.publisher.Mono; /** * @author Spencer Gibb */ -public class InMemoryRouteRepository implements RouteLocator, RouteWriter { +public class InMemoryRouteDefinitionRepository implements RouteDefinitionLocator, RouteDefinitionWriter { private final Map routes = synchronizedMap(new LinkedHashMap()); @@ -56,7 +56,7 @@ public class InMemoryRouteRepository implements RouteLocator, RouteWriter { } @Override - public Flux getRoutes() { + public Flux getRouteDefinitions() { return Flux.fromIterable(routes.values()); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveNonProxyHeadersWebFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveNonProxyHeadersWebFilterFactoryTests.java index e51b0e957..23562f6df 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveNonProxyHeadersWebFilterFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveNonProxyHeadersWebFilterFactoryTests.java @@ -26,6 +26,7 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.EnableGateway; import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Import; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit4.SpringRunner; @@ -69,7 +70,7 @@ public class RemoveNonProxyHeadersWebFilterFactoryTests extends BaseWebClientTes @EnableAutoConfiguration @SpringBootConfiguration - @EnableGateway + @Import(DefaultTestConfig.class) public static class TestConfig { } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRequestPredicateFactoryTests.java similarity index 97% rename from spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateIntegrationTests.java rename to spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRequestPredicateFactoryTests.java index 2fe27ccff..114836095 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateIntegrationTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRequestPredicateFactoryTests.java @@ -41,7 +41,7 @@ import reactor.test.StepVerifier; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext -public class HostRoutePredicateIntegrationTests extends BaseWebClientTests { +public class HostRequestPredicateFactoryTests extends BaseWebClientTests { @Test public void hostRouteWorks() { diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java index 201f65fdd..7caa8c5be 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java @@ -37,7 +37,7 @@ import org.springframework.cloud.gateway.filter.factory.SetStatusWebFilterFactor import org.springframework.cloud.gateway.handler.predicate.AfterRequestPredicateFactoryTests; import org.springframework.cloud.gateway.handler.predicate.BeforeRequestPredicateFactoryTests; import org.springframework.cloud.gateway.handler.predicate.BetweenRequestPredicateFactoryTests; -import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateIntegrationTests; +import org.springframework.cloud.gateway.handler.predicate.HostRequestPredicateFactoryTests; import org.springframework.cloud.gateway.handler.predicate.MethodRequestPredicateFactoryTests; import org.springframework.cloud.gateway.handler.predicate.PathRequestPredicateFactoryTests; @@ -66,7 +66,7 @@ import org.springframework.cloud.gateway.handler.predicate.PathRequestPredicateF AfterRequestPredicateFactoryTests.class, BeforeRequestPredicateFactoryTests.class, BetweenRequestPredicateFactoryTests.class, - HostRoutePredicateIntegrationTests.class, + HostRequestPredicateFactoryTests.class, MethodRequestPredicateFactoryTests.class, PathRequestPredicateFactoryTests.class, }) diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java index cf899db7a..7861c3132 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java @@ -28,13 +28,19 @@ import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.springframework.boot.web.server.LocalServerPort; import org.springframework.cloud.gateway.EnableGateway; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; +import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator; import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.cloud.gateway.model.Route; import org.springframework.cloud.gateway.model.RouteDefinition; +import org.springframework.cloud.gateway.support.CompositeRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.InMemoryRouteDefinitionRepository; import org.springframework.cloud.netflix.ribbon.RibbonClient; import org.springframework.cloud.netflix.ribbon.RibbonClients; import org.springframework.cloud.netflix.ribbon.StaticServerList; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.core.annotation.Order; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -50,6 +56,7 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.G import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR; import static org.springframework.cloud.gateway.test.TestUtils.parseMultipart; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** @@ -156,6 +163,13 @@ public class BaseWebClientTests { }); } + @Bean + @Primary + public RouteDefinitionLocator routeDefinitionLocator(InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository, + PropertiesRouteDefinitionLocator propertiesRouteDefinitionLocator) { + return new CompositeRouteDefinitionLocator(Flux.just(inMemoryRouteDefinitionRepository, propertiesRouteDefinitionLocator)); + } + @Bean @Order(500) public GlobalFilter modifyResponseFilter() { @@ -163,9 +177,9 @@ public class BaseWebClientTests { log.info("modifyResponseFilter start"); String value = (String) exchange.getAttribute(GATEWAY_HANDLER_MAPPER_ATTR).orElse("N/A"); exchange.getResponse().getHeaders().add(HANDLER_MAPPER_HEADER, value); - RouteDefinition routeDefinition = (RouteDefinition) exchange.getAttribute(GATEWAY_ROUTE_ATTR).orElse(null); - if (routeDefinition != null) { - exchange.getResponse().getHeaders().add(ROUTE_ID_HEADER, routeDefinition.getId()); + Route route = (Route) exchange.getAttribute(GATEWAY_ROUTE_ATTR).orElse(null); + if (route != null) { + exchange.getResponse().getHeaders().add(ROUTE_ID_HEADER, route.getId()); } return chain.filter(exchange); }; diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java index b139b4703..b9a42650f 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java @@ -23,13 +23,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.gateway.EnableGateway; -import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.PropertiesRouteLocator; -import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteLocator; -import org.springframework.cloud.gateway.support.CachingRouteLocator; -import org.springframework.cloud.gateway.support.CompositeRouteLocator; -import org.springframework.cloud.gateway.support.InMemoryRouteRepository; +import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator; +import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.CachingRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.CompositeRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.InMemoryRouteDefinitionRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @@ -53,22 +53,22 @@ public class GatewayTestApplication { protected static class GatewayDiscoveryConfiguration { @Bean - public DiscoveryClientRouteLocator discoveryClientRouteLocator(DiscoveryClient discoveryClient) { - return new DiscoveryClientRouteLocator(discoveryClient); + public DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator(DiscoveryClient discoveryClient) { + return new DiscoveryClientRouteDefinitionLocator(discoveryClient); } @Bean - public PropertiesRouteLocator propertiesRouteLocator(GatewayProperties properties) { - return new PropertiesRouteLocator(properties); + public PropertiesRouteDefinitionLocator propertiesRouteLocator(GatewayProperties properties) { + return new PropertiesRouteDefinitionLocator(properties); } @Bean @Primary - public RouteLocator compositeRouteLocator(InMemoryRouteRepository inMemoryRouteRepository, - DiscoveryClientRouteLocator discoveryClientRouteLocator, - PropertiesRouteLocator propertiesRouteLocator) { - Flux flux = Flux.just(inMemoryRouteRepository, discoveryClientRouteLocator, propertiesRouteLocator); - return new CachingRouteLocator(new CompositeRouteLocator(flux)); + public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository, + DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator, + PropertiesRouteDefinitionLocator propertiesRouteLocator) { + Flux flux = Flux.just(inMemoryRouteRepository, discoveryClientRouteLocator, propertiesRouteLocator); + return new CachingRouteDefinitionLocator(new CompositeRouteDefinitionLocator(flux)); } } @@ -77,17 +77,17 @@ public class GatewayTestApplication { protected static class GatewayInMemoryConfiguration { @Bean - public PropertiesRouteLocator propertiesRouteLocator(GatewayProperties properties) { - return new PropertiesRouteLocator(properties); + public PropertiesRouteDefinitionLocator propertiesRouteLocator(GatewayProperties properties) { + return new PropertiesRouteDefinitionLocator(properties); } @Bean @Primary - public RouteLocator compositeRouteLocator(InMemoryRouteRepository inMemoryRouteRepository, - PropertiesRouteLocator propertiesRouteLocator) { - Flux flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator); - CompositeRouteLocator composite = new CompositeRouteLocator(flux); - return new CachingRouteLocator(composite); + public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository, + PropertiesRouteDefinitionLocator propertiesRouteLocator) { + Flux flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator); + CompositeRouteDefinitionLocator composite = new CompositeRouteDefinitionLocator(flux); + return new CachingRouteDefinitionLocator(composite); } } diff --git a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java index 945954bc9..1befe7c7d 100644 --- a/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java +++ b/spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java @@ -21,12 +21,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.cloud.gateway.EnableGateway; -import org.springframework.cloud.gateway.api.RouteLocator; +import org.springframework.cloud.gateway.api.RouteDefinitionLocator; import org.springframework.cloud.gateway.config.GatewayProperties; -import org.springframework.cloud.gateway.config.PropertiesRouteLocator; -import org.springframework.cloud.gateway.support.CachingRouteLocator; -import org.springframework.cloud.gateway.support.CompositeRouteLocator; -import org.springframework.cloud.gateway.support.InMemoryRouteRepository; +import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.CachingRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.CompositeRouteDefinitionLocator; +import org.springframework.cloud.gateway.support.InMemoryRouteDefinitionRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import reactor.core.publisher.Flux; @@ -39,17 +39,17 @@ import reactor.core.publisher.Flux; @EnableGateway public class GatewaySampleApplication { @Bean - public PropertiesRouteLocator propertiesRouteLocator(GatewayProperties properties) { - return new PropertiesRouteLocator(properties); + public PropertiesRouteDefinitionLocator propertiesRouteLocator(GatewayProperties properties) { + return new PropertiesRouteDefinitionLocator(properties); } @Bean @Primary - public RouteLocator compositeRouteLocator(InMemoryRouteRepository inMemoryRouteRepository, - PropertiesRouteLocator propertiesRouteLocator) { - Flux flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator); - CompositeRouteLocator composite = new CompositeRouteLocator(flux); - return new CachingRouteLocator(composite); + public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository, + PropertiesRouteDefinitionLocator propertiesRouteLocator) { + Flux flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator); + CompositeRouteDefinitionLocator composite = new CompositeRouteDefinitionLocator(flux); + return new CachingRouteDefinitionLocator(composite); } public static void main(String[] args) {