Browse Source

Move resolution of Factories to RouteLocator

pull/41/head
Spencer Gibb 8 years ago
parent
commit
0cb76f4513
No known key found for this signature in database
GPG Key ID: 7788A47380690861
  1. 29
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java
  2. 29
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionLocator.java
  3. 2
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionWriter.java
  4. 4
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java
  5. 42
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java
  6. 8
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java
  7. 8
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java
  8. 7
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java
  9. 212
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java
  10. 134
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java
  11. 147
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/model/Route.java
  12. 63
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteDefinitionLocator.java
  13. 13
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java
  14. 12
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteDefinitionLocator.java
  15. 334
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/DefaultRouteLocator.java
  16. 8
      spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteDefinitionRepository.java
  17. 3
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveNonProxyHeadersWebFilterFactoryTests.java
  18. 2
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRequestPredicateFactoryTests.java
  19. 4
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/AdhocTestSuite.java
  20. 20
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java
  21. 44
      spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java
  22. 24
      spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java

29
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java

@ -25,8 +25,8 @@ import java.util.Optional; @@ -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 @@ -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<GlobalFilter> globalFilters;
private List<WebFilterFactory> webFilterFactories;
private FilteringWebHandler filteringWebHandler;
private RouteWriter routeWriter;
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher publisher;
public GatewayEndpoint(RouteLocator routeLocator, List<GlobalFilter> globalFilters,
public GatewayEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
List<WebFilterFactory> 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 @@ -119,7 +119,7 @@ public class GatewayEndpoint implements ApplicationEventPublisherAware {/*extend
@GetMapping("/routes")
public Mono<List<RouteDefinition>> 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 @@ -127,7 +127,7 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre
*/
@PostMapping("/routes/{id}")
public Mono<ResponseEntity<Void>> save(@PathVariable String id, @RequestBody Mono<RouteDefinition> 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 @@ -138,14 +138,14 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre
@DeleteMapping("/routes/{id}")
public Mono<ResponseEntity<Object>> 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<ResponseEntity<RouteDefinition>> 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 @@ -154,10 +154,11 @@ http POST :8080/admin/gateway/routes/apiaddreqhead uri=http://httpbin.org:80 pre
@GetMapping("/routes/{id}/combinedfilters")
public Map<String, Object> combinedfilters(@PathVariable String id) {
Mono<RouteDefinition> route = this.routeLocator.getRoutes()
Mono<RouteDefinition> route = this.routeDefinitionLocator.getRouteDefinitions()
.filter(r -> r.getId().equals(id))
.singleOrEmpty();
Optional<RouteDefinition> optional = Optional.ofNullable(route.block()); //TODO: remove block();
return getNamesToOrders(this.filteringWebHandler.combineFiltersForRoute(optional));
//FIXME: return getNamesToOrders(this.filteringWebHandler.combineFiltersForRoute(optional));
return null;
}
}

29
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionLocator.java

@ -0,0 +1,29 @@ @@ -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<RouteDefinition> getRouteDefinitions();
}

2
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteWriter.java → spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteDefinitionWriter.java

@ -23,7 +23,7 @@ import reactor.core.publisher.Mono; @@ -23,7 +23,7 @@ import reactor.core.publisher.Mono;
/**
* @author Spencer Gibb
*/
public interface RouteWriter {
public interface RouteDefinitionWriter {
Mono<Void> save(Mono<RouteDefinition> route);

4
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/api/RouteLocator.java

@ -17,7 +17,7 @@ @@ -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; @@ -25,5 +25,5 @@ import reactor.core.publisher.Flux;
*/
public interface RouteLocator {
Flux<RouteDefinition> getRoutes();
Flux<Route> getRoutes();
}

42
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 @@ -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 @@ -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 { @@ -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<GlobalFilter> globalFilters,
List<WebFilterFactory> webFilterFactories) {
return new FilteringWebHandler(properties, globalFilters, webFilterFactories);
@ConditionalOnMissingBean
public RouteLocator defaultRouteLocator(GatewayProperties properties, List<GlobalFilter> globalFilters,
List<WebFilterFactory> webFilterFactories,
List<RequestPredicateFactory> 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<RequestPredicateFactory> predicates,
RouteLocator routeLocator) {
return new RequestPredicateHandlerMapping(webHandler, predicates, routeLocator);
return new RequestPredicateHandlerMapping(webHandler, routeLocator);
}
// ConfigurationProperty beans
@ -288,8 +298,8 @@ public class GatewayAutoConfiguration { @@ -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 { @@ -297,10 +307,10 @@ public class GatewayAutoConfiguration {
protected static class GatewayActuatorConfiguration {
@Bean
public GatewayEndpoint gatewayEndpoint(RouteLocator routeLocator, List<GlobalFilter> globalFilters,
public GatewayEndpoint gatewayEndpoint(RouteDefinitionLocator routeDefinitionLocator, List<GlobalFilter> globalFilters,
List<WebFilterFactory> webFilterFactories, FilteringWebHandler filteringWebHandler,
RouteWriter routeWriter) {
return new GatewayEndpoint(routeLocator, globalFilters, webFilterFactories, filteringWebHandler, routeWriter);
RouteDefinitionWriter routeDefinitionWriter) {
return new GatewayEndpoint(routeDefinitionLocator, globalFilters, webFilterFactories, filteringWebHandler, routeDefinitionWriter);
}
}

8
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteLocator.java → spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/PropertiesRouteDefinitionLocator.java

@ -18,23 +18,23 @@ @@ -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<RouteDefinition> getRoutes() {
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.properties.getRoutes());
}
}

8
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteLocator.java → spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/discovery/DiscoveryClientRouteDefinitionLocator.java

@ -20,7 +20,7 @@ package org.springframework.cloud.gateway.discovery; @@ -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; @@ -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<RouteDefinition> getRoutes() {
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(discoveryClient.getServices())
.map(serviceId -> {
RouteDefinition routeDefinition = new RouteDefinition();

7
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/RouteToRequestUrlFilter.java

@ -21,6 +21,7 @@ import java.net.URI; @@ -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 { @@ -48,13 +49,13 @@ public class RouteToRequestUrlFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> 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);

212
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/FilteringWebHandler.java

@ -17,38 +17,22 @@ @@ -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 { @@ -65,202 +49,34 @@ public class FilteringWebHandler extends WebHandlerDecorator {
protected final Log logger = LogFactory.getLog(getClass());
private final GatewayProperties gatewayProperties;
private final List<GlobalFilter> globalFilters;
private final Map<String, WebFilterFactory> webFilterFactories = new HashMap<>();
private final ConcurrentMap<String, List<WebFilter>> combinedFiltersForRoute = new ConcurrentHashMap<>();
private final RouteLocator routeLocator;
public FilteringWebHandler(GatewayProperties gatewayProperties, List<GlobalFilter> globalFilters,
List<WebFilterFactory> 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<GlobalFilter> globalFilters,
List<WebFilterFactory> 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 <T> List<T> initList(List<T> list) {
return (list != null ? list : emptyList());
}
/**
* Return a read-only list of the configured globalFilters.
*/
public List<GlobalFilter> 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<Void> handle(ServerWebExchange exchange) {
Optional<RouteDefinition> route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
List<WebFilter> webFilters = combineFiltersForRoute(route);
Optional<Route> route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
List<WebFilter> webFilters = route.get().getWebFilters();
logger.debug("Sorted webFilterFactories: "+ webFilters);
return new DefaultWebFilterChain(webFilters, getDelegate()).filter(exchange);
}
public List<WebFilter> combineFiltersForRoute(Optional<RouteDefinition> route) {
if (!route.isPresent()) {
return Collections.emptyList();
}
List<WebFilter> combinedFilters = this.combinedFiltersForRoute.computeIfAbsent(
route.get().getId(), s -> {
//TODO: probably a java 8 stream way of doing this
List<WebFilter> 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<WebFilter> loadFilters(List<GlobalFilter> 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<WebFilter> loadWebFilters(String id, List<FilterDefinition> filterDefinitions) {
List<WebFilter> 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<String, String> 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<String> 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<String, String> 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<WebFilter> 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<Void> 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<Void> 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;

134
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/RequestPredicateHandlerMapping.java

@ -17,21 +17,11 @@ @@ -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; @@ -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<String, RequestPredicateFactory> requestPredicates = new LinkedHashMap<>();
//TODO: define semeantics for refresh (ie clearing and recalculating combinedPredicates)
private final Map<String, RequestPredicate> combinedPredicates = new ConcurrentHashMap<>();
private final RouteLocator routeLocator;
public RequestPredicateHandlerMapping(WebHandler webHandler, List<RequestPredicateFactory> 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 { @@ -79,7 +52,7 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping {
return lookupRoute(exchange)
.log("TRACE")
.then((Function<RouteDefinition, Mono<?>>) r -> {
.then((Function<Route, Mono<?>>) r -> {
if (logger.isDebugEnabled()) {
logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
}
@ -105,18 +78,17 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping { @@ -105,18 +78,17 @@ public class RequestPredicateHandlerMapping extends AbstractHandlerMapping {
}
protected Mono<RouteDefinition> lookupRoute(ServerWebExchange exchange) {
protected Mono<Route> 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 { @@ -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<PredicateDefinition> 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<String, String> args = predicate.getArgs();
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + routeDefinition.getId() + " applying "
+ args + " to " + predicate.getName());
}
TupleBuilder builder = TupleBuilder.tuple();
List<String> 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<String, String> 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.
* <p>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) {
}
}

147
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/model/Route.java

@ -0,0 +1,147 @@ @@ -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<WebFilter> 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<WebFilter> 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<WebFilter> 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<WebFilter> 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<WebFilter> 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();
}
}

63
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteDefinitionLocator.java

@ -0,0 +1,63 @@ @@ -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<List<RouteDefinition>> cachedRoutes = new AtomicReference<>();
public CachingRouteDefinitionLocator(RouteDefinitionLocator delegate) {
this.delegate = delegate;
this.cachedRoutes.compareAndSet(null, collectRoutes());
}
@Override
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(this.cachedRoutes.get());
}
/**
* Sets the new routes
* @return old routes
*/
public Flux<RouteDefinition> refresh() {
return Flux.fromIterable(this.cachedRoutes.getAndUpdate(
routes -> CachingRouteDefinitionLocator.this.collectRoutes()));
}
private List<RouteDefinition> collectRoutes() {
return this.delegate.getRouteDefinitions().collectList().block();
}
@EventListener(RefreshRoutesEvent.class)
/* for testing */ void handleRefresh() {
refresh();
}
}

13
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CachingRouteLocator.java

@ -20,9 +20,12 @@ package org.springframework.cloud.gateway.support; @@ -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; @@ -31,7 +34,7 @@ import reactor.core.publisher.Flux;
public class CachingRouteLocator implements RouteLocator {
private final RouteLocator delegate;
private final AtomicReference<List<RouteDefinition>> cachedRoutes = new AtomicReference<>();
private final AtomicReference<List<Route>> cachedRoutes = new AtomicReference<>();
public CachingRouteLocator(RouteLocator delegate) {
this.delegate = delegate;
@ -39,7 +42,7 @@ public class CachingRouteLocator implements RouteLocator { @@ -39,7 +42,7 @@ public class CachingRouteLocator implements RouteLocator {
}
@Override
public Flux<RouteDefinition> getRoutes() {
public Flux<Route> getRoutes() {
return Flux.fromIterable(this.cachedRoutes.get());
}
@ -47,12 +50,12 @@ public class CachingRouteLocator implements RouteLocator { @@ -47,12 +50,12 @@ public class CachingRouteLocator implements RouteLocator {
* Sets the new routes
* @return old routes
*/
public Flux<RouteDefinition> refresh() {
public Flux<Route> refresh() {
return Flux.fromIterable(this.cachedRoutes.getAndUpdate(
routes -> CachingRouteLocator.this.collectRoutes()));
}
private List<RouteDefinition> collectRoutes() {
private List<Route> collectRoutes() {
return this.delegate.getRoutes().collectList().block();
}

12
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteLocator.java → spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/CompositeRouteDefinitionLocator.java

@ -17,23 +17,23 @@ @@ -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<RouteLocator> delegates;
private final Flux<RouteDefinitionLocator> delegates;
public CompositeRouteLocator(Flux<RouteLocator> delegates) {
public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
this.delegates = delegates;
}
@Override
public Flux<RouteDefinition> getRoutes() {
return this.delegates.flatMap(RouteLocator::getRoutes);
public Flux<RouteDefinition> getRouteDefinitions() {
return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
}
}

334
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/DefaultRouteLocator.java

@ -0,0 +1,334 @@ @@ -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<String, RequestPredicateFactory> requestPredicates = new LinkedHashMap<>();
private final List<GlobalFilter> globalFilters;
private final Map<String, WebFilterFactory> webFilterFactories = new HashMap<>();
public DefaultRouteLocator(GatewayProperties gatewayProperties,
RouteDefinitionLocator routeDefinitionLocator,
List<RequestPredicateFactory> requestPredicates,
List<GlobalFilter> globalFilters,
List<WebFilterFactory> webFilterFactories) {
this.gatewayProperties = gatewayProperties;
this.routeDefinitionLocator = routeDefinitionLocator;
this.globalFilters = initList(globalFilters);
initFactories(requestPredicates);
webFilterFactories.forEach(factory -> this.webFilterFactories.put(factory.name(), factory));
}
private static <T> List<T> initList(List<T> list) {
return (list != null ? list : emptyList());
}
private void initFactories(List<RequestPredicateFactory> 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<Route> 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<WebFilter> webFilters = getFilters(routeDefinition);
return Route.builder(routeDefinition)
.requestPredicate(requestPredicate)
.webFilters(webFilters)
.build();
}
private Collection<WebFilter> loadFilters(List<GlobalFilter> 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<WebFilter> loadWebFilters(String id, List<FilterDefinition> filterDefinitions) {
List<WebFilter> 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<String, String> 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<String> 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<String, String> 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<WebFilter> 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<WebFilter> getFilters(RouteDefinition routeDefinition) {
//TODO: probably a java 8 stream way of doing this
List<WebFilter> 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<PredicateDefinition> 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<String, String> args = predicate.getArgs();
if (logger.isDebugEnabled()) {
logger.debug("RouteDefinition " + routeDefinition.getId() + " applying "
+ args + " to " + predicate.getName());
}
TupleBuilder builder = TupleBuilder.tuple();
List<String> 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<String, String> 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<Void> 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<Void> 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();
}
}
}

8
spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteRepository.java → spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/InMemoryRouteDefinitionRepository.java

@ -20,9 +20,9 @@ package org.springframework.cloud.gateway.support; @@ -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; @@ -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<String, RouteDefinition> routes = synchronizedMap(new LinkedHashMap<String, RouteDefinition>());
@ -56,7 +56,7 @@ public class InMemoryRouteRepository implements RouteLocator, RouteWriter { @@ -56,7 +56,7 @@ public class InMemoryRouteRepository implements RouteLocator, RouteWriter {
}
@Override
public Flux<RouteDefinition> getRoutes() {
public Flux<RouteDefinition> getRouteDefinitions() {
return Flux.fromIterable(routes.values());
}
}

3
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; @@ -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 @@ -69,7 +70,7 @@ public class RemoveNonProxyHeadersWebFilterFactoryTests extends BaseWebClientTes
@EnableAutoConfiguration
@SpringBootConfiguration
@EnableGateway
@Import(DefaultTestConfig.class)
public static class TestConfig { }
}

2
spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRoutePredicateIntegrationTests.java → spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/HostRequestPredicateFactoryTests.java

@ -41,7 +41,7 @@ import reactor.test.StepVerifier; @@ -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() {

4
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 @@ -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 @@ -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,
})

20
spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/BaseWebClientTests.java

@ -28,13 +28,19 @@ import org.apache.commons.logging.LogFactory; @@ -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 @@ -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 { @@ -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 { @@ -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);
};

44
spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java

@ -23,13 +23,13 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; @@ -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 { @@ -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<RouteLocator> flux = Flux.just(inMemoryRouteRepository, discoveryClientRouteLocator, propertiesRouteLocator);
return new CachingRouteLocator(new CompositeRouteLocator(flux));
public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository,
DiscoveryClientRouteDefinitionLocator discoveryClientRouteLocator,
PropertiesRouteDefinitionLocator propertiesRouteLocator) {
Flux<RouteDefinitionLocator> flux = Flux.just(inMemoryRouteRepository, discoveryClientRouteLocator, propertiesRouteLocator);
return new CachingRouteDefinitionLocator(new CompositeRouteDefinitionLocator(flux));
}
}
@ -77,17 +77,17 @@ public class GatewayTestApplication { @@ -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<RouteLocator> flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator);
CompositeRouteLocator composite = new CompositeRouteLocator(flux);
return new CachingRouteLocator(composite);
public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository,
PropertiesRouteDefinitionLocator propertiesRouteLocator) {
Flux<RouteDefinitionLocator> flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator);
CompositeRouteDefinitionLocator composite = new CompositeRouteDefinitionLocator(flux);
return new CachingRouteDefinitionLocator(composite);
}
}

24
spring-cloud-gateway-sample/src/main/java/org/springframework/cloud/gateway/sample/GatewaySampleApplication.java

@ -21,12 +21,12 @@ import org.springframework.boot.SpringApplication; @@ -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; @@ -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<RouteLocator> flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator);
CompositeRouteLocator composite = new CompositeRouteLocator(flux);
return new CachingRouteLocator(composite);
public RouteDefinitionLocator compositeRouteLocator(InMemoryRouteDefinitionRepository inMemoryRouteRepository,
PropertiesRouteDefinitionLocator propertiesRouteLocator) {
Flux<RouteDefinitionLocator> flux = Flux.just(inMemoryRouteRepository, propertiesRouteLocator);
CompositeRouteDefinitionLocator composite = new CompositeRouteDefinitionLocator(flux);
return new CachingRouteDefinitionLocator(composite);
}
public static void main(String[] args) {

Loading…
Cancel
Save