【源码解析】Spring Cloud Gateway的断言和过滤器源码解析

news2024/11/14 19:52:03

路由断言(Route Predicate)工厂

Spring Cloud Gateway包括许多内置的路由断言(Route Predicate)工厂,所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合。

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#the-cookie-route-predicate-factory

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

该路由会转发 80% 的流量to https://weighthigh.org ,转发 20% 的流量 https://weightlow.org

路由判断

RoutePredicateHandlerMapping#lookupRoute,寻找路由,会将路由中的断言进行判断,判断通过才会返回路由。

	protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator
				.getRoutes()
				//individually filter routes so that filterWhen error delaying is not a problem
				.concatMap(route -> Mono
						.just(route)
						.filterWhen(r -> {
							// add the current route we are testing
							exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
							return r.getPredicate().apply(exchange);
						})
						//instead of immediately stopping main flux due to error, log and swallow it
						.doOnError(e -> logger.error("Error applying predicate for route: "+route.getId(), e))
						.onErrorResume(e -> Mono.empty())
				)
				// .defaultIfEmpty() put a static Route not found
				// or .switchIfEmpty()
				// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
				.next()
				//TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("Route matched: " + route.getId());
					}
					validateRoute(route, exchange);
					return route;
				});

		/* TODO: trace logging
			if (logger.isTraceEnabled()) {
				logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
			}*/
	}

权重路由

WeightCalculatorWebFilter#filter,获取groupWeights中的config.ranges,判断当前获取的随机数是否在该区间,将路由信息存放在weights中。

	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		Map<String, String> weights = getWeights(exchange);

		groupWeights.forEach((group, config) -> {
			double r = this.random.nextDouble();

			List<Double> ranges = config.ranges;

			if (log.isTraceEnabled()) {
				log.trace("Weight for group: "+group +", ranges: "+ranges +", r: "+r);
			}

			for (int i = 0; i < ranges.size() - 1; i++) {
				if (r >= ranges.get(i) && r < ranges.get(i+1)) {
					String routeId = config.rangeIndexes.get(i);
					weights.put(group, routeId);
					break;
				}
			}
		});

		if (log.isTraceEnabled()) {
			log.trace("Weights attr: "+weights);
		}

		return chain.filter(exchange);
	}

	static Map<String, String> getWeights(ServerWebExchange exchange) {
		Map<String, String> weights = exchange.getAttribute(WEIGHT_ATTR);

		if (weights == null) {
			weights = new ConcurrentHashMap<>();
			exchange.getAttributes().put(WEIGHT_ATTR, weights);
		}
		return weights;
	}

WeightCalculatorWebFilter#onApplicationEvent监听到PredicateArgsEvent会进行初始化

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof PredicateArgsEvent) {
			handle((PredicateArgsEvent) event);
		} else if (event instanceof WeightDefinedEvent) {
			addWeightConfig(((WeightDefinedEvent)event).getWeightConfig());
		}

	}

	public void handle(PredicateArgsEvent event) {
		Map<String, Object> args = event.getArgs();

		if (args.isEmpty() || !hasRelevantKey(args)) {
			return;
		}

		WeightConfig config = new WeightConfig(event.getRouteId());

		ConfigurationUtils.bind(config, args,
				WeightConfig.CONFIG_PREFIX, WeightConfig.CONFIG_PREFIX, validator);

		addWeightConfig(config);
	}

