【SpringCloud】Zuul源码解析

news2025/2/23 10:53:49

e246a1dda09849a5a89535a62441565d.png

Zuul是一个网关组件,是微服务的入口,网关会根据配置将请求转发给指定的服务。本章分析Zuul组件是如何实现请求过滤和转发的

参考源码:<spring-cloud.version>Hoxton.SR9</spring-cloud.version>

1、过滤

spring-cloud-netflix-zuul依赖包下有一个spring.factories文件,文件内容如下

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.zuul.ZuulServerAutoConfiguration,\
org.springframework.cloud.netflix.zuul.ZuulProxyAutoConfiguration

根据springboot自动配置的原理可知,ZuulServerAutoConfiguration会被标记成了一个自动配置类,其中和Zuul组件核心功能相关的是ZuulServlet

@Bean
@ConditionalOnMissingBean(
	name = {"zuulServlet"}
)
@ConditionalOnProperty(
	name = {"zuul.use-filter"},
	havingValue = "false",
	matchIfMissing = true
)
public ServletRegistrationBean zuulServlet() {
	ServletRegistrationBean<ZuulServlet> servlet = new ServletRegistrationBean(new ZuulServlet(), new String[]{this.zuulProperties.getServletPattern()});
	servlet.addInitParameter("buffer-requests", "false");
	return servlet;
}

ZuulServlet会处理所有的请求,在service方法内依次调用preRoute、route、postRoute方法,分别对应前置,路由和后置处理器

public class ZuulServlet extends HttpServlet {
 
    public ZuulServlet() {
    }

    public void init(ServletConfig config) throws ServletException {
        super.init(config);
        String bufferReqsStr = config.getInitParameter("buffer-requests");
        boolean bufferReqs = bufferReqsStr != null && bufferReqsStr.equals("true");
        this.zuulRunner = new ZuulRunner(bufferReqs);
    }

    // 处理请求
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        try {
            this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse);
            RequestContext context = RequestContext.getCurrentContext();
            context.setZuulEngineRan();

            try {
                this.preRoute();
            } catch (ZuulException var12) {
                this.error(var12);
                this.postRoute();
                return;
            }

            try {
                this.route();
            } catch (ZuulException var13) {
                this.error(var13);
                this.postRoute();
                return;
            }

            try {
                this.postRoute();
            } catch (ZuulException var11) {
                this.error(var11);
            }
        } catch (Throwable var14) {
            this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName()));
        } finally {
            RequestContext.getCurrentContext().unset();
        }
    }

    void preRoute() throws ZuulException {
        this.zuulRunner.preRoute();
    }
}

以preRoute方法为例,zuul网关会对请求做前置处理,遍历ZuulFilter接口的实现类执行runFilter方法

调用链:
-> ZuulServlet.preRoute
-> ZuulRunner.preRoute
-> FilterProcessor.preRoute
-> runFilters
public Object runFilters(String sType) throws Throwable {
	if (RequestContext.getCurrentContext().debugRouting()) {
		Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
	}

	boolean bResult = false;

	// 获取指定类型的filter
	List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
	if (list != null) {
		for(int i = 0; i < list.size(); ++i) {
			ZuulFilter zuulFilter = (ZuulFilter)list.get(i);

			// 遍历ZuulFilter实现类,前置处理请求
			Object result = this.processZuulFilter(zuulFilter);
			if (result != null && result instanceof Boolean) {
				bResult |= (Boolean)result;
			}
		}
	}

	return bResult;
}

public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
	...
	// 调用ZuulFilter实现类的runFilter方法
	ZuulFilterResult result = filter.runFilter();
	...
}

public ZuulFilterResult runFilter() {
	...
	// run方法来自IZuulFilter接口
	Object res = this.run();
	...
}

请求传递给了ZuulFitler的实现类,遍历这些实现类时调用它们的run方法,请求过滤的逻辑一般就写在run方法中

2、转发

2.1 获取路由信息

PreDecorationFilter是zuul依赖包下的一个Filter,它实现了ZuulFilter抽象类,会在处理请求时被调用

public Object run() {
	RequestContext ctx = RequestContext.getCurrentContext();
	String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());

	// 获取请求的路由信息
	Route route = this.routeLocator.getMatchingRoute(requestURI);
	String location;
	...
}

this.routeLocator指向的是SimpleRouteLocator对象,查看它的getMatchingRoute方法

public Route getMatchingRoute(final String path) {
	return this.getSimpleMatchingRoute(path);
}

