Merge branch 'dennis-menge-master'
This commit is contained in:
@@ -2518,6 +2518,15 @@ The folloiwng table below summarizes the Spring Cloud Gateway actuator endpoints
|
||||
|
||||
|===
|
||||
|
||||
=== Sharing Routes between multiple Gateway instances
|
||||
Spring Cloud Gateway offers two `RouteDefinitionRepository` implementations. The first one is the
|
||||
`InMemoryRouteDefinitionRepository` which only lives within the memory of one Gateway instance.
|
||||
This type of Repository is not suited to populate Routes across multiple Gateway instances.
|
||||
|
||||
In order to share Routes across a cluster of Spring Cloud Gateway instances, `RedisRouteDefinitionRepository` can be used.
|
||||
To enable this kind of repository, the following property has to set to true: `spring.cloud.gateway.redis-route-definition-repository.enabled`
|
||||
Likewise to the RedisRateLimiter Filter Factory it requires the use of the spring-boot-starter-data-redis-reactive Spring Boot starter.
|
||||
|
||||
[[troubleshooting]]
|
||||
== Troubleshooting
|
||||
|
||||
|
||||
@@ -27,15 +27,21 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
|
||||
import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepository;
|
||||
import org.springframework.cloud.gateway.route.RouteDefinition;
|
||||
import org.springframework.cloud.gateway.support.ConfigurationService;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
import org.springframework.web.reactive.DispatcherHandler;
|
||||
|
||||
@@ -65,4 +71,28 @@ class GatewayRedisAutoConfiguration {
|
||||
return new RedisRateLimiter(redisTemplate, redisScript, configurationService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "spring.cloud.gateway.redis-route-definition-repository.enabled",
|
||||
havingValue = "true")
|
||||
@ConditionalOnClass(ReactiveRedisTemplate.class)
|
||||
public RedisRouteDefinitionRepository redisRouteDefinitionRepository(
|
||||
ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
|
||||
return new RedisRouteDefinitionRepository(reactiveRedisTemplate);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisRouteDefinitionTemplate(
|
||||
ReactiveRedisConnectionFactory factory) {
|
||||
StringRedisSerializer keySerializer = new StringRedisSerializer();
|
||||
Jackson2JsonRedisSerializer<RouteDefinition> valueSerializer = new Jackson2JsonRedisSerializer<>(
|
||||
RouteDefinition.class);
|
||||
RedisSerializationContext.RedisSerializationContextBuilder<String, RouteDefinition> builder = RedisSerializationContext
|
||||
.newSerializationContext(keySerializer);
|
||||
RedisSerializationContext<String, RouteDefinition> context = builder
|
||||
.value(valueSerializer).build();
|
||||
|
||||
return new ReactiveRedisTemplate<>(factory, context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.route;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.cloud.gateway.support.NotFoundException;
|
||||
import org.springframework.data.redis.core.ReactiveRedisTemplate;
|
||||
import org.springframework.data.redis.core.ReactiveValueOperations;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* @author Dennis Menge
|
||||
*/
|
||||
@Repository
|
||||
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RedisRouteDefinitionRepository.class);
|
||||
|
||||
/**
|
||||
* Key prefix for RouteDefinition queries to redis.
|
||||
*/
|
||||
private static final String ROUTEDEFINITION_REDIS_KEY_PREFIX_QUERY = "routedefinition_";
|
||||
|
||||
private ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate;
|
||||
|
||||
private ReactiveValueOperations<String, RouteDefinition> routeDefinitionReactiveValueOperations;
|
||||
|
||||
public RedisRouteDefinitionRepository(
|
||||
ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
|
||||
this.reactiveRedisTemplate = reactiveRedisTemplate;
|
||||
this.routeDefinitionReactiveValueOperations = reactiveRedisTemplate.opsForValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RouteDefinition> getRouteDefinitions() {
|
||||
return reactiveRedisTemplate.keys(createKey("*"))
|
||||
.flatMap(key -> reactiveRedisTemplate.opsForValue().get(key))
|
||||
.onErrorContinue((throwable, routeDefinition) -> {
|
||||
if (log.isErrorEnabled()) {
|
||||
log.error("get routes from redis error cause : {}", throwable.toString(), throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> save(Mono<RouteDefinition> route) {
|
||||
return route.flatMap(routeDefinition -> routeDefinitionReactiveValueOperations
|
||||
.set(createKey(routeDefinition.getId()), routeDefinition)
|
||||
.flatMap(success -> {
|
||||
if (success) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.defer(() -> Mono.error(new RuntimeException(
|
||||
String.format("Could not add route to redis repository: %s",
|
||||
routeDefinition))));
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> delete(Mono<String> routeId) {
|
||||
return routeId.flatMap(id -> routeDefinitionReactiveValueOperations
|
||||
.delete(createKey(id)).flatMap(success -> {
|
||||
if (success) {
|
||||
return Mono.empty();
|
||||
}
|
||||
return Mono.defer(() -> Mono.error(new NotFoundException(String
|
||||
.format("Could not remove route from redis repository with id: %s",
|
||||
routeId))));
|
||||
}));
|
||||
}
|
||||
|
||||
private String createKey(String routeId) {
|
||||
return ROUTEDEFINITION_REDIS_KEY_PREFIX_QUERY + routeId;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,23 @@
|
||||
|
||||
package org.springframework.cloud.gateway.config;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.experimental.runners.Enclosed;
|
||||
import org.junit.runner.RunWith;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
|
||||
import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepository;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.data.redis.core.script.RedisScript;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@@ -75,4 +83,69 @@ public class GatewayRedisAutoConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dennis Menge
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(
|
||||
classes = RedisRouteDefinitionRepositoryDisabledByProperty.TestConfig.class,
|
||||
properties = "spring.cloud.gateway.redis-route-definition-repository.enabled=false")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public static class RedisRouteDefinitionRepositoryDisabledByProperty {
|
||||
|
||||
@Autowired(required = false)
|
||||
private RedisRouteDefinitionRepository redisRouteDefinitionRepository;
|
||||
|
||||
@Test
|
||||
public void redisRouteDefinitionRepository() {
|
||||
assertThat(redisRouteDefinitionRepository).isNull();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Dennis Menge
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest(
|
||||
classes = RedisRouteDefinitionRepositoryEnabledByProperty.TestConfig.class,
|
||||
properties = "spring.cloud.gateway.redis-route-definition-repository.enabled=true")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
public static class RedisRouteDefinitionRepositoryEnabledByProperty {
|
||||
|
||||
@Autowired(required = false)
|
||||
private RedisRouteDefinitionRepository redisRouteDefinitionRepository;
|
||||
|
||||
@Test
|
||||
public void redisRouteDefinitionRepository() {
|
||||
assertThat(redisRouteDefinitionRepository).isNotNull();
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
private RedisServer redisServer;
|
||||
|
||||
@Bean
|
||||
public RedisServer redisServer() throws IOException {
|
||||
redisServer = new RedisServer();
|
||||
redisServer.start();
|
||||
return redisServer;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
redisServer.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.route;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
import redis.embedded.RedisServer;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.SpringBootConfiguration;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.cloud.gateway.filter.FilterDefinition;
|
||||
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Dennis Menge
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@SpringBootTest
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
@ActiveProfiles("redis-route-repository")
|
||||
public class RedisRouteDefinitionRepositoryTests {
|
||||
|
||||
@Autowired
|
||||
private RedisRouteDefinitionRepository redisRouteDefinitionRepository;
|
||||
|
||||
@Test
|
||||
public void testAddRouteToRedis() {
|
||||
|
||||
RouteDefinition testRouteDefinition = defaultTestRoute();
|
||||
|
||||
redisRouteDefinitionRepository.save(Mono.just(testRouteDefinition)).block();
|
||||
|
||||
List<RouteDefinition> routeDefinitions = redisRouteDefinitionRepository
|
||||
.getRouteDefinitions().collectList().block();
|
||||
|
||||
assertThat(routeDefinitions.size()).isEqualTo(1);
|
||||
assertThat(routeDefinitions.get(0)).isEqualTo(testRouteDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveRouteFromRedis() {
|
||||
|
||||
RouteDefinition testRouteDefinition = defaultTestRoute();
|
||||
|
||||
redisRouteDefinitionRepository.save(Mono.just(testRouteDefinition)).block();
|
||||
|
||||
List<RouteDefinition> routeDefinitions = redisRouteDefinitionRepository
|
||||
.getRouteDefinitions().collectList().block();
|
||||
String routeId = routeDefinitions.get(0).getId();
|
||||
|
||||
// Assert that route has been added.
|
||||
assertThat(routeDefinitions.size()).isEqualTo(1);
|
||||
|
||||
// Delete route from repository
|
||||
redisRouteDefinitionRepository.delete(Mono.just(routeId)).block();
|
||||
|
||||
// Assert that route has been removed.
|
||||
assertThat(redisRouteDefinitionRepository.getRouteDefinitions().collectList()
|
||||
.block().size()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private RouteDefinition defaultTestRoute() {
|
||||
RouteDefinition testRouteDefinition = new RouteDefinition();
|
||||
testRouteDefinition.setUri(URI.create("http://example.org"));
|
||||
|
||||
FilterDefinition prefixPathFilterDefinition = new FilterDefinition(
|
||||
"PrefixPath=/test-path");
|
||||
FilterDefinition redirectToFilterDefinition = new FilterDefinition(
|
||||
"RemoveResponseHeader=Sensitive-Header");
|
||||
FilterDefinition testFilterDefinition = new FilterDefinition("TestFilter");
|
||||
testRouteDefinition.setFilters(Arrays.asList(prefixPathFilterDefinition,
|
||||
redirectToFilterDefinition, testFilterDefinition));
|
||||
|
||||
PredicateDefinition hostRoutePredicateDefinition = new PredicateDefinition(
|
||||
"Host=myhost.org");
|
||||
PredicateDefinition methodRoutePredicateDefinition = new PredicateDefinition(
|
||||
"Method=GET");
|
||||
PredicateDefinition testPredicateDefinition = new PredicateDefinition(
|
||||
"Test=value");
|
||||
testRouteDefinition.setPredicates(Arrays.asList(hostRoutePredicateDefinition,
|
||||
methodRoutePredicateDefinition, testPredicateDefinition));
|
||||
return testRouteDefinition;
|
||||
}
|
||||
|
||||
@EnableAutoConfiguration
|
||||
@SpringBootConfiguration
|
||||
public static class TestConfig {
|
||||
|
||||
RedisServer redisServer;
|
||||
|
||||
@Bean
|
||||
public RedisServer redisServer() throws IOException {
|
||||
|
||||
redisServer = new RedisServer();
|
||||
redisServer.start();
|
||||
return redisServer;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void destroy() {
|
||||
redisServer.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
redis-route-definition-repository:
|
||||
enabled: true
|
||||
Reference in New Issue
Block a user