WeightCalculatorWebFilter#addWeightConfig,更新config.ranges的数据。

	void addWeightConfig(WeightConfig weightConfig) {
		String group = weightConfig.getGroup();
		GroupWeightConfig c = groupWeights.get(group);
		if (c == null) {
			c = new GroupWeightConfig(group);
			groupWeights.put(group, c);
		}
		GroupWeightConfig config = c;
		config.weights.put(weightConfig.getRouteId(), weightConfig.getWeight());

		//recalculate

		// normalize weights
		int weightsSum = config.weights.values().stream().mapToInt(Integer::intValue).sum();

		final AtomicInteger index = new AtomicInteger(0);
		config.weights.forEach((routeId, weight) -> {
			Double nomalizedWeight = weight / (double) weightsSum;
			config.normalizedWeights.put(routeId, nomalizedWeight);

			// recalculate rangeIndexes
			config.rangeIndexes.put(index.getAndIncrement(), routeId);
		});

		//TODO: calculate ranges
		config.ranges.clear();

		config.ranges.add(0.0);

		List<Double> values = new ArrayList<>(config.normalizedWeights.values());
		for (int i = 0; i < values.size(); i++) {
			Double currentWeight = values.get(i);
			Double previousRange = config.ranges.get(i);
			Double range = previousRange + currentWeight;
			config.ranges.add(range);
		}

		if (log.isTraceEnabled()) {
			log.trace("Recalculated group weight config "+ config);
		}
	}

WeightRoutePredicateFactory#apply,根据exchange的属性weights,获取到选择的路由chosenRoute。

	@Override
	public Predicate<ServerWebExchange> apply(WeightConfig config) {
		return exchange -> {
			Map<String, String> weights = exchange.getAttributeOrDefault(WEIGHT_ATTR,
					Collections.emptyMap());

			String routeId = exchange.getAttribute(GATEWAY_PREDICATE_ROUTE_ATTR);

			// all calculations and comparison against random num happened in
			// WeightCalculatorWebFilter
			String group = config.getGroup();
			if (weights.containsKey(group)) {

				String chosenRoute = weights.get(group);
				if (log.isTraceEnabled()) {
					log.trace("in group weight: "+ group + ", current route: " + routeId +", chosen route: " + chosenRoute);
				}

				return routeId.equals(chosenRoute);
			}

			return false;
		};
	}

路由过滤器

路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应。Spring Cloud Gateway内置了多种路由过滤器,由GatewayFilter的工厂类禅城。更多资料可以看官方文档:https://docs.spring.io/spring-cloud-gateway/docs/2.2.9.RELEASE/reference/html/#gatewayfilter-factories

责任链处理

FilteringWebHandler#handle,根据路由获取到GatewayFilter的集合,再添加全局的路由器,构成责任链,执行filter方法。

	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);
		List<GatewayFilter> gatewayFilters = route.getFilters();

		List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);
		combined.addAll(gatewayFilters);
		//TODO: needed or cached?
		AnnotationAwareOrderComparator.sort(combined);

		if (logger.isDebugEnabled()) {
			logger.debug("Sorted gatewayFilterFactories: "+ combined);
		}

		return new DefaultGatewayFilterChain(combined).filter(exchange);
	}

StripPrefixGatewayFilterFactory

StripPrefixGatewayFilterFactory#apply,获取到uri的路径,用/切分,过滤掉第n个。

public class StripPrefixGatewayFilterFactory extends AbstractGatewayFilterFactory<StripPrefixGatewayFilterFactory.Config> {

	public static final String PARTS_KEY = "parts";

	public StripPrefixGatewayFilterFactory() {
		super(Config.class);
	}

	@Override
	public List<String> shortcutFieldOrder() {
		return Arrays.asList(PARTS_KEY);
	}

	@Override
	public GatewayFilter apply(Config config) {
		return (exchange, chain) ->  {
			ServerHttpRequest request = exchange.getRequest();
			addOriginalRequestUrl(exchange, request.getURI());
			String path = request.getURI().getRawPath();
			String newPath = "/" + Arrays.stream(StringUtils.tokenizeToStringArray(path, "/"))
					.skip(config.parts).collect(Collectors.joining("/"));
			newPath += (newPath.length() > 1 && path.endsWith("/") ? "/" : "");
			ServerHttpRequest newRequest = request.mutate()
					.path(newPath)
					.build();

			exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newRequest.getURI());

			return chain.filter(exchange.mutate().request(newRequest).build());
		};
	}

	public static class Config {
		private int parts;

		public int getParts() {
			return parts;
		}

		public void setParts(int parts) {
			this.parts = parts;
		}
	}

}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/488432.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TCP协议结构