protected Route getSimpleMatchingRoute(final String path) {
	if (log.isDebugEnabled()) {
		log.debug("Finding route for path: " + path);
	}

	this.getRoutesMap();
	if (log.isDebugEnabled()) {
		log.debug("servletPath=" + this.dispatcherServletPath);
		log.debug("zuulServletPath=" + this.zuulServletPath);
		log.debug("RequestUtils.isDispatcherServletRequest()=" + RequestUtils.isDispatcherServletRequest());
		log.debug("RequestUtils.isZuulServletRequest()=" + RequestUtils.isZuulServletRequest());
	}

	String adjustedPath = this.adjustPath(path);
	
	// 获取路由信息
	ZuulProperties.ZuulRoute route = this.getZuulRoute(adjustedPath);
	return this.getRoute(route, adjustedPath);
}

protected ZuulProperties.ZuulRoute getZuulRoute(String adjustedPath) {
   
	// 匹配路由地址
	if (!this.matchesIgnoredPatterns(adjustedPath)) {

		// 遍历路由map
		Iterator var2 = this.getRoutesMap().entrySet().iterator();

		while(var2.hasNext()) {
			Map.Entry<String, ZuulProperties.ZuulRoute> entry = (Map.Entry)var2.next();
			String pattern = (String)entry.getKey();
			log.debug("Matching pattern:" + pattern);
			if (this.pathMatcher.match(pattern, adjustedPath)) {
				return (ZuulProperties.ZuulRoute)entry.getValue();
			}
		}
	}

	return null;
}

原始的路由信息来源于SimpleRouteLocator的routes成员,是一个map对象,调用getRoutesMap方法时如果routes对象为空会对它进行初始化

protected Map<String, ZuulProperties.ZuulRoute> getRoutesMap() {
	if (this.routes.get() == null) {
		// 初始化routes
		this.routes.set(this.locateRoutes());
	}

	return (Map)this.routes.get();
}

protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
	LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap();
	
	// 获取配置文件中的路由信息
	Iterator var2 = this.properties.getRoutes().values().iterator();

	while(var2.hasNext()) {
		ZuulProperties.ZuulRoute route = (ZuulProperties.ZuulRoute)var2.next();
		routesMap.put(route.getPath(), route);
	}

	return routesMap;
}

配置文件中的路由信息封装在了ZuulProperties对象内,供初始化使用。拿到route后,PreDecorationFilter会在请求上下文中放入路由信息,这些路由信息会为后续ZuulFilter转发服务提供依据

public Object run() {
	...
	if (route != null) {
		location = route.getLocation();
		if (location != null) {
			ctx.put("requestURI", route.getPath());
			ctx.put("proxy", route.getId());
			...

			if (route.getRetryable() != null) {
				ctx.put("retryable", route.getRetryable());
			}

			if (!location.startsWith("http:") && !location.startsWith("https:")) {
				if (location.startsWith("forward:")) {
					ctx.set("forward.to", StringUtils.cleanPath(location.substring("forward:".length()) + route.getPath()));
					ctx.setRouteHost((URL)null);
					return null;
				} 

				// 置入serviceId
				ctx.set("serviceId", location);
				ctx.setRouteHost((URL)null);
				ctx.addOriginResponseHeader("X-Zuul-ServiceId", location);
			} else {

				// 置入路由url
				ctx.setRouteHost(this.getUrl(location));
				ctx.addOriginResponseHeader("X-Zuul-Service", location);
			}

			if (this.properties.isAddProxyHeaders()) {
				this.addProxyHeaders(ctx, route);
				String xforwardedfor = ctx.getRequest().getHeader("X-Forwarded-For");
				String remoteAddr = ctx.getRequest().getRemoteAddr();
				if (xforwardedfor == null) {
					xforwardedfor = remoteAddr;
				} else if (!xforwardedfor.contains(remoteAddr)) {
					xforwardedfor = xforwardedfor + ", " + remoteAddr;
				}

				ctx.addZuulRequestHeader("X-Forwarded-For", xforwardedfor);
			}

			if (this.properties.isAddHostHeader()) {
				ctx.addZuulRequestHeader("Host", this.toHostHeader(ctx.getRequest()));
			}
		}
	} 
	...
}

2.2 具体转发

Zuul支持以下三种类型的转发

