博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud Gateway(六):路由谓词工厂 RoutePredicateFactory
阅读量:5050 次
发布时间:2019-06-12

本文共 7414 字,大约阅读时间需要 24 分钟。

本文基于 spring cloud gateway 2.0.1

1、简介

Spring Cloud Gateway 创建 Route 对象时, 使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。 Spring Cloud Gateway 包含许多内置的Route Predicate Factories。所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and。

路由选择是通过Predicate函数式接口进行判断当前路由是否满足给定条件。

image

路由谓词工厂 RoutePredicateFactory 包含的主要实现类如图所示,可以看到该接口有多个实现类。抽象类 AbstractRoutePredicateFactory 实现了路由谓词工厂,但是没有实际的方法,具体的实现类都是继承自抽象类 AbstractRoutePredicateFactory 包括 Datetime、 请求的远端地址、 路由权重、 请求头、 Host 地址、 请求方法、 请求路径 和 请求参数等类型的路由断言。

2、路由谓词工厂 RoutePredicateFactory

按照功能对路由谓词工厂进行划分,可以划分为以下几种,如图所示:

路由谓词工厂功能划分

@FunctionalInterfacepublic interface RoutePredicateFactory
extends ShortcutConfigurable, Configurable
{ String PATTERN_KEY = "pattern"; // useful for javadsl default Predicate
apply(Consumer
consumer) { C config = newConfig(); consumer.accept(config); beforeApply(config); return apply(config); } default AsyncPredicate
applyAsync(Consumer
consumer) { C config = newConfig(); consumer.accept(config); beforeApply(config); return applyAsync(config); } default Class
getConfigClass() { throw new UnsupportedOperationException("getConfigClass() not implemented"); } @Override default C newConfig() { throw new UnsupportedOperationException("newConfig() not implemented"); } default void beforeApply(C config) {} Predicate
apply(C config); default AsyncPredicate
applyAsync(C config) { return toAsyncPredicate(apply(config)); } default String name() { return NameUtils.normalizeRoutePredicateName(getClass()); }}

RoutePredicateFactory 接口继承自 ShortcutConfigurable 接口,ShortcutConfigurable 接口在多个实现类中都有出现,根据传入的具体 RouteDefinitionLocator 获取路由定义对象时,就用到了该接口中的默认方法。路由谓词的种类很多,不同的谓词需要的配置参数不一样,所以每种 路由谓词(断言)和过滤器的实现都会实现 ShortcutConfigurable 接口,来指定自身参数的个数和顺序。

2.1、ShortcutConfigurable

ShortcutConfigurable 接口提供的默认方法,主要用于对过滤器和断言参数进行标准化处理,将表达式和生成的键进行转换。

public interface ShortcutConfigurable {	enum ShortcutType {		DEFAULT {			@Override			public Map
normalize(Map
args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) { Map
map = new HashMap<>(); int entryIdx = 0; for (Map.Entry
entry : args.entrySet()) { String key = normalizeKey(entry.getKey(), entryIdx, shortcutConf, args); Object value = getValue(parser, beanFactory, entry.getValue()); map.put(key, value); entryIdx++; } return map; } }, GATHER_LIST { @Override public Map
normalize(Map
args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory) { Map
map = new HashMap<>(); // field order should be of size 1 List
fieldOrder = shortcutConf.shortcutFieldOrder(); Assert.isTrue(fieldOrder != null && fieldOrder.size() == 1, "Shortcut Configuration Type GATHER_LIST must have shortcutFieldOrder of size 1"); String fieldName = fieldOrder.get(0); map.put(fieldName, args.values().stream() .map(value -> getValue(parser, beanFactory, value)) .collect(Collectors.toList())); return map; } }; public abstract Map
normalize(Map
args, ShortcutConfigurable shortcutConf, SpelExpressionParser parser, BeanFactory beanFactory); } static String normalizeKey(String key, int entryIdx, ShortcutConfigurable argHints, Map
args) { /*************************(1)*********************************/ // RoutePredicateFactory has name hints and this has a fake key name // replace with the matching key hint if (key.startsWith(NameUtils.GENERATED_NAME_PREFIX) && !argHints.shortcutFieldOrder().isEmpty() && entryIdx < args.size() && entryIdx < argHints.shortcutFieldOrder().size()) { key = argHints.shortcutFieldOrder().get(entryIdx); } return key; } static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, String entryValue) { /*************************(2)*********************************/ Object value; String rawValue = entryValue; if (rawValue != null) { rawValue = rawValue.trim(); } if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { /*************************(3)*********************************/ // assume it's spel StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new BeanFactoryResolver(beanFactory)); Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); value = expression.getValue(context); } else { value = entryValue; } return value; } default ShortcutType shortcutType() { return ShortcutType.DEFAULT; } /** * Returns hints about the number of args and the order for shortcut parsing. * @return */ default List
shortcutFieldOrder() { /*************************(4)*********************************/ return Collections.emptyList(); } default String shortcutFieldPrefix() { return ""; }}

以上代码主要做了以下工作:

1)对键进行标准化处理,因为键有可能是自动生成,当键以_ genkey_ 开头时, 表明是自动生成的。