文章目录 TCP---传输控制协议TCP报文结构 TCP—传输控制协议 缓冲区的意义 TCP协议是自带发送和接收缓冲区的&#xff0c;相当于malloc了两段内存空间。 系统调用接口send,write等并不是直接把数据发送到网络上&#xff0c;而是把数据拷贝到TCP的发送缓冲区&#xff0c;至此应…

Java读取Properties配置文件的6种方式

Java读取Properties的方式 项目结构&#xff1a;经典的maven项目结构 配置文件1和2内容一致&#xff1a; jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urlmysql://localhost:3306/database?useUnicodetrue&characterEncodingutf-8&serverTimezoneAsia/Shanghai jdbc.…

Axios二次封装

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、Axios是什么&#xff1f;二、为什么要对Axios进行二次封装三、Axios二次封装1.首先&#xff0c;安装 Axios&#xff1a;2.创建一个名为 http.js 的文件&#xf…

AI 工具合辑盘点(三)持续更新

人工智能技术的发展已经改变了我们的生活&#xff0c;越来越多的AI工具正在被广泛应用于各个领域。ChatGPT这样的代表性AI模型正在大放异彩&#xff0c;为我们带来了无数的便利和惊喜。在本文中&#xff0c;我们将介绍一系列优秀的AI工具&#xff0c;这些工具可以帮助你完成各种…

课程教学大纲系统的设计与实现

技术栈&#xff1a; Nginx、MySQL、Maven、SpringBoot、Spring、SpringMVC、MyBatis、HikariCP、fastjson、slf4j、Vue、NodeJS系统功能&#xff1a; 本系统分教师和管理员两种角色&#xff0c;不同角色可操作的功能不尽相同&#xff0c;各个角色具体功能如下&#xff1a;教师 …

