Moves routeId generation schedule to more specific location.
This allows only the routeId generation to be on an elastic scheduler while everything else remains on the main thread. That way if there are errors loading routes, this will halt execution. Fixes gh-1574 Fixes gh-1575
This commit is contained in:
@@ -50,18 +50,21 @@ public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions)
|
||||
.flatMap(routeDefinition -> Mono.justOrEmpty(routeDefinition.getId())
|
||||
.defaultIfEmpty(idGenerator.generateId().toString())
|
||||
.publishOn(Schedulers.elastic()).map(id -> {
|
||||
if (routeDefinition.getId() == null) {
|
||||
routeDefinition.setId(id);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Id set on route definition: "
|
||||
+ routeDefinition);
|
||||
}
|
||||
.flatMap(routeDefinition -> {
|
||||
if (routeDefinition.getId() == null) {
|
||||
return randomId().map(id -> {
|
||||
routeDefinition.setId(id);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Id set on route definition: " + routeDefinition);
|
||||
}
|
||||
return routeDefinition;
|
||||
}));
|
||||
});
|
||||
}
|
||||
return Mono.just(routeDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
protected Mono<String> randomId() {
|
||||
return Mono.fromSupplier(idGenerator::toString).publishOn(Schedulers.boundedElastic());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.cloud.gateway.config.GatewayProperties;
|
||||
import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator;
|
||||
@@ -34,7 +36,7 @@ import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGate
|
||||
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.cloud.gateway.support.ConfigurationService;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -66,19 +68,74 @@ public class RouteDefinitionRouteLocatorTests {
|
||||
}
|
||||
}));
|
||||
|
||||
PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator(gatewayProperties);
|
||||
@SuppressWarnings("deprecation")
|
||||
RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator(
|
||||
new PropertiesRouteDefinitionLocator(gatewayProperties), predicates,
|
||||
gatewayFilterFactories, gatewayProperties,
|
||||
new DefaultConversionService());
|
||||
new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)),
|
||||
predicates, gatewayFilterFactories, gatewayProperties,
|
||||
new ConfigurationService());
|
||||
|
||||
List<Route> routes = routeDefinitionRouteLocator.getRoutes().collectList()
|
||||
.block();
|
||||
List<GatewayFilter> filters = routes.get(0).getFilters();
|
||||
assertThat(filters).hasSize(3);
|
||||
assertThat(getFilterClassName(filters.get(0))).contains("RemoveResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(2)))
|
||||
.contains("RouteDefinitionRouteLocatorTests$TestOrderedGateway");
|
||||
StepVerifier.create(routeDefinitionRouteLocator.getRoutes())
|
||||
.assertNext(route -> {
|
||||
List<GatewayFilter> filters = route.getFilters();
|
||||
assertThat(filters).hasSize(3);
|
||||
assertThat(getFilterClassName(filters.get(0))).contains("RemoveResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(2)))
|
||||
.contains("RouteDefinitionRouteLocatorTests$TestOrderedGateway");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contextLoadsWithErrorRecovery() {
|
||||
List<RoutePredicateFactory> predicates = Arrays
|
||||
.asList(new HostRoutePredicateFactory());
|
||||
List<GatewayFilterFactory> gatewayFilterFactories = Arrays.asList(
|
||||
new RemoveResponseHeaderGatewayFilterFactory(),
|
||||
new AddResponseHeaderGatewayFilterFactory(),
|
||||
new TestOrderedGatewayFilterFactory());
|
||||
GatewayProperties gatewayProperties = new GatewayProperties();
|
||||
gatewayProperties.setRoutes(containsInvalidRoutes());
|
||||
|
||||
PropertiesRouteDefinitionLocator routeDefinitionLocator = new PropertiesRouteDefinitionLocator(gatewayProperties);
|
||||
@SuppressWarnings("deprecation")
|
||||
RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator(
|
||||
new CompositeRouteDefinitionLocator(Flux.just(routeDefinitionLocator)),
|
||||
predicates, gatewayFilterFactories, gatewayProperties,
|
||||
new ConfigurationService());
|
||||
|
||||
StepVerifier.create(routeDefinitionRouteLocator.getRoutes())
|
||||
.assertNext(route -> {
|
||||
List<GatewayFilter> filters = route.getFilters();
|
||||
assertThat(filters).hasSize(3);
|
||||
assertThat(getFilterClassName(filters.get(0))).contains("RemoveResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(1))).contains("AddResponseHeader");
|
||||
assertThat(getFilterClassName(filters.get(2)))
|
||||
.contains("RouteDefinitionRouteLocatorTests$TestOrderedGateway");
|
||||
})
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
private List<RouteDefinition> containsInvalidRoutes() {
|
||||
RouteDefinition foo = new RouteDefinition();
|
||||
foo.setId("foo");
|
||||
foo.setUri(URI.create("https://foo.example.com"));
|
||||
foo.setPredicates(
|
||||
Arrays.asList(new PredicateDefinition("Host=*.example.com")));
|
||||
foo.setFilters(Arrays.asList(
|
||||
new FilterDefinition("RemoveResponseHeader=Server"),
|
||||
new FilterDefinition("TestOrdered="),
|
||||
new FilterDefinition("AddResponseHeader=X-Response-Foo, Bar")));
|
||||
RouteDefinition bad = new RouteDefinition();
|
||||
bad.setId("exceptionRaised");
|
||||
bad.setUri(URI.create("https://foo.example.com"));
|
||||
bad.setPredicates(
|
||||
Arrays.asList(new PredicateDefinition("Host=*.example.com")));
|
||||
bad.setFilters(Arrays.asList(new FilterDefinition("Generate exception")));
|
||||
return Arrays.asList(foo, bad);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2013-2020 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
|
||||
*
|
||||
* https://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.test;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilter;
|
||||
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
||||
public class RouteConstructionIntegrationTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void routesWithVerificationShouldFail() {
|
||||
exception.expect(Throwable.class);
|
||||
new SpringApplicationBuilder(TestConfig.class)
|
||||
.profiles("verification-route")
|
||||
.run();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
@Bean
|
||||
public TestFilterGatewayFilterFactory testFilterGatewayFilterFactory() {
|
||||
return new TestFilterGatewayFilterFactory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class TestFilterGatewayFilterFactory
|
||||
extends AbstractGatewayFilterFactory<TestFilterGatewayFilterFactory.Config> {
|
||||
|
||||
public TestFilterGatewayFilterFactory() {
|
||||
super(Config.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GatewayFilter apply(Config config) {
|
||||
throw new AssertionError("Stop right now!");
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
|
||||
private String arg1;
|
||||
|
||||
public String getArg1() {
|
||||
return arg1;
|
||||
}
|
||||
|
||||
public void setArg1(String arg1) {
|
||||
this.arg1 = arg1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
routes:
|
||||
- uri: https://example.com
|
||||
predicates:
|
||||
- Path=/verification/**
|
||||
filters:
|
||||
- name: TestFilter
|
||||
args:
|
||||
arg1: world
|
||||
Reference in New Issue
Block a user