基于zuul实现自定义路由源码分析
ZuulFilter定义
通过继承ZuulFilter我们可以定义一个新的过滤器,如下
public class IpAddressFilter extends ZuulFilter {
@Autowired
private IGatewayService iGatewayService;
@Override
public String filterType() {
// pre类型的过滤器
return PRE_TYPE;
}
@Override
public int filterOrder() {
// 排序
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String ip = ctx.getRequest().getRemoteAddr();
Set<String> blackList = ConcurrentCache.getBlackSet();
Set<String> whiteList = ConcurrentCache.getWhiteSet();
blackList.removeAll(whiteList);
// 在黑名单中禁用
if (StringUtils.isNotBlank(ip)&& blackList.contains(ip)) {
ctx.setSendZuulResponse(false);
ctx.setResponseBody("Suspected flooding attack, IP blocked");
ctx.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
ctx.addZuulResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
return null;
}
return null;
}
}
ZuulFilter中实现了compareTo()方法,根据它的值决定同类型的filter的执行顺序。compareTo()方法如下:
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
注册ZuulFilter到spring容器中
ZuulFilter可以通过@Component,也可以通过@Bean实例化来纳入spring的生命周期中。
@Configuration
public class FilterConfig {
@Bean
public IpAddressFilter addIpAddressFilter() {
return new IpAddressFilter();
}
}
ZuulServerAutoConfiguration中自动装配了filter,被spring实例化出来的所有的ZuulFilter都会被自动装配到Map中。
@Configuration
protected static class ZuulFilterConfiguration {
// 根据类型,自动装配ZuulFilter到Map对象中
@Autowired
private Map<String, ZuulFilter> filters;
@Bean
public ZuulFilterInitializer zuulFilterInitializer(
CounterFactory counterFactory, TracerFactory tracerFactory) {
FilterLoader filterLoader = FilterLoader.getInstance();
// 单例模式
FilterRegistry filterRegistry = FilterRegistry.instance();
return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);
}
}
上面的代码会调用ZuulFilterInitializer的构造方法。
ZuulFilterInitializer中的contextInitialized()开启了@PostConstruct注解,在构造方法完成时,容器会调用contextInitialized()方法(注意:ZuulFilterInitializer对象要由spring管理才会调用到@PostConstruct),将所有的filter保存到filterRegistry中,filterRegistry是一个单例对象。
说明:PostConstruct 注释用于在依赖关系注入完成之后需要执行的方法上
contextInitialized()方法如下:
@PostConstruct
public void contextInitialized() {
log.info("Starting filter initializer");
TracerFactory.initialize(tracerFactory);
CounterFactory.initialize(counterFactory);
for (Map.Entry<String, ZuulFilter> entry : this.filters.entrySet()) {
// 保存filter
filterRegistry.put(entry.getKey(), entry.getValue());
}
}
自定义路由转发规则
ZuulProxyAutoConfiguration类中注册了RouteLocator的bean,@Bean会按照类型,自动注入RouteLocator的实现类。
@Bean
public PreDecorationFilter preDecorationFilter(RouteLocator routeLocator,
ProxyRequestHelper proxyRequestHelper) {
return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(),
this.zuulProperties, proxyRequestHelper);
}
RouteLocator实例化
@Configuration
public class AppConfig{
//.....省略....
@Bean(value = "discoveryRouteLocator")
public DiscoveryClientRouteLocator discoveryClientRouteLocator(ServerProperties server, DiscoveryClient discovery, ZuulProperties properties,ServiceInstance localInstance) {
return new CustomRouteLocator(server.getServletPath(), discovery,properties,localInstance);
}
}
CustomRouteLocator实现自定义路由的功能,类如下。
public class CustomRouteLocator extends DiscoveryClientRouteLocator {
// ....省略....
@Override
// 重写
public Route getMatchingRoute(String path) {
// ....省略....
//可以从数据库中读取路由规则,并进行处理
}
// 重写
@Override
protected LinkedHashMap<String, ZuulRoute> locateRoutes() {
// ....省略....
}
}
Servlet初始化
为什么通过访问网关可以自动跳转到zuul中,其实是通过servlet的实现的,该servlet对根路径/进行过滤。下面说明servlet的初始化内容。
ZuulServerAutoConfiguration类中定义了ZuulController
@Bean
public ZuulController zuulController() {
return new ZuulController();
}
ZuulController继承了ServletWrappingController类
public class ZuulController extends ServletWrappingController {
public ZuulController() {
// 设置类为ZuulServlet
setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null);
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
}
ServletWrappingController对ZuulServlet进行实例化
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("'servletClass' is required");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
// 实例化
this.servletInstance = this.servletClass.newInstance();
// 调用servlet的init方法
this.servletInstance.init(new DelegatingServletConfig());
}
当访问一个url的时候,服务请求会跳转到ZuulController中,执行handleRequest()方法。
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
// 调用父类的handleRequestInternal方法
return super.handleRequestInternal(request, response);
}
finally {
RequestContext.getCurrentContext().unset();
}
}
handleRequestInternal()方法如下:
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception {
this.servletInstance.service(request, response);
return null;
}
servletInstance即ZuulServlet的实例,上面的方法最终调用ZuulServlet中的service()方法。
service()方法如下:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
// pre过滤器
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// route过滤器
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
// post过滤器
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
根据上面的源码我们知道,一个请求到来的时候,就要经历preRoute、route、postRoute几个阶段,用官方的图来说明

