基于 Redis 配置网关路由
RouteDefinitionRepository
- 网关路由接口
InMemoryRouteDefinitionRepository
- 网关路由基于内存的实现
- 默认实现: @ConditionalOnMissingBean(RouteDefinitionRepository.class)
RedisRouteDefinitionRepository
- 网关路由基于 Redis 的实现
源码
java
/*
* 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.data.redis.core.ScanOptions;
import org.springframework.stereotype.Repository;
/**
* @author Dennis Menge
* @author lzhpo
*/
@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.scan(ScanOptions.scanOptions().match(createKey("*")).build())
.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;
}
}
配置
properties
# https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java
spring.cloud.gateway.redis-route-definition-repository.enabled=true
yaml
# https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayRedisAutoConfiguration.java
spring:
cloud:
gateway:
redis-route-definition-repository:
enabled: true
java
package cn.com.xuxiaowei.shield.gateway.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RedisRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
/**
* 使用 Redis 储存 网关 route 路由配置
*
* @author xuxiaowei
* @since 0.0.1
* @see <a href="https://github.com/spring-cloud/spring-cloud-gateway/blob/main/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java#L232">inMemoryRouteDefinitionRepository</a>
*/
@Slf4j
@Configuration
public class RedisRouteDefinitionRepositoryConfig {
@Bean
public RedisRouteDefinitionRepository redisRouteDefinitionRepository(
ReactiveRedisTemplate<String, RouteDefinition> reactiveRedisTemplate) {
return new RedisRouteDefinitionRepository(reactiveRedisTemplate);
}
}
使用
Redis 网关路由数据前缀
- 由 RedisRouteDefinitionRepository 可知,Redis 网关路由数据的前缀是
routedefinition_
网关路由数据类型
- 由
spring.cloud.gateway.routes
可知, 网关路由配置类是 RouteDefinition
手动创建 Redis 网关路由数据
yaml
spring:
cloud:
gateway:
routes:
- id: baidu
uri: https://www.baidu.com
predicates:
- Host=baidu.localdev.me:*
properties
spring.cloud.gateway.routes[0].id=baidu
spring.cloud.gateway.routes[0].uri=https://www.baidu.com
spring.cloud.gateway.routes[0].predicates[0]=Host=baidu.localdev.me:*
json
{
"id": "baidu",
"predicates": [
{
"name": "Host",
"args": {
"args1": "baidu.localdev.me:*"
}
}
],
"filters": [],
"uri": "https://www.baidu.com",
"metadata": {
},
"order": 0
}
使用程序创建 Redis 网关路由数据
java
package cn.com.xuxiaowei.shield.gateway.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
/**
* 使用 Redis 储存 网关 route 路由配置 测试类
*
* @author xuxiaowei
* @since 0.0.1
*/
@Slf4j
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RedisRouteDefinitionRepositoryConfigTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
@SneakyThrows
public void set() {
String key = "routedefinition_:" + UUID.randomUUID().toString().substring(0, 8);
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId("baidu");
routeDefinition.setUri(new URI("https://www.baidu.com"));
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setName("Host");
HashMap<String, String> args = new HashMap<>();
args.put("args1", "baidu.localdev.me:*");
predicateDefinition.setArgs(args);
routeDefinition.setPredicates(List.of(predicateDefinition));
ObjectMapper objectMapper = new ObjectMapper();
String routeDefinitionStr = objectMapper.writeValueAsString(routeDefinition);
// {"id":"baidu","predicates":[{"name":"Host","args":{"args1":"baidu.localdev.me:*"}}],"filters":[],"uri":"https://www.baidu.com","metadata":{},"order":0}
log.info("routeDefinition: {}", routeDefinitionStr);
// 根据自己的需求,设置有效期
stringRedisTemplate.opsForValue().set(key, routeDefinitionStr);
}
}
使用 事件订阅
刷新 Redis 网关路由配置
java
package cn.com.xuxiaowei.shield.gateway.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
/**
* 过滤器
*
* @author xuxiaowei
* @since 0.0.1
*/
@Slf4j
@Setter
@Component
public class ApplicationEventPublisherWebFilter implements WebFilter, Ordered {
public static final int ORDERED = Ordered.HIGHEST_PRECEDENCE + 20000;
private static final String PUBLISH_EVENT_PATH = "/publish-event/refresh-routes-event";
private ApplicationEventPublisher applicationEventPublisher;
private int order = ORDERED;
@Autowired
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
@Override
public int getOrder() {
return order;
}
@NonNull
@Override
@SneakyThrows
@SuppressWarnings("all")
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
String path = uri.getPath();
if (PUBLISH_EVENT_PATH.equals(path)) {
applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
ServerHttpResponse response = exchange.getResponse();
// 响应状态码
response.setStatusCode(HttpStatus.OK);
// 不可使用 APPLICATION_JSON(部分浏览器会乱码)
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
Map<String, Object> map = new HashMap<>();
map.put("msg", "网关路由事件已订阅");
ObjectMapper objectMapper = new ObjectMapper();
byte[] bytes = objectMapper.writeValueAsBytes(map);
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(dataBuffer));
}
return chain.filter(exchange);
}
}
使用 端点
刷新 Redis 网关路由配置
- 引入 端点 依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 启用网关
端点
配置
yaml
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
gateway:
# Spring Boot 2 默认为 true
# https://github.com/spring-cloud/spring-cloud-gateway/blame/3.1.x/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java
# https://github.com/spring-cloud/spring-cloud-gateway/blame/4.0.x/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java
# https://github.com/spring-cloud/spring-cloud-gateway/commit/25fb7475a766345928a86652f0d56b771018b483
enabled: true
yaml
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
gateway:
# Spring Boot 3 默认为 false
# https://github.com/spring-cloud/spring-cloud-gateway/blame/3.1.x/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java
# https://github.com/spring-cloud/spring-cloud-gateway/blame/4.0.x/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/actuate/GatewayControllerEndpoint.java
# https://github.com/spring-cloud/spring-cloud-gateway/commit/25fb7475a766345928a86652f0d56b771018b483
enabled: true
- 触发
端点
刷新- POST 请求访问
/actuator/gateway/refresh
- POST 请求访问