Predicate(断言)
Predicate(断言),用于进行判断,如果返回为真,才会路由到具体服务。SpirnngCloudGateway由路由断言工厂实现,直接配置即生效,当然也支持自定义路由断言工厂。
内置路由断言工厂实现
SpringCloudGateway路由断言工厂实现有很多,可以帮助开发者完成不同的功能。
AfterRoutePredicateFactory
:设定日期参数,允许在指定日期时间之后的请求通过
- After=2020-01-20T17:42:47.789+08:00[Asia/Shanghai]
BeforeRoutePredicateFactory
:设定日期参数,允许在指定日期时间之前的请求通过
- Before=2020-01-20T17:42:47.789+08:00[Asia/Shanghai]
BetweenRoutePredicateFactory
:设定日期区间,使用逗号分隔,表示允许在时间段内的请求通过
- Between=2020-01-20T17:42:47.789+08:00[Asia/Shanghai],2023-02-20T17:42:47.789+08:00[Asia/Shanghai]
CookieRoutePredicateFactory
:设置Cookie的名称和cookie值的正则表达式,判断请求是否含有该Cookie名称且值与正则表达式相匹配
- Cookie=chocolate, ch.p
HeaderRoutePredicateFactory
:设定请求头名称和请求头值的正则表达式,判断请求是否含有该请求头,且值与正则相匹配
- Header=X-Request-Id, \d+
HostRoutePredicateFactory
:设定host,判断请求的host是否满足条件。
- Host=**.test.com
MethodRoutePredicateFactory
:设定请求方式,只允许满足请求类型的请求方式通过
- Method=GET
PathRoutePredicateFactory
:设定路由规则,判断请求地址是否满足设定的路径规则
- Path=/shop/**
QueryRoutePredicateFactory
:设定参数名称和参数值的正则,判断请求是否含有该参数,且值符合正则规则
- Query=name,z.
RemoteAddrRoutePredicateFactory
:设置IP地址段,判断请求的主机地址是否满足条件
- RemoteAddr=10.xxx.xxx.xxx/24
注意:这里是一个IP的地址段,不是单纯的IP地址
WeightRoutePredicateFactory
:设定权重分组和权重值,同一个分组名的路由根据权重值进行转发
routes:
- id: user_group1
uri: http://test1.com
predicates:
- Path=/user/**
- Weight=user_grp,8 # 设置user_grp组,80%请求进入user_group1
- id: user_group2
uri: http://test2.com
predicates:
- Path=/user/**
- Weight=user_grp,2 # 设置user_grp组,20%请求进入user_group2
内置断言实例
指定POST请求
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- Method=POST # 表示需要POST请求
使用CMD测试下:当使用GET请求时,gateway直接打回,告知沒有找到
curl localhost:8083/user/findById?id=1
{"timestamp":"2023-08-26T00:24:35.884+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"0c70f6cf-1"}
使用POST请求,正确访问(-X POST
表示使用POST请求方式)
curl -X POST localhost:8083/user/findById?id=1
Zhangsan
不指定为POST请求时,请求会被404.
使用Query路由断言
使用Query路由断言工厂,且设置参数名为id,正则表达式\d+
(一个或多个数字)
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- Query=id, \d+ # 参数名id,且值是数字(一位或多位)
使用cmd调用测试,使用非数字时,被告知404
curl localhost:8083/user/findById?id=ss
{"timestamp":"2023-08-26T00:29:38.252+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"bc0fe260-3"}
使用数字,符合正则,正确访问
curl localhost:8083/user/findById?id=1
Zhangsan
自定义路由断言工厂
在实际的项目开发中,开发者也可以创建自定义的路由断言工厂,进行自己的业务扩展。实际上前面所有的断言工厂都是继承自AbstractRoutePredicateFactory
,并且配置是-Method
时那么类就是MethodRoutePredicateFactory
要创建自定义断言工厂,我们也需要集成自此类,然后重写自己的扩展即可。
实例:创建自定义断言工厂,只允许id为1~1000的请求通过
- 编写自定义的断言工厂
CustomRoutePredicateFactory
,那么配置时就是-Custom
@Component // 交给Spring管理
public class CustomRoutePredicateFactory extends AbstractRoutePredicateFactory<CustomRoutePredicateFactory.Config> {
public static final String[] KEY_ARRAY = {"minId", "maxId"}; // 对象属性
public CustomRoutePredicateFactory() { // 构造函数,需要给父类初始化一个配置类
super(CustomRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY_ARRAY);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() { // 设置断言逻辑
@Override
public String toString() {
return String.format("minId:%d, maxId:%d", config.getMinId(), config.getMaxId());
}
@Override
public boolean test(ServerWebExchange exchange) {
// 得到id
String id = exchange.getRequest().getQueryParams().getFirst("id");
if (id != null) {
int numberId = Integer.parseInt(id);
// 当 请求的id是在此范围内,返回true
return numberId >= config.getMinId() && numberId <= config.getMaxId();
}
return false;
}
};
}
@Validated
public static class Config { // 配置类(注意这里是静态类,否则属性是注入不进去的)
private Integer minId; // 最小id
private Integer maxId; // 最大id
public Integer getMinId() {
return minId;
}
public void setMinId(Integer minId) {
this.minId = minId;
}
public Integer getMaxId() {
return maxId;
}
public void setMaxId(Integer maxId) {
this.maxId = maxId;
}
}
}
yml配置
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
- Custom=1,1000 # 设置值为1,1000
测试,当id不在范围内
curl localhost:8083/user/findById?id=0
{"timestamp":"2023-08-26T01:09:02.567+00:00","path":"/user/findById","status":404,"error":"Not Found","message":null,"requestId":"61dcf9de-1"}
在范围内
curl localhost:8083/user/findById?id=1
Zhangsan
没问题!~
Filter过滤器
做过J2EE开发的应该不会对这个词感到陌生。在SpringCloudGateway中的过滤器也有类似的功能,都可以在请求和响应间做一些自定义的逻辑。
Gateway的过滤器又分为局部
和全局
,而且同样有内置和自定义。
内置局部过滤器
局部过滤器只针对当前路由的
AddRequestHeader
:设定Header的值,为原始的请求添加Header
- AddRequestHeader=X-Request-token, 123456
AddRequestParameter
:设定参数名和值,为原始请求添加参数
- AddRequestParameter=name, zhangsan
AddResponseHeader
:设定Header的值,为响应添加Header
- AddResponseHeader=token, 123456
DedupeResponseHeader
:设定去重的Header名称和去重策略,删除响应头中的重复值
- DedupeResponseHeader=Access-Control-Allow-Credentinals Access-Control-Allow-Origin
多个Header使用空格分隔,去重策略:RETAIN_FIRST(保留第一个)
、RETAIN_LAST(保留最后一个)
、RETAIN_UNIQUE(保留所有的唯一值)
PrefixPath
:给请求加前缀,为原始请求添加路径
- PrefixPath=/userPrefix
RedirectTo
:设定HTTP状态码和URL,将原始请求重定向到指定的URL
- RedirectTo=302, https://test.com
RewritePath
:设定原始路径(支持正则)和重写路径(支持正则),重写原始路径
- RewritePath=/user/findById, /user/test
StripPrefix
:设定要拦截的路径数量,用于修改原始路
- StripPrefix=1 # 如果请求是 /user/findById,那么会被拦截为 /findById
RequestSize
:设定请求包大小(单位字节,默认5MB)
filters:
- name: RequestSize
args:
maxSize: 5000000
自定义局部过滤器
还是按照前面提到的,id在1~1000内允许访问,否则直接拒绝。同路由断言工厂类似,是配置+GatewayFilterFactory
的命名然后继承AbstractGatewayFilterFactory
写一个UserGatewayFilterFactory的类
@Component
public class UserGatewayFilterFactory extends AbstractGatewayFilterFactory<UserGatewayFilterFactory.Config> {
private static final String[] KEY_ARRAYS = {"minId", "maxId"};
public UserGatewayFilterFactory() { // 构造函数
super(UserGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
// 获取id
String id = exchange.getRequest().getQueryParams().getFirst("id");
if (id != null) {
int number = Integer.parseInt(id);
if (number >= config.getMinId() && number <= config.getMaxId()) {
// 放行
return chain.filter(exchange);
}
}
byte[] bytes = ("您不能访问"+id+"用户的数据").getBytes(StandardCharsets.UTF_8);
DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
// 设定HTTP状态码
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
// 返回友好提示
return exchange.getResponse().writeWith(Flux.just(wrap));
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList(KEY_ARRAYS);
}
public static class Config {
private Integer minId;
private Integer maxId;
public Integer getMinId() {
return minId;
}
public void setMinId(Integer minId) {
this.minId = minId;
}
public Integer getMaxId() {
return maxId;
}
public void setMaxId(Integer maxId) {
this.maxId = maxId;
}
}
}
yml配置
server:
port: 8083
spring:
application:
name: gateway # 服务名
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 路由,可配置多个
- id: user_route # 路由id,唯一即可,默认UUID
uri: lb://user # 路由地址(匹配成功后的服务地址) user是用户服务的服务名称
order: 1 # 路由优先级,默认0,越低优先级越高
predicates:
- Path=/user/** # 断言,匹配规则
filters:
- User=1,1000 # 设置id为1~1000的过滤
测试一手,id空或id并不在范围内
curl localhost:8083/user/findById
您不能访问null用户的数据
curl localhost:8083/user/findById?id=2000
您不能访问2000用户的数据
成功测试
curl localhost:8083/user/findById?id=1
Zhangsan
全局过滤器
全局过滤器在前面的文章中也大概了解过几个,如openFeign的超时时间等,其他的也基本类似,用的不是很多。
通常情况下自定义全局过滤器是比较常用的了,下面看一个案例,客户端访问时的token认证功能的。
处理类需要实现GlobalFilter和Ordered接口,重写方法
@Component
public class TokenFilter implements GlobalFilter, Ordered { // 自定义全局过滤
private final static String token = "123456"; // 这里做个模拟token
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 从请求头中拿到token
String token = exchange.getRequest().getHeaders().getFirst("token");
if (token != null) {
if (token.equals(TokenFilter.token)) { // 如果请求头中带的token是123456 那么放行
// 放行
return chain.filter(exchange);
}
}
byte[] bytes = ("您不能访问" + exchange.getRequest().getPath() + "地址").getBytes(StandardCharsets.UTF_8);
DataBuffer wrap = exchange.getResponse().bufferFactory().wrap(bytes);
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
// 返回友好提示
return exchange.getResponse().writeWith(Flux.just(wrap));
}
@Override
public int getOrder() { // 排序,值越小,优先级越高(说的是过滤器的优先级,当有多个过滤器时,order可以控制它们的执行优先级)
return 0;
}
}
下面用工具测试一下,token