网关请求执行的过程
根据第上面的内容,我们知道,当通过网关对服务进行请求的时候,要经历preRoute,route、postRoute阶段,这里以以preRoute()方法为例,对路由的处理过程进行说明。
preRoute()方法如下:
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
ZuulRunner中的preRoute()方法如下:
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
FilterProcessor是一个单例模式,FilterProcessor中的preRoute()方法如下:
public void preRoute() throws ZuulException {
try {
// 运行pre过滤器
runFilters("pre");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());
}
}
执行过滤器,runFilters()方法如下:
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
// 根据过滤器类型,获取过滤器列表。
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
// 依次调用过滤器
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
// 过滤器处理过程
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
根据类型获取过滤器列表,getFiltersByType()方法如下:
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
// 获取所有的过滤器
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
// 取得filterType的类型列表
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
// 对filter进行排序
Collections.sort(list); // sort by priority
// 保存列表
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
FilterRegistry类是一个单例模式,getAllFilters()方法如下
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
// ....省略....
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}
过滤器的处理方法processZuulFilter()如下:
public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
boolean bDebug = ctx.debugRouting();
final String metricPrefix = "zuul.filter-";
long execTime = 0;
String filterName = "";
try {
long ltime = System.currentTimeMillis();
filterName = filter.getClass().getSimpleName();
// ....省略....
// 运行filter
ZuulFilterResult result = filter.runFilter();
ExecutionStatus s = result.getStatus();
execTime = System.currentTimeMillis() - ltime;
// .....省略....
usageNotifier.notify(filter, s);
return o;
} catch (Throwable e) {
// .....省略.....
}
}
runFilter()方法如下,:
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
// 判断过滤器是否需要执行
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
// 调用filter的run方法。
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
// ....省略....
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
最终调用到各ZuulFilter中的run()方法。
路由查找
pre类型的PreDecorationFilter过滤器,用来进行路由规则的匹配
如下:

执行后,上下文内容中的内容如下,加入了requestURI

访问服务
根据下图可以知道,真正访问服务的是route阶段。如下:

