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:
MattyA
2020-02-17 11:01:59 +00:00
committed by Spencer Gibb
parent 88be470c9b
commit 75a34df7e2
4 changed files with 172 additions and 22 deletions

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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;
}
}
}
}

View File

@@ -0,0 +1,11 @@
spring:
cloud:
gateway:
routes:
- uri: https://example.com
predicates:
- Path=/verification/**
filters:
- name: TestFilter
args:
arg1: world