转发类型ZuulFilter实现类配置文件案例
serviceId转发RibbonRoutingFilterzuul.routes.test-service.serviceId=test-service
url转发SimpleHostRoutingFilterzuul.routes.test-service.url=http://localhost:8080/
forward转发SendForwardFilterforward:/test

以RibbonRoutingFilter为例,当请求上下文中包含serviceId时,会调用RibbonRoutingFilter对象的run方法转发请求,具体的请求和负载均衡相关,就不加赘述了

// 判断是否启用过滤器
public boolean shouldFilter() {
	RequestContext ctx = RequestContext.getCurrentContext();
	// 上下文包含serviceId
	return ctx.getRouteHost() == null && ctx.get("serviceId") != null 
			 && ctx.sendZuulResponse();
}

public Object run() {
	RequestContext context = RequestContext.getCurrentContext();
	this.helper.addIgnoredHeaders(new String[0]);

	try {
		// 转发请求,获取响应信息
		RibbonCommandContext commandContext = this.buildCommandContext(context);
		ClientHttpResponse response = this.forward(commandContext);
		this.setResponse(response);
		return response;
	} catch (ZuulException var4) {
		throw new ZuulRuntimeException(var4);
	} catch (Exception var5) {
		throw new ZuulRuntimeException(var5);
	}
}

3、总结

Zuul组件以ZuulServlet为核心处理请求,将处理请求的过程分化成了请求前置、路由、后置三个阶段,而具体的处理逻辑则下放在ZuulFilter的实现类中

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

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

相关文章

重温react-13(嵌套路由和重定向等)

重定向和404 import React from react; import { Routes, Route, Link,NavLink ,Navigate} from react-router-dom; import Home from ./Home/Home import About from ./About/About import News from ./News/News import NotFound from ./NotFound/NotFound; export default …

51单片机第18步_将TIM0用作13位定时器

本章重点学习将TIM0用作13位定时器。 1、定时器0工作在模式0框图 2、定时器0工作在模式0举例 1、Keil C51中有一些关键字&#xff0c;需要牢记&#xff1a; interrupt 0&#xff1a;指定当前函数为外部中断0&#xff1b; interrupt 1&#xff1a;指定当前函数为定时器0中断…

VUE 修改密码功能+密码强度校验

效果图 <template><el-dialogtitle"修改密码":visible.sync"dialog":before-close"cancel":close-on-click-modal"false"width"500px":modal"false"><el-form ref"form" :model"…

SpringMvc 执行原理

当用户请求 会发送到前端控制器&#xff0c;DisptcherServlet根据请求参数生成代理请求&#xff0c;找到对应的实际控制器&#xff0c;控制器处理请求&#xff0c;创建数据模型&#xff0c;访问数据库&#xff0c;将模型响应给中心控制器&#xff0c;控制器使用模型与视图渲染视…

算法题型归类整理及同类题型解法思路总结(持续更新)

1、最优路线 通用思路 1、递归 #案例1-最优路测路线 题目描述 评估一个网络的信号质量&#xff0c;其中一个做法是将网络划分为栅格&#xff0c;然后对每个栅格的信号质量计算。 路测的时候&#xff0c;希望选择一条信号最好的路线&#xff08;彼此相连的栅格集合&#x…

人工智能的目标分类

欢迎来到 Papicatch的博客 目录 &#x1f349;引言 &#x1f349;目标分类的概述 &#x1f348;背景 &#x1f348;分类的重要性 &#x1f34d;明确研究重点 &#x1f34d;促进应用推广 &#x1f34d;便于评估和比较 &#x1f348;分类的原则 &#x1f34d;基于应用领…

F_GETDOWN的例子

代码&#xff1a; 7:46 2024/7/1#include <unistd.h> #include <fcntl.h> #include <stdio.h> int main(void) {int uid;int fdopen("test.txt",O_RDWR);uidfcntl(fd,F_GETOWN);printf("the SIG recv ID is %d\n",uid);close(fd);retur…

使用 Vue 实现包含单选框的弹窗功能(附Demo)

目录 前言1. Vue22. Vue3 前言 如果在弹窗中单独增设一些选项或者少部分的数据&#xff0c;可用如下的方式 &#xff08;不用单独创建专门的表单样式&#xff09; 如果单纯可以通过基本的按钮传输给后端&#xff0c;可用如下知识点 对于弹窗的基本知识推荐阅读&#xff1a; …

了解 ZooKeeper:关键概念和架构