对于正常的服务,比如:/xxx/service_name是通过RibbonRoutingFilter实现对服务的负载均衡访问,它的run()方法如下:
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
如果是固定的url链接,如:http://www.abc.com/xxx/service_name这种,则是通过SendForwardFilter过滤器实现转发。它的run()方法如下:
public Object run() {
try {
RequestContext ctx = RequestContext.getCurrentContext();
String path = (String) ctx.get(FORWARD_TO_KEY);
RequestDispatcher dispatcher = ctx.getRequest().getRequestDispatcher(path);
if (dispatcher != null) {
ctx.set(SEND_FORWARD_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
// url转发
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
}
}
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
基于zuul实现自定义路由源码分析的更多相关文章
- 基于Linux平台的libpcap源码分析和优化
目录 1..... libpcap简介... 1 2..... libpcap捕包过程... 2 2.1 数据包基本捕包流程... 2 2.2 libpcap捕包过程... ...
- Java -- 基于JDK1.8的LinkedList源码分析
1,上周末我们一起分析了ArrayList的源码并进行了一些总结,因为最近在看Collection这一块的东西,下面的图也是大致的总结了Collection里面重要的接口和类,如果没有意外的话后面基本 ...
- Java集合基于JDK1.8的ArrayList源码分析
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
- Java集合基于JDK1.8的LinkedList源码分析
上篇我们分析了ArrayList的底层实现,知道了ArrayList底层是基于数组实现的,因此具有查找修改快而插入删除慢的特点.本篇介绍的LinkedList是List接口的另一种实现,它的底层是基于 ...
- 基于ReentrantLock的AQS的源码分析(独占、非中断、不超时部分)
刚刚看完了并发实践这本书,算是理论具备了,看到了AQS的介绍,再看看源码,发现要想把并发理解透还是很难得,花了几个小时细分析了一下把可能出现的场景尽可能的往代码中去套,还是有些收获,但是真的很费脑,还 ...
- 基于XMPP协议的aSmack源码分析
在研究如何实现Pushing功能期间,收集了很多关于Pushing的资料,其中有一个androidnp开源项目用的人比较多,但是由于长时间没有什么人去维护,听说bug的几率挺多的,为了以后自己的产品稳 ...
- Java -- 基于JDK1.8的ThreadLocal源码分析
1,最近在做一个需求的时候需要对外部暴露一个值得应用 ,一般来说直接写个单例,将这个成员变量的值暴露出去就ok了,但是当时突然灵机一动(现在回想是个多余的想法),想到handle源码里面有使用过Th ...
- 基于jdk1.8的ArrayList源码分析
前言ArrayList作为一个常用的集合类,这次我们简单的根据源码来看看AarryList是如何使用的. ArrayList拥有的成员变量 public class ArrayList<E> ...
- Java -- 基于JDK1.8的ArrayList源码分析
1,前言 很久没有写博客了,很想念大家,18年都快过完了,才开始写第一篇,争取后面每周写点,权当是记录,因为最近在看JDK的Collection,而且ArrayList源码这一块也经常被面试官问道,所 ...
随机推荐
- P3294 [SCOI2016]背单词
P3294 [SCOI2016]背单词 Trie+贪心 倒插进树+取出重建+子树处理+贪心遍历 倒插进树:把后缀转化为前缀,所以把字符串倒着插进Trie中 取出重建:重新建立一棵以单词为节点的树,如果 ...
- P3501 [POI2010]ANT-Antisymmetry
P3501 [POI2010]ANT-Antisymmetry 二分+hash 注意:答案超出int范围 ------------ 先拿一个反对称串来做栗子:010101 我们可以发现 0101(左边 ...
- 一个远程启动windows c++程序引发的技术决策现象
还是因为那个8点半前要启动近百套报盘程序的问题,差不多两周前表示自己会抽空给解决掉,一次性启动,直到昨天才差不多能够抽点时间出来开始想怎么解决的问题. 这个问题的复杂点在于除了启动exe外,还需要鼠标 ...
- 20145101《Java程序设计》第5周学习总结
20145101<Java程序设计>第5周学习总结 教材学习内容总结 第八章 异常处理 Java是通过try,catch,throw,throws,finally这5个关键字来实现异常处理 ...
- Django框架(二) MTV模型简介
MTV模型 Django的MTV分别代表 Model(模型):和数据库相关的,负责业务对象与数据库的对象(ORM) Template(模板):放所有的html文件 模板语法:目的是将白变量(数据库的内 ...
- spring与spring-data-redis整合redis
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.sp ...
- 论文笔记——ThiNet: A Filter Level Pruning Method for Deep Neural Network Compreesion
论文地址:https://arxiv.org/abs/1707.06342 主要思想 选择一个channel的子集,然后让通过样本以后得到的误差最小(最小二乘),将裁剪问题转换成了优化问题. 这篇论文 ...
- Unity3D学习笔记(四):物理系统碰撞和预制体
Rigidbody(刚体组件):加了此组件游戏物体就变成刚体了 ----Mass(质量,单位kg):重力G = 质量m * 重力加速度g(g=9.81 m/s^2) --------冲量守恒定理 动量 ...
- confluence导出pdf 文字显示不全
当使用confluence编辑页面时,当一行的文字过多,且中间没什么逗号分隔时,有时会出现导出的pdf文件中,这一行显示的文字不全的情况. 如: 很明显费用的费字没有显示完全,且后面还有其他的字. 可 ...
- [转]python新手必碰到的问题---encode与decode,中文乱码--转载
edu.codepub.com/2009/1029/17037.php 这个问题在python3.0里已经解决了. 这有篇很好的文章,可以明白这个问题: 为什么会报错“UnicodeEncodeErr ...