# SpringCloudGateway
Gateway网关
# 1. XXXX
待补充
# 2. 动态路由
路由配置默认是从配置文件读取,这样展示和更新都不方便,可以调整为将路由配置保存到 MySQL 数据库或者 Redis 缓存处理
- 提供一个统一刷新当前机器路由方法,每次调用去查询最新配置加载到当前机器
- 改造读取路由代码为直接从 MySQL 或者 Redis 读取,当然一般是从 Redis 时时读取
第一种集群机器太多不方便,需要每台机器调用,可以使用消息订阅方案,调用接口触发一个消息,所有网关机器订阅消费更新,第二种每次请求时时读取这样也稍微影响性能,所以推荐是使用第一种,机器多可以使用消息订阅方案
# 2.1. Actuator
可以从 Actuator 直接处理本机内存的动态路由,一般不配置默认显示
# 默认为true
management.endpoint.gateway.enabled = true
# 显示gateway端点
management.endpoints.web.exposure.include = gateway
以下所有端点都挂在 /actuator/gateway
下面
ID | HTTP Method | Description |
---|---|---|
/globalfilters | GET | 展示所有的全局过滤器 |
/routepredicates | GET | 展示所有的路由断言工厂 |
/routefilters | GET | 展示所有的路由过滤器工厂 |
/routedefinitions | GET | 展示当前所有的路由配置 |
/routes | GET | 展示当前所有的路由列表 |
/routes/{id} | GET | 展示指定id的路由的信息 |
/routes/{id} | POST | 新增一个路由 |
/routes/{id} | DELETE | 删除一个路由 |
/refresh | POST | 刷新当前路由配置 |
删除路由和刷新配置都不需要参数,新增路由参数如下,其实就和路由列表结构一样
{
"predicates": [
{
"name": "Path",
"args": {
"_genkey_0": "/gw/**"
}
}
],
"filters": [
{
"name": "AddRequestHeader",
"args": {
"_genkey_0": "X-Request-Foo",
"_genkey_1": "Bar"
}
},
{
"name": "StripPrefix",
"args": {
"_genkey_0": "1"
}
}
],
"uri": "https://dolyw.com",
"order": 0
}
# 2.2. 统一加载
一般就是实现 ApplicationEventPublisherAware 接口进行内存里路由统一刷新,使用 RouteDefinitionRepository 里的 RouteDefinitionWriter 的 save() 及 delete() 方法处理内存里路由的新增,修改
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.pcic.dto.GatewayRouteDto;
import com.pcic.service.GatewayRouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.Inet4Address;
import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 网关路由处理
* https://www.cnblogs.com/ylcc-zyq/p/13156526.html
* https://blog.csdn.net/qiuxinfa123/article/details/120317471
* https://blog.csdn.net/huangjinjin520/article/details/124976940
*
* @author wliduo[i@dolyw.com]
* @date 2022/11/2 16:47
*/
@Slf4j
@Service("gatewayRouteHandle")
public class GatewayRouteHandle implements ApplicationEventPublisherAware, CommandLineRunner {
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private List<GatewayFilterFactory> gatewayFilters;
@Autowired
private List<RoutePredicateFactory> routePredicates;
@Autowired
private RouteDefinitionRepository routeDefinitionRepository;
@Autowired
private Environment environment;
@Autowired
private GatewayRouteService gatewayRouteService;
/**
* 获取推送事件
*
* @param applicationEventPublisher
*/
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 启动运行
*
* @param args
* @throws Exception
*/
@Override
public void run(String... args) throws Exception {
try {
this.loadRoute();
} catch (Exception e) {
log.error("动态路由加载异常:", e);
}
}
/**
* 加载数据库动态路由
*
* @param
* @return void
* @throws
* @author wliduo[i@dolyw.com]
* @date 2022/11/2 16:52
*/
public String loadRoute() throws Exception {
log.info("---------- Loaded Route Start ----------");
// 获取原有路由
Flux<RouteDefinition> routeDefinitionFlux = routeDefinitionRepository.getRouteDefinitions();
Mono<List<RouteDefinition>> routeDefinitionListMono = routeDefinitionFlux.collectList();
List<RouteDefinition> routeDefinitionList = routeDefinitionListMono.block();
// 删除原有路由
for (RouteDefinition routeDefinition : routeDefinitionList) {
routeDefinitionRepository.delete(Mono.just(routeDefinition.getId())).subscribe();
}
// 查询数据库最新配置路由
List<GatewayRouteDto> gatewayRouteList = gatewayRouteService.list();
log.info("【加载路由列表】: {}", JSON.toJSONString(gatewayRouteList));
for (GatewayRouteDto gatewayRouteDto : gatewayRouteList) {
RouteDefinition routeDefinition = new RouteDefinition();
routeDefinition.setId(gatewayRouteDto.getRouteId());
routeDefinition.setUri(URI.create(gatewayRouteDto.getRouteUri()));
// 多个规则转换List
gatewayRouteDto = gatewayRouteService.transList(gatewayRouteDto);
// 处理路由匹配规则
if (CollUtil.isNotEmpty(gatewayRouteDto.getPredicateList())) {
List<PredicateDefinition> predicateDefinitionList = gatewayRouteDto.getPredicateList().stream()
.map(predicateDefinition -> new PredicateDefinition(predicateDefinition.getName()))
.collect(Collectors.toList());
routeDefinition.setPredicates(predicateDefinitionList);
}
// 处理路由过滤规则
if (CollUtil.isNotEmpty(gatewayRouteDto.getFilterList())) {
List<FilterDefinition> filterDefinitionList = gatewayRouteDto.getFilterList().stream()
.map(filterDefinition -> new FilterDefinition(filterDefinition.getName()))
.collect(Collectors.toList());
routeDefinition.setFilters(filterDefinitionList);
}
// 路由规则验证
String errorMessage = this.validateRouteDefinition(routeDefinition);
if (StrUtil.isBlank(errorMessage)) {
routeDefinitionRepository.save(Mono.just(routeDefinition)).subscribe();
} else {
// 验证不通过
log.error("【路由{}配置异常】: {}", gatewayRouteDto.getRouteId(), errorMessage);
}
}
// 刷新路由
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
log.info("---------- Loaded Route End ----------");
// 返回当前处理机器IP
/*String ip = Inet4Address.getLocalHost().getHostAddress();
// 端口号
String port = environment.getProperty("server.port");
// 项目名称
String webApp = environment.getProperty("server.servlet.context-path");
if (StrUtil.isBlank(webApp)) {
webApp = StrUtil.EMPTY;
}
return ip + ":" + port + webApp;*/
return Inet4Address.getLocalHost().getHostAddress();
}
/**
* 路由规则验证
*
* @param routeDefinition
* @return java.lang.String
* @throws
* @author wliduo[i@dolyw.com]
* @date 2022/11/9 14:00
*/
public String validateRouteDefinition(RouteDefinition routeDefinition) {
Set<String> predicatesValidateSet = routeDefinition.getPredicates().stream().filter((predicateDefinition) -> {
return !this.routePredicates.stream().anyMatch((routePredicate) -> {
return predicateDefinition.getName().equals(routePredicate.name());
});
}).map(PredicateDefinition::getName).collect(Collectors.toSet());
Set<String> filterValidateSet = routeDefinition.getFilters().stream().filter((filterDefinition) -> {
return !this.gatewayFilters.stream().anyMatch((gatewayFilterFactory) -> {
return filterDefinition.getName().equals(gatewayFilterFactory.name());
});
}).map(FilterDefinition::getName).collect(Collectors.toSet());
// 判断验证结果不为空
StringBuffer errorMessage = new StringBuffer();
if (CollUtil.isNotEmpty(predicatesValidateSet)) {
errorMessage.append(String.format("[无效的路由匹配规则: %s]", predicatesValidateSet));
}
if (CollUtil.isNotEmpty(filterValidateSet)) {
errorMessage.append(String.format("[无效的路由过滤规则: %s]", filterValidateSet));
}
return errorMessage.toString();
}
}
# 2.3. 时时读取
一般就是改造 RouteDefinitionLocator 接口的 getRouteDefinitions() 方法
# 3. 自定义
自定义断言 Predicate 和过滤器 Filter
# 3.1. Predicate
待补充
# 3.2. Filter
自定义过滤器工厂,类似 SetPathGatewayFilterFactory
,配置方式 ParamPathFilter={Path},单个参数的,实现多个参数可以查看其他默认提供的 KeyValue 的过滤器工厂源码
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.support.GatewayToStringStyler;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriTemplate;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* 自定义过滤器工厂,类似 SetPathGatewayFilterFactory
* 配置 ParamPathFilter={Path}
* https://www.jianshu.com/p/5d394afab425
*
* @author wliduo[i@dolyw.com]
* @date 2023/1/5 17:57
*/
@Slf4j
@Component
public class ParamPathFilter extends AbstractGatewayFilterFactory<ParamPathFilter.Config> {
public static final String PARAM_KEY = "param";
public ParamPathFilter() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(PARAM_KEY);
}
@Override
public GatewayFilter apply(Config config) {
// 创建Uri模板用于替换动态Uri参数{uri}
UriTemplate uriTemplate = new UriTemplate(config.param);
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 获取当前请求路径
ServerHttpRequest req = exchange.getRequest();
ServerWebExchangeUtils.addOriginalRequestUrl(exchange, req.getURI());
// 获取动态Uri参数
Map<String, String> uriVariables = ServerWebExchangeUtils.getUriTemplateVariables(exchange);
// 当前请求路径动态Uri参数替换得到最终转发地址
URI uri = uriTemplate.expand(uriVariables);
String newPath = uri.getRawPath();
// 请求转发
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, uri);
ServerHttpRequest request = req.mutate().path(newPath).build();
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public String toString() {
return GatewayToStringStyler.filterToStringCreator(ParamPathFilter.this).append(PARAM_KEY, config.getParam()).toString();
}
};
}
public static class Config {
private String param;
public Config() {}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
}
# P. 问题
记录问题
# P.1. 参数过大
错误内容,默认请求参数最大不能超过 256KB
org.springframework.core.io.buffer.DataBufferLimitException: Exceeded limit on max bytes to buffer : 262144
at org.springframework.core.io.buffer.LimitedDataBufferList.raiseLimitException(LimitedDataBufferList.java:101)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP POST "/" [ExceptionHandlingWebHandler]
添加配置
# 参数缓存区大小
spring.codec.max-in-memory-size = 16MB
spring:
codec:
max-in-memory-size: 16MB
调整初始化 ServerRequest 代码
@Autowired
private ServerCodecConfigurer serverCodecConfigurer;
// ServerRequest serverRequest = ServerRequest.create(exchange, HandlerStrategies.withDefaults().messageReaders());
// 使用默认参数缓冲区只能256K,切换为自定义大小 spring.codec.max-in-memory-size = 16MB
ServerRequest serverRequest = ServerRequest.create(exchange, serverCodecConfigurer.getReaders());
参考