基于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源码这一块也经常被面试官问道,所 ...
随机推荐
- Shell学习笔记之shell脚本和python脚本实现批量ping IP测试
0x00 将IP列表放到txt文件内 先建一个存放ip列表的txt文件: [root@yysslopenvpn01 ~]# cat hostip.txt 192.168.130.1 192.168.1 ...
- mysql 批处理文件出错后继续执行
在升级批处理sql脚本的时候,由于各种编写的不规范.不可重复执行,我们通常希望在sql脚本出错后不中止,而是执行完成.虽然这些问题可通过编写可重复执行的mysql存储过程比如add_column/dr ...
- 求LCA练习+部分算法复习 2017.1.22
第一题就LCA即可.不过推荐用Tarjan(最快,常数很小).然后Tarjan的时候顺便就出一个dist[i],表示i节点到根节点的距离.求出了LCA,那么两点间的距离就为dist[u] + dist ...
- [BZOJ2208][Jsoi2010]连通数 暴力枚举
Description Input 输入数据第一行是图顶点的数量,一个正整数N. 接下来N行,每行N个字符.第i行第j列的1表示顶点i到j有边,0则表示无边. Output 输出一行一个整数,表示该图 ...
- Unity3D学习笔记(十五):寻路系统
动画生硬切换:animation.play();//极少使用,常用融合方法 动画融合淡入:animation.CrossFade(“Idle”, 0.2f);//0.2f为与前一动画的融合百分比为20 ...
- HDU 3065 病毒侵袭持续中(AC自动机(每个模式串出现次数))
http://acm.hdu.edu.cn/showproblem.php?pid=3065 题意:求每个模式串出现的次数. 思路: 不难,把模板修改一下即可. #include<iostrea ...
- jquery 封装插件
如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jquery 及ui 内置web项目里了.至于使用jquery好处这里就不再赘述了,用过的都知道.今天我们来讨论下jq ...
- [ios]ios tts的使用
参考:http://www.tekuba.net/program/327/ http://blog.sina.com.cn/s/blog_923fdd9b0101flx3.html iOS平台由于本身 ...
- 更新自带pip
想安装docker-compose发现居然找不到pip curl https://bootstrap.pypa.io/get-pip.py | python 直接sudo不行.还是提示权限不够. su ...
- ninja install error
ninja install ...... CMake Error at cmake_install.cmake:36 (FILE): file INSTALL cannot find " ...