ZooKeeper 是一种分布式协调服务&#xff0c;广泛用于分布式系统中&#xff0c;用于维护配置信息、命名、同步和组服务。它最初由雅虎开发&#xff0c;现在是一个 Apache 项目&#xff0c;已成为许多大型分布式应用程序不可或缺的一部分。本文深入探讨 ZooKeeper 的关键概念和架…

(四)Appdesigner-文件存在判断及对话框设计

目录 前言 一、文件存在判断 &#xff08;一&#xff09;基础知识 &#xff08;二&#xff09;实际操作 二、对话框设计 &#xff08;一&#xff09;基础知识 1.提示对话框 2.询问对话框 3.文件选择对话框 &#xff08;二&#xff09;实际操作 1.提示对话框 2.询问…

考研生活day1--王道课后习题2.2.1、2.2.2、2.2.3

2.2.1 题目描述&#xff1a; 解题思路&#xff1a; 这是最基础的操作&#xff0c;思路大家应该都有&#xff0c;缺少的应该是如何下笔&#xff0c;很多同学都是有思路但是不知道如何下笔&#xff0c;这时候看思路的意义不大&#xff0c;可以直接看答案怎么写&#xff0c;最好…

关于内存和外存文件不同字符集下占用空间大小问题

关于内存和外存不同字符集下文件占用空间大小问题 存储&#xff08;外存&#xff09;的文件中的字符&#xff1a; ASCII&#xff1a;每个字符占用1个字节&#xff0c;用来存储英文字符和常用标点符号。ISO-8859-1&#xff1a;每个字符占用1个字节&#xff0c;向下兼容ASCII。G…

【代码随想录】【算法训练营】【第53天】 [739]每日温度 [496]下一个更大元素I [503]下一个更大元素II

前言 思路及算法思维&#xff0c;指路 代码随想录。 题目来自 LeetCode。 day 48&#xff0c;周六&#xff0c;不能再坚持~ 题目详情 [739] 每日温度 题目描述 739 每日温度 解题思路 前提&#xff1a; 思路&#xff1a; 重点&#xff1a; 代码实现 C语言 [496] 下一…

【一篇搞懂】操作系统期末大题:进程同步与互斥 PV操作

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;☀️☀️☀️题型一&#xff1a;利用信号量实现前驱关系题型二&#xff1a;利用信号量实现资源同步与互斥 一、前言&#x1f680;&#x1f680;&#x1f680; 本文简介&#xff1a;这是一篇基于b…

Pycharm常用快捷键整理

1&#xff0c;格式化代码 【ctrlAltL】 写代码的时候会发现有很多黄色的波浪号&#xff0c;这个时候可以点击任意黄色波浪号的代码&#xff0c;然后按下【Ctrl Alt L】进行代码格式化 2&#xff0c;快速往返 ctrll Alt ⬅ &#xff0c;表示查看上一步调用函数位置&#xff0…

Redis 7.x 系列【9】数据类型之自动排重集合(Set)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 前言2. 常用命令2.1 SADD2.2 SCARD2.3 SISMEMBER2.4 SREM2.5 SSCAN2.6 SDIFF2.7 SU…

华为OD机试 - 启动多任务排序 - 拓扑排序(Java 2024 D卷 200分)

华为OD机试 2024D卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;D卷C卷A卷B卷&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;每一题都有详细的答题思路、详细的代码注释、样例测…

如何做好一个企业家IP:塑造独特的个人品牌

在当今数字化时代&#xff0c;个人品牌的力量愈发凸显&#xff0c;对于企业家而言&#xff0c;一个强大的IP&#xff08;Intellectual Property&#xff0c;即知识产权或个人品牌&#xff09;不仅有助于提升个人影响力&#xff0c;还能为企业的发展注入强大动力。那么&#xff…

BGE M3-Embedding 模型介绍

BGE M3-Embedding来自BAAI和中国科学技术大学&#xff0c;是BAAI开源的模型。相关论文在https://arxiv.org/abs/2402.03216&#xff0c;论文提出了一种新的embedding模型&#xff0c;称为M3-Embedding&#xff0c;它在多语言性&#xff08;Multi-Linguality&#xff09;、多功能…

Feign 原理流程图练习-01

目录 作业: 老师给的参考流程图 要求 解答 知识扩展 Feign基础原理 接口定义 代理对象生成 请求调用 请求发送 响应处理 容错与熔断 总结 作业: 老师给的参考流程图 pdf版本 【金山文档 | WPS云文档】 Feign https://kdocs.cn/l/ctbagIyxN348 ​ 要求 结合上面…