Python实现哈里斯鹰优化算法(HHO)优化随机森林回归模型(RandomForestRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 2019年Heidari等人提出哈里斯鹰优化算法(Harris Hawk Optimization, HHO)&#xff0c;该算法有较强的全…

SpringCloud------Eureka单机版整合生产以及消费(四)

SpringCloud------Eureka&#xff08;四&#xff09; Eureka 读音&#xff1a;有瑞卡 Eureka基础知识 单机构架步骤 集群构建步骤 autuator微服务信息完善 服务发现Discovery eureka自我保护 服务注册与发现 包括&#xff1a; Eureka Zookeeper Consul Nacos 【分布式的CAP理…

FlinkCDC初体验

一、CDC简介 1.1 什么是CDC&#xff1f; CDC是 Change Data Capture(变更数据获取 )的简称。 核心思想是&#xff0c;监测并捕获数据库的 变动&#xff08;包括数据或数据表的插入 、 更新 以及 删除等&#xff09;&#xff0c;将这些变更按发生的顺序完整记录下 来&#xff0c…

【Flask】Python基于Flask应用

Flask介绍 Flask 是一款发布于2010年非常流行的 Python Web 框架。 特点 微框架、简洁&#xff0c;给开发者提供了很大的扩展性。Flask和相应的插件写得很好&#xff0c;用起来很爽。 开发效率非常高&#xff0c;比如使用 SQLAlchemy 的 ORM 操作数据库可以节省开发者大量书…

【LeetCode】数据结构题解(5)[分割链表]

分割链表 1.题目来源2.题目描述3.解题思路4.代码展示 所属专栏&#xff1a;玩转数据结构题型 博主首页&#xff1a;初阳785 代码托管&#xff1a;chuyang785 感谢大家的支持&#xff0c;您的点赞和关注是对我最大的支持&#xff01;&#xff01;&#xff01; 博主也会更加的努力…

聊一聊 GDB 调试程序时的几个实用命令

一&#xff1a;背景 1. 讲故事 用惯了宇宙第一的 Visual Studio 再用其他的开发工具还是有一点不习惯&#xff0c;不习惯在于想用的命令或者面板找不到&#xff0c;总的来说还是各有千秋吧&#xff0c;今天我们来聊一下几个在调试中比较实用的命令&#xff1a; 查看内存硬件…

B站java、计算机学习整理(菜鸟版本)

B站java、计算机学习整理&#xff08;菜鸟版本&#xff09; 简介1、入门篇2、工具篇3、数据库篇4、框架篇5、JVM 篇6、源码篇7、算法与数据结构8、操作系统9、计算机组成原理10、计算机网络11、 设计模式 简介 处在互联网时代&#xff0c;是一种幸福&#xff0c;因为各式各样的…

Win10系统开机自动蓝屏无法使用怎么U盘重装系统?

Win10系统开机自动蓝屏无法使用怎么U盘重装系统&#xff1f;今天和大家一起来分享Win10系统蓝屏之后怎么去进行修复的方法。很多用户都有遇到电脑蓝屏无法启动的问题&#xff0c;那么遇到这个问题之后怎么去重装系统呢&#xff1f;接下来我们来看看以下的解决方法分享吧。 准备…

Python突破某网游游戏JS加密限制,进行逆向解密,实现自动登录

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 今天来分享一下如何使用Python突破某网游游戏JS加密限制&#xff0c;进行逆向解密&#xff0c;实现自动登录。 逆向目标 目标&#xff1a;某 7 网游登录 主页&#xff1a;aHR0cHM6Ly93d3cuMzcuY29tLw 接口&#xff1a;aHR…

牛客_华为_ HJ63 DNA序列

HJ63 DNA序列 st input() n int(input())max_ratio 0 ratio 0 res for i in range(0,len(st)-n1):s st[i:in]ratio s.count(C)s.count(G)if ratio > max_ratio:max_ratio ratiores s print(res)

cPanel XSS漏洞分析研究(CVE-2023-29489)

一、漏洞原理 漏洞简述 cPanel 是一套在网页寄存业中最享负盛名的商业软件&#xff0c;是基于于 Linux 和 BSD 系统及以 PHP 开发且性质为闭源软件&#xff1b;提供了足够强大和相当完整的主机管理功能&#xff0c;诸如&#xff1a;Webmail 及多种电邮协议、网页化 FTP 管理、…

【考前看几题】系统集成项目管理师-2022年上半年-上午真题(广东卷)

前言 汇总知识点、重点问题、难点 由问题引出知识点 软件技术、其他技术、管理基础、整体管理、范围管理、成本管理、人力资源管理 干系人管理、合同管理、采购管理、配置管理、质量管理、风险管理、安全管理 文章目录 前言软件技术、其他技术管理基础整体管理范围管理成本管理…

第12届蓝桥杯国赛真题剖析-2021年5月29日Scratch编程初中级组

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第128讲。 第12届蓝桥杯Scratch国赛真题&#xff0c;这是2021年5月29日举办的全国总决赛&#xff0c;比赛仍然采取线上…

【java】Java 异常处理的十个建议

文章目录 前言一、尽量不要使用e.printStackTrace(),而是使用log打印。二、catch了异常&#xff0c;但是没有打印出具体的exception&#xff0c;无法更好定位问题三、不要用一个Exception捕捉所有可能的异常四、记得使用finally关闭流资源或者直接使用try-with-resource五、捕获…

TCP协议特性讲解

文章目录 TCP报文结构确认应答超时重传三次握手与四次挥手滑动窗口流量控制拥塞控制延时应答捎带应答面向字节流 - 粘包问题异常处理 - 心跳包 TCP报文结构 16位源端口号&#xff1a;表示数据从哪来的。 16位目的端口号&#xff1a;表示数据要到哪里去。 32位序号&#xff1a;由…