自定義Predicate
思路
在SCG初始化解析中我們已經知道了Predicate是怎麼根據我們的配置裝配的。
以
RemoteAddrRoutePredicateFactory
為例。
public class RemoteAddrRoutePredicateFactory
extends AbstractRoutePredicateFactory<RemoteAddrRoutePredicateFactory.Config>
RemoteAddrRoutePredicateFactory
繼承了抽象類
AbstractRoutePredicateFactory
,泛型為内部類
Config
。
@Override
public ShortcutType shortcutType() {
return GATHER_LIST;
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("sources");
}
重寫
shortcutType
和
shortcutFieldOrder
方法,這兩個方法主要是用來定義Config的配置及生成方式,具體細節不再深叙,個人認為SCG的
ShortcutType
設計的很不好了解。
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
return false;
}
};
}
實作
apply
方法,内部建立
GatewayPredicate
匿名内部類。
public static class Config {
@NotEmpty
private List<String> sources = new ArrayList<>();
@NotNull
private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
};
public List<String> getSources() {
return sources;
}
public Config setSources(List<String> sources) {
this.sources = sources;
return this;
}
public Config setSources(String... sources) {
this.sources = Arrays.asList(sources);
return this;
}
public Config setRemoteAddressResolver(
RemoteAddressResolver remoteAddressResolver) {
this.remoteAddressResolver = remoteAddressResolver;
return this;
}
}
根據Predicate功能定義内部類Config。
綜上所述總結自定義Predicate要做的事情有如下幾點:
- 類名稱,以XXX開頭,RoutePredicateFactory結尾。
- 定義内部Config類,内部定義Predicate所需配置。
- 繼承了抽象類
,泛型為内部類AbstractRoutePredicateFactory
Config
- 重寫
和shortcutType
方法shortcutFieldOrder
- 實作
方法,内部建立apply
匿名内部類。GatewayPredicate
自定義黑名單Predicate
實作
實作黑名單可以通過配置的IP或者IP段進行限制。當請求進入時,擷取到目前請求的用戶端的IP,判斷是否與配置的黑名單比對,比對傳回false即可。
具體的實作邏輯見下方代碼即可,與SCG内置的
RemoteAddrRoutePredicateFactory
類似
/**
* Description:黑名單Predicate
* @author li.hongjian
* @email [email protected]
* @Date 2021/3/31
*/
public class BlackRemoteAddrRoutePredicateFactory
extends AbstractRoutePredicateFactory<BlackRemoteAddrRoutePredicateFactory.Config> {
public BlackRemoteAddrRoutePredicateFactory() {
super(Config.class);
}
@NotNull
private List<IpSubnetFilterRule> convert(List<String> values) {
List<IpSubnetFilterRule> sources = new ArrayList<>();
for (String arg : values) {
addSource(sources, arg);
}
return sources;
}
/**
* 此方法需重寫,用來建立Config使用
* @return
*/
@Override
public ShortcutType shortcutType() {
/**
* GATHER_LIST類型隻能有一個shortcutField
* {@link this#shortcutFieldOrder()}
*/
return GATHER_LIST;
}
/**
* 配置中的value對應的字段
* 比如目前我們的Config中的字段就為sources
* @return
*/
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("sources");
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
/**
* IpSubnetFilterRule是Netty中定義的IP過濾規則
*/
//根據配置的sources生成對應規則
List<IpSubnetFilterRule> sources = convert(config.sources);
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
InetSocketAddress remoteAddress = config.remoteAddressResolver
.resolve(exchange);
if (remoteAddress != null && remoteAddress.getAddress() != null) {
//隻要符合任意一個規則就傳回false,與RemoteAddrRoutePredicateFactory相反
for (IpSubnetFilterRule source : sources) {
if (source.matches(remoteAddress)) {
return false;
}
}
}
//如果沒有比對所有規則,則通過
return true;
}
};
}
private void addSource(List<IpSubnetFilterRule> sources, String source) {
//判斷是否配置了IP段,如果沒有則預設為最大為32,如配置172.15.32.15,則被修改為172.15.32.15/32
if (!source.contains("/")) { // no netmask, add default
source = source + "/32";
}
//假設配置的為 172.15.32.15/18
//根據'/'分割 [0]:172.15.32.15 [1]:18
String[] ipAddressCidrPrefix = source.split("/", 2);
String ipAddress = ipAddressCidrPrefix[0];
int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]);
//設定拒絕規則
sources.add(
new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.REJECT));
}
public static class Config{
/**
* 可配置多個IP/IP段
*/
@NotEmpty
private List<String> sources = new ArrayList<>();
/**
* 用來解析用戶端IP
*/
@NotNull
private RemoteAddressResolver remoteAddressResolver = new RemoteAddressResolver() {
};
public List<String> getSources() {
return sources;
}
public Config setSources(List<String> sources) {
this.sources = sources;
return this;
}
public Config setSources(String... sources) {
this.sources = Arrays.asList(sources);
return this;
}
public Config setRemoteAddressResolver(
RemoteAddressResolver remoteAddressResolver) {
this.remoteAddressResolver = remoteAddressResolver;
return this;
}
}
}
使用
spring:
cloud:
gateway:
routes:
- id: hello_route
uri: http://localhost:8088/api/hello
predicates:
- BlackRemoteAddr=169.254.183.1/18,172.17.31.1/18 #配置指定IP段限制
- Path=/api/hello
驗證
啟動SCG和一個本地服務并暴露接口為/api/hello,傳回結果為hello world。在自定義的
BlackRemoteAddrRoutePredicateFactory#test
中斷點.
**環境:本機IP **169.254.183.16。配置限制IP為
169.254.183.1/18
。此時我們的請求應該是會被拒絕傳回404狀态碼的。

可以看到
Predicate
擷取到了我們的IP,并且比對了我們配置的規則,是以傳回false,表示不比對該路由,是以傳回404.
修改配置為其他IP段,比如169.254.183.18/32,此時我們的IP是不符合該規則的,是以會放行。
重新開機項目再次測試。
可以看到我們的IP并沒有比對配置的規則,傳回true,表示可以走該路由。
上邊我對“重新開機”重點标注了,每次修改規則都要重新開機項目才能生效,那麼在實際用的時候,我們肯定想實作不重新開機就可以動态修改我們配置的規則,那麼該怎麼做呢?
我們可以通過将配置寫到外部一個源中,比如DB中,通過類似Nacos一樣定時發送一個重新整理配置的事件去實作重新整理Predicate配置。明天我們就來基于Redis來實作動态重新整理配置的功能。