2)获取真实值,需要传入 Spring EL 解析器、Bean工厂等工具类。

3)对传入的 entryValue 是一个表达式的情况进行处理,这里默认是 Spring EL 表达式。

4)返回有关参数数量和解析顺序的提示。

3、Datetime 类型的路由谓词工厂

Datetime 类型的路由谓词工厂有三种,分别为:

  • AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日志

  • BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日志

  • BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

以 AfterRoutePredicateFactory 为例,介绍 Datetime 类型的断言工厂的应用:

spring:    cloud:         gateway:            routes:             - id: after_route_id            uri: http://www.baidu.com            predicates:             - After= 2018-12-30T23:59:59.789+08:00[Asia/Shanghai]

上面的配置文件指定了路由的断言,谓词关键字是 After ,表示请求时间必须晚于上海时间 2018年12月30日 23:59:59 才可用。

4、基于远程地址(RemoteAttr)的路由谓词工厂

RemoteAddrRoutePredicateFactory 属于根据请求IP进行路由类型,接收 CIDR表示 法(IPv4或IPv6)的字符串列表(列表最小长度为1)作为参数, 例如 192.168.0.1/ 16, 其中 192. 168. 0. 1 是 IP 地址, 16 是 子 网 掩 码。

spring:     cloud:        gateway:             routes:            -id: remoteaddr_ route_id            uri: http://www.baidu.com            predicates:            -RemoteAddr=192.168.1.1/24

以上配置表示如果请求的远程地址是 192.168.1.10,将会匹配该路由。

// RemoteAddrRoutePredicateFactory.java

@Override	public Predicate
apply(Config config) { List
sources = convert(config.sources); return exchange -> { //获取请求中的远程地址 InetSocketAddress remoteAddress = config.remoteAddressResolver.resolve(exchange); if (remoteAddress != null) { String hostAddress = remoteAddress.getAddress().getHostAddress(); String host = exchange.getRequest().getURI().getHost(); if (log.isDebugEnabled() && !hostAddress.equals(host)) { log.debug("Remote addresses didn't match " + hostAddress + " != " + host); } //遍历配置好的 RemoteAddr 列表, 如果远程地址在列表中则匹配成功 for (IpSubnetFilterRule source : sources) { if (source.matches(remoteAddress)) { return true; } } } return false; }; } private void addSource(List
sources, String source) { if (!source.contains("/")) { //当 RemoteAddr 没有子网掩码时,默认为/32 source = source + "/32"; } String[] ipAddressCidrPrefix = source.split("/",2); String ipAddress = ipAddressCidrPrefix[0]; int cidrPrefix = Integer.parseInt(ipAddressCidrPrefix[1]); //根据 ip 和 子网,确定 RemoteAddr 的范围,并加入到 sources 中 sources.add(new IpSubnetFilterRule(ipAddress, cidrPrefix, IpFilterRuleType.ACCEPT)); }

基于远程地址匹配的路由断言实现,首先获取配置文件中的 RemoteAttr 列表,然后将配置的 RemoteAttr 列表转换成 sources 列表,主要是根据IP地址和子网掩码确定地址范围,最后判断请求的远程地址是否在设置的 IP 列表中。

转载于:https://www.cnblogs.com/liukaifeng/p/10055867.html

你可能感兴趣的文章
PHP传值和传引用、传地址的区别
查看>>
【Leetcode】Search in Rotated Sorted Array II
查看>>
蓬佐错觉:蓬佐错觉
查看>>
Redis:目录
查看>>
[usaco3.3.2]shopping
查看>>
分享一下:2011回顾:20个将JavaScript推到极致的网站(转)
查看>>
作业5.2 对5.1内容的完善体会
查看>>
使用antd UI 制作菜单
查看>>
五分钟入门 Dingo API
查看>>
带入gRPC:对 RPC 方法做自定义认证
查看>>
HP下kafka的实践
查看>>
Buffer.compare()
查看>>
buf.writeInt32BE()函数详解
查看>>
网络编程-Python的socket库
查看>>
●数组及应用举例
查看>>
Ajax表单提交插件jquery form
查看>>
个人作业2——英语学习APP案例分析
查看>>
Oracle中的数据字典技术初级入门
查看>>
Python发送邮件
查看>>
Apache-Superset 汉化教程
查看>>