基于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源码这一块也经常被面试官问道,所 ...
随机推荐
- 记一次ping: unknown host错误
虚拟机上一台主机,之前一直在用,可以通过xshell连接,但是忽然发现ping百度失败了! [root@mgt02 ~]# ping www.baidu.com ping: unknown host ...
- Python Web学习笔记之并发编程IO模型
了解新知识之前需要知道的一些知识 同步(synchronous):一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行 #所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调 ...
- QTQuick控件基础(2)
import QtQuick 2.2import QtQuick.Controls 1.2import QtQuick.Window 2.1ApplicationWindow { visible ...
- HDU 2204 Eddy's爱好(容斥原理dfs写法)题解
题意:定义如果一个数能表示为M^k,那么这个数是好数,问你1~n有几个好数. 思路:如果k是合数,显然会有重复,比如a^(b*c) == (a^b)^c,那么我们打个素数表,指数只枚举素数,2^60 ...
- 论文笔记——NEURAL ARCHITECTURE SEARCH WITH REINFORCEMENT LEARNING
论文地址:https://arxiv.org/abs/1611.01578 1. 论文思想 强化学习,用一个RNN学一个网络参数的序列,然后将其转换成网络,然后训练,得到一个反馈,这个反馈作用于RNN ...
- NOI 4978 宠物小精灵之收服(二维背包)
http://noi.openjudge.cn/ch0206/4978/ 描述 宠物小精灵是一部讲述小智和他的搭档皮卡丘一起冒险的故事. 一天,小智和皮卡丘来到了小精灵狩猎场,里面有很多珍贵的野生宠物 ...
- UVa 12545 比特变换器
https://vjudge.net/problem/UVA-12545 题意:输入两个等长的串S和T,其中S包含字符0,1,?,但T只包含0和1. 用尽量少的步数把S变成T.每步有3种操作: ①把S ...
- .net 与 java 开发微服务对比
java+spring boot+maven对比.net 优势: 1. spring 自身带的ioc 比.net 更简单易用. 2. spring actuator的健康检测等运行时状态查看功能很赞. ...
- c++ 数组元素拷贝到容器(copy)
#include <iostream> // cout #include <algorithm> // copy #include <vector> // vect ...
- string_01
内存释放和越界 越界:(1).复制越界,(2).取值越界 构造函数 (1).默认 空(无参) (2).拷贝(const string &) (3).带参数: const char * // Z ...