Zuul目的
请求路由,它作为一个网关,接收到一个请求,然后将这个请求转发给其他的服务
Zuul核心
Zuul核心的代码都在一坨过滤器中
pre过滤器-3:ServletDetectionFilter-2:Servlet30WrapperFilter-1:FromBodyWrapperFilter1:DebugFilter5:PreDecorationFilterrouting过滤器10:RibbonRoutingFilter100:SimpleHostRoutingFilter500:SendForwardFilterpost过滤器900:LocationRewriteFilter1000:SendResponseFiltererror过滤器0:SendErrorFilter
Zuul源码阅读开始
1. 一切的开始从注解 @EnableZuulProxy 开始
@EnableZuulProxy 主要干2件事:
- 启用一个zuul server,这个东西可以接收所有的http请求,都会被他给拦截,所以这块我们可以想象一下,这里的zuul一定是搞了一个类似与servlet、filter、spring boot拦截器的东西
- 就是给那个zuul server(拦截器,servlet,filter)加入一些内置的filter,过滤器,就是我们之前看到的各种过滤器
注解@EnableZuulProxy 源码:
@EnableCircuitBreaker
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public @interface EnableZuulProxy {
}
//这个ZuulProxyMarkerConfiguration是用来触发ZuulProxyAutoConfiguration的执行的
@Configuration
public class ZuulProxyMarkerConfiguration {
@Bean
public Marker zuulProxyMarkerBean() {
return new Marker();
}
class Marker {
}
}
分析源码 ZuulProxyMarkerConfiguration 的注解可知,Zuul框架加载主要分析源码 ZuulProxyMarkerConfiguration 类
2.ZuulProxyMarkerConfiguration 源码分析之加载组件
@Configuration
@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//在ZuulProxyMarkerConfiguration中的zuulProxyMarkerBean()方法创建了
@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
//这里之后补充其他组件
- 必须有
ZuulProxyMarkerConfiguration里面的一个Marker作为spring容器中的bean,然后才能触发ZuulProxyAutoConfiguruation的执行 这个类里面大概来说,就是初始化了一系列的zuul的过滤器,以及其他组件
3.Zuul框架Request请求拦截之
ZuulServlet通过Debug可知,请求过来直接进入
ZuulServlet中的,你会发现原来zuul的核心请求是由ZuulServlet来作为一个入口来处理的 ```java //com.netflix.zuul.http.ZuulServlet#service()方法,zuul处理request的拦截 @Override public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException { try {//1.进入init方法去初始化RequestContext实例,具体方法如下面图1 init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse); RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); //2.这里是ZuulServlet的重点,这块就是Zuul的核心,各个拦截器过滤,下面有分析 //这块主要是查看3-1.Zuul try { preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; } try { route(); } catch (ZuulException e) { error(e); postRoute(); return; } try { postRoute(); } catch (ZuulException e) { error(e); return; }} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));} finally {
RequestContext.getCurrentContext().unset();} }
图1:<br /><br />图2<br />
1. `init()` 方法是内主要是构建了RequestContext对象,并且对resquet、response进行封装,其中response是由HttpServletRequestWrapper进行封装
1. `RequestContext ctx = RequestContext.getCurrentContext()` 进入后发现,其构建由ThreadLocal来实现,本次请求就是一个线程在处理,对这个线程就可以使用ThreadLocal放入各种数据副本
<a name="3YhRQ"></a>
### 3-1. ZuulServlet的拦截器
这里主要是过滤器的流程图,主要是根据下面代码执行如图:
```java
//截取自com.netflix.zuul.http.ZuulServlet#service()方法内代码
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} finally {
//清理RequestContext
RequestContext.getCurrentContext().unset();
}
由此翻译出相关的流程图如下:
通过上面的流程图可以知道:
- request在经过pre过滤器、route过滤器、post过滤器之后,才到达指定的controller接口
- 如果上述过滤器中出错,会有对应的流程处理流程(上图分析几种流程处理)
3-2.Pre拦截器加载流程
同上图中,本小节主要分析以下过程处理:
执行代码如下: ```java //1.截取自com.netflix.zuul.http.ZuulServlet#service()方法内代码 try { //当中下图preRoute()为 preRoute(); } catch (ZuulException e) { error(e); postRoute(); return; }
//2.截取自com.netflix.zuul.http.ZuulServlet#preRoute()方法 void preRoute() throws ZuulException { //这里ZuulRunner的preRoute()方法 zuulRunner.preRoute(); }
//3.ZuulRunner#preRoute()实际上通过单例模式获取一系列pre过滤器,为什么是一系列,看下面preRoute()源码 public void preRoute() throws ZuulException { FilterProcessor.getInstance().preRoute(); }
//4.FilterProcessor#preRoute()方法去加载pre拦截器 public void preRoute() throws ZuulException { try { //入口是下面方法,这里通过‘pre’关键字加载pre一些列的过滤器 runFilters(“pre”); } catch (ZuulException e) { throw e; } catch (Throwable e) { throw new ZuulException(e, 500, “UNCAUGHTEXCEPTION_IN_PRE_FILTER“ + e.getClass().getName()); } }
//5.这里是默认加载sType字符串对应拦截器,sType分别加载先后为”pre”、”route”、”post”、”error”(正常情况下) public Object runFilters(String sType) throws Throwable { if (RequestContext.getCurrentContext().debugRouting()) { Debug.addRoutingDebug(“Invoking {“ + sType + “} type filters”); } boolean bResult = false;
//这里获取一些列的pre拦截器,这里就是我上面说 runFilters("pre")会加载一些列拦截器
//这里下面debug可知道,这里会怎么加载的
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for (int i = 0; i < list.size(); i++) {
ZuulFilter zuulFilter = list.get(i);
//这里是本方法重点:执行每个拦截器的run方法,具体如下面源码方法知
Object result = processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= ((Boolean) result);
}
}
}
return bResult;
}
//6.执行相关runFilter()的实现方法 public Object processZuulFilter(ZuulFilter filter) throws ZuulException { //代码省略… //这里执行每个实现ZuulFilter接口的runFilter()实现方法 ZuulFilterResult result = filter.runFilter(); ExecutionStatus s = result.getStatus(); //代码省略… }
- 这里相关debug可知5当中获取到的对应的5个pre拦截器:

<a name="Cffte"></a>
### 3-3. 多个pre拦截器 `run()` 方法处理业务
通过上面了解到加载了5个pre拦截器,下面,具体分析主要处理的业务:
<a name="2pD9I"></a>
#### a.`ServletDetectionFilter` 分析

> ServletDetectionFilter#run方法主要是在RequestContext对应的 `IS_DISPATCHER_SERVLET_REQUEST_KEY` 设置为true
> RequestContext:isDispatcherServletRequest = true
<a name="K0Kqr"></a>
#### b.`Servlet30WrapperFilter`分析

> 当中 `RequestUtils.isDispatcherServletRequest()` 其实是在上面 `ServletDetectionFilter#run()`方法下设置的true,
> 其目的是对request封装为 `Servlet30RequestWrapper` , 目的是提供其 `getRequest()` 方法
<a name="6BB26"></a>
#### c. `FormBodyWrapperFilter` 分析
这里执行 `shouldFilter() ` 方法,主要是判断是否是post方法,不过在该request的header必须是APPLICATION_FORM_URLENCODED这种media type才会执行,或者是MULTIPART_FORM_DATA这种media type才会执行<br />
<a name="IeHkJ"></a>
#### d. `DebugFilter` 分析
这里也是执行`shouldFilter() ` 方法<br />
> 你必须在http请求中加入一个参数,debug=true,然后才会执行DebugFilter,这个DebugFilter其实就是打开几个debug的标识,然后在后面的运行中会打印一些debug的日志
> 这里_ROUTING_DEBUG_.get() 为false
<a name="YLQT0"></a>
#### e. `PreDecorationFilter` 分析
这里主要分析 `run()` 方法执行:
```java
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
//下面2行代码非常核心
final String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
Route route = this.routeLocator.getMatchingRoute(requestURI);
//代码省略.....
}
Debug中可知道:
route的数据如上图,他其实就是根据我们的请求url地址,去匹配我们的application.yml中的路由规则的配置,然后拿到了请求url对应的路由规则 Route{id=’ServiceB’, fullPath=’/demo/ServiceB/user/1’, path=’/ServiceB/user/1’, location=’ServiceB’, prefix=’/demo’, retryable=false, sensitiveHeaders=[], customSensitiveHeaders=false, prefixStripped=true} urlPathHelper是springWeb项目的一个工具类,获取到URI。 根据获取到的requestURI去所有的路由规则中去匹配。
zuul最核心的逻辑,在这个PreDecoration中就完成了,当然了,其实解析请求uri,以及完成请求uri和application.yml中的路由规则的匹配的事儿,是zuul最最核心的事儿,但是这个里面的源码是比较琐碎没任何技术含量的源码
Route route = this.routeLocator.getMatchingRoute(requestURI);
这行代码,其实是根据解析出来的请求URI,去匹配application.yml文件中我们配置的路由规则,将路由规则封装成一个Route
进入 getMatchingRoute(requestURI) 方法中,如图:
通过path: /demo/ServiceB/user/1 和 已在 ZuulProxyAutoConfiguration 中加载好的 RouteLocator 去匹配执行下列方法流程:
知道执行getSimpleMatchingRoute 方法中的 getRoutesMap() 方法和 getZuulRoute(adjustedPath) 方法
getRoutesMap():获取到所有路由器的地址
getZuulRoute(adjustedPath):根据已知的adjustedPath获取对应的路由器地址。 这里获取到是: ZuulRoute{id=’ServiceB’, path=’/demo/**’, serviceId=’ServiceB’, url=’null’, stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }
通过 getRoute(..) 方法封装一个Route对象生成返回
protected Route getRoute(ZuulRoute route, String path) {
//代码省略.....
String targetPath = path;
String prefix = this.properties.getPrefix();
if(prefix.endsWith("/")) {
prefix = prefix.substring(0, prefix.length() - 1);
}
if (path.startsWith(prefix + "/") && this.properties.isStripPrefix()) {
targetPath = path.substring(prefix.length());
}
//这里就是根据把path由/demo/ServiceB/user/1 --> /ServiceB/user/1 这样
if (route.isStripPrefix()) {
int index = route.getPath().indexOf("*") - 1;
if (index > 0) {
String routePrefix = route.getPath().substring(0, index);
targetPath = targetPath.replaceFirst(routePrefix, "");
prefix = prefix + routePrefix;
}
}
Boolean retryable = this.properties.getRetryable();
if (route.getRetryable() != null) {
retryable = route.getRetryable();
}
//返回一个初始化的Route对象
return new Route(route.getId(), targetPath, route.getLocation(), prefix,
retryable,
route.isCustomSensitiveHeaders() ? route.getSensitiveHeaders() : null,
route.isStripPrefix());
}
生成一个route对象属性如下: Route{id=’ServiceB’, fullPath=’/demo/ServiceB/user/1’, path=’/ServiceB/user/1’, location=’ServiceB’, prefix=’/demo’, retryable=false, sensitiveHeaders=[], customSensitiveHeaders=false, prefixStripped=true}
然后把匹配到的URI放入到RequestContext中:
3-4. Route拦截器加载及业务流程
同上图中,本小节主要分析以下过程处理:
执行代码如下:
//1.截取自com.netflix.zuul.http.ZuulServlet#service()方法内代码
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
//2.截取自com.netflix.zuul.http.ZuulServlet#route()方法
void route() throws ZuulException {
zuulRunner.route();
}
//3.ZuulRunner#route()实际上通过单例模式获取一系列route过滤器,为什么是一系列,看下面route()源码
public void route() throws ZuulException {
FilterProcessor.getInstance().route();
}
//4.FilterProcessor#route()方法去加载route拦截器
public void route() throws ZuulException {
try {
//入口是下面方法,这里通过‘route’关键字加载route一些列的过滤器
runFilters("route");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_ROUTE_FILTER_" + e.getClass().getName());
}
}
执行到route过滤有三个ZuulFilter
routing过滤器
10:RibbonRoutingFilter
100:SimpleHostRoutingFilter
500:SendForwardFilter
上面三个过滤器是根据application.yml选择性执行的!只会执行其中一个,一般常用RibbonRoutingFilter
a. RibbonRoutingFilter分析

上图中,通过forward()方法就获取到了response,说明这个方法是极为重要的!
在forward方法中,先创建了一个RibbonCommand,然后执行了它的execute()方法得到了response。我们来看看创建RibbonCommand的方法和execute的方法吧:
创建RibbonCommand:
根据一系列参数创建了一个HttpClientRibbonCommand的类,这个类最终还是继承自HystrixCommand!
在AbstractRibbonCommand类中有一个run()方法,重写自HystrixCommand,此方法会调用一个executeWithLoadBalancer方法来实现最终的调用。
在executeWithLoadBalancer方法中,先创建了一个LoadBalancerCommand,在调用submit方法使用ILoadBalancer选取一个server。



上图中,其实就已经进入到了ribbon的源码中了。
获取到合适的server之后,就使用http组件发送了http请求。
最后,把返回来的response结果放入到了RequestContext中去。
b.SimpleHostRoutingFilter分析
修改配置:
发送请求:
在这个过滤器里面,说白了就是获取到匹配规则,把url截取替换。用http组件进行转发了!
c.SendForwardFilter
修改配置:
直接使用RequestDispatcher进行了请求转发。
3-5.Post过滤器中的逻辑
同上图中,本小节主要分析以下过程处理:
执行代码如下:
//1.截取自com.netflix.zuul.http.ZuulServlet#service()方法内代码
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
//2.截取自com.netflix.zuul.http.ZuulServlet#postRoute()方法
void postRoute() throws ZuulException {
zuulRunner.postRoute();
}
//3.ZuulRunner#postRoute()实际上通过单例模式获取一系列post过滤器,为什么是一系列,看下面postRoute()源码
public void postRoute() throws ZuulException {
FilterProcessor.getInstance().postRoute();
}
//4.FilterProcessor#preRoute()方法去加载post拦截器
public void postRoute() throws ZuulException {
try {
runFilters("post");
} catch (ZuulException e) {
throw e;
} catch (Throwable e) {
throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_POST_FILTER_" + e.getClass().getName());
}
}
post过滤器
post过滤器
900:LocationRewriteFilter
1000:SendResponseFilter
当中 LocationRewriteFilter 过滤器其实并没有实质处理业务,更多是 SendResponseFilter 处理
a. SendResponseFilter 分析


说白了就是从inputStream输入流中读取json串,然后写到outStream输出流中,写给客户端,如图:
3-6.error 拦截器
这个过滤器只要一个filter:SendErrorFilter
a. SendErrorFilter 分析

在这个filter中进行了异常转发,我们可以自己写一个controller,专门来处理zuul的所有异常报错,继承另外一个controller的基类就可以了,这样的话就可以实现统一的异常处理。
getZuulRoute(adjustedPath):根据已知的adjustedPath获取对应的路由器地址。
这里获取到是:
ZuulRoute{id=’ServiceB’, path=’/demo/**’, serviceId=’ServiceB’, url=’null’, stripPrefix=true, retryable=null, sensitiveHeaders=[], customSensitiveHeaders=false, }