diff --git a/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java b/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java index 25705d1b..92ecfd33 100644 --- a/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java +++ b/src/main/java/org/springframework/cloud/gateway/actuate/GatewayEndpoint.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import org.springframework.cloud.gateway.api.CachingRouteReader; import org.springframework.cloud.gateway.api.RouteReader; import org.springframework.cloud.gateway.config.Route; import org.springframework.cloud.gateway.filter.GlobalFilter; @@ -13,9 +14,11 @@ import org.springframework.cloud.gateway.handler.FilteringWebHandler; import org.springframework.core.Ordered; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** @@ -43,9 +46,17 @@ public class GatewayEndpoint {/*extends AbstractEndpoint> {* /*@Override public Map invoke() { - }*/ + //TODO: this should really be a listener that responds to a RefreshEvent + @PostMapping("/refresh") + public Flux refresh() { + if (this.routeReader instanceof CachingRouteReader) { + return ((CachingRouteReader)this.routeReader).refresh(); + } + return Flux.empty(); + } + @GetMapping("/globalfilters") public Map globalfilters() { return getNamesToOrders(this.globalFilters); diff --git a/src/main/java/org/springframework/cloud/gateway/api/CachingRouteReader.java b/src/main/java/org/springframework/cloud/gateway/api/CachingRouteReader.java new file mode 100644 index 00000000..1e737358 --- /dev/null +++ b/src/main/java/org/springframework/cloud/gateway/api/CachingRouteReader.java @@ -0,0 +1,40 @@ +package org.springframework.cloud.gateway.api; + +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.springframework.cloud.gateway.config.Route; + +import reactor.core.publisher.Flux; + +/** + * @author Spencer Gibb + */ +public class CachingRouteReader implements RouteReader { + + private final RouteReader delegate; + private final AtomicReference> cachedRoutes = new AtomicReference<>(); + + public CachingRouteReader(RouteReader delegate) { + this.delegate = delegate; + this.cachedRoutes.compareAndSet(null, collectRoutes()); + } + + @Override + public Flux getRoutes() { + return Flux.fromIterable(this.cachedRoutes.get()); + } + + /** + * Sets the new routes + * @return old routes + */ + public Flux refresh() { + return Flux.fromIterable(this.cachedRoutes.getAndUpdate( + routes -> CachingRouteReader.this.collectRoutes())); + } + + private List collectRoutes() { + return this.delegate.getRoutes().collectList().block(); + } +} diff --git a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index d8bbc667..a5d51583 100644 --- a/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -10,6 +10,7 @@ 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.CachingRouteReader; import org.springframework.cloud.gateway.api.RouteReader; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter; @@ -67,8 +68,9 @@ public class GatewayAutoConfiguration { @Bean @ConditionalOnMissingBean(RouteReader.class) - public PropertiesRouteReader propertiesRouteReader(GatewayProperties properties) { - return new PropertiesRouteReader(properties); + public RouteReader propertiesRouteReader(GatewayProperties properties) { + //TODO: how to automatically apply CachingRouteReader + return new CachingRouteReader(new PropertiesRouteReader(properties)); } @Bean diff --git a/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java b/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java index 3f42ed92..a46f50cd 100644 --- a/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java +++ b/src/main/java/org/springframework/cloud/gateway/handler/RoutePredicateHandlerMapping.java @@ -31,8 +31,6 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { private RouteReader routeReader; private WebHandler webHandler; - private List routes; - public RoutePredicateHandlerMapping(WebHandler webHandler, Map predicates, RouteReader routeReader) { this.webHandler = webHandler; @@ -58,24 +56,13 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { return name.replace(RoutePredicate.class.getSimpleName(), ""); } - @Override - protected void initApplicationContext() throws BeansException { - super.initApplicationContext(); - Flux routes = this.routeReader.getRoutes(); - registerHandlers(routes.collectList().block()); //TODO: convert rest of class to Reactive - } - - protected void registerHandlers(List routes) { - this.routes = routes; - } - @Override protected Mono getHandlerInternal(ServerWebExchange exchange) { exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName()); Route route; try { - route = lookupRoute(this.routes, exchange); + route = lookupRoute(exchange); } catch (Exception ex) { return Mono.error(ex); @@ -107,7 +94,9 @@ public class RoutePredicateHandlerMapping extends AbstractHandlerMapping { } - protected Route lookupRoute(List routes, ServerWebExchange exchange) throws Exception { + protected Route lookupRoute(ServerWebExchange exchange) throws Exception { + List routes = this.routeReader.getRoutes().collectList().block(); //TODO: convert rest of class to Reactive + for (Route route : routes) { if (!route.getPredicates().isEmpty()) { //TODO: cache predicate diff --git a/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java b/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java index 595e170a..012d919a 100644 --- a/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java +++ b/src/test/java/org/springframework/cloud/gateway/test/GatewayTestApplication.java @@ -5,7 +5,9 @@ import org.springframework.boot.SpringBootConfiguration; 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.api.CachingRouteReader; import org.springframework.cloud.gateway.api.CompositeRouteReader; +import org.springframework.cloud.gateway.api.RouteReader; import org.springframework.cloud.gateway.config.GatewayProperties; import org.springframework.cloud.gateway.config.PropertiesRouteReader; import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteReader; @@ -42,9 +44,11 @@ public class GatewayTestApplication { @Bean @Primary - public CompositeRouteReader compositeRouteReader(DiscoveryClientRouteReader discoveryClientRouteReader, - PropertiesRouteReader propertiesRouteReader) { - return new CompositeRouteReader(Flux.just(discoveryClientRouteReader, propertiesRouteReader)); + public RouteReader compositeRouteReader(DiscoveryClientRouteReader discoveryClientRouteReader, + PropertiesRouteReader propertiesRouteReader) { + final Flux flux = Flux.just(discoveryClientRouteReader, propertiesRouteReader); + final CompositeRouteReader composite = new CompositeRouteReader(flux); + return new CachingRouteReader(composite); } }