Zuul目的

请求路由,它作为一个网关,接收到一个请求,然后将这个请求转发给其他的服务

Zuul核心

Zuul核心的代码都在一坨过滤器中

  1. pre过滤器
  2. -3ServletDetectionFilter
  3. -2Servlet30WrapperFilter
  4. -1FromBodyWrapperFilter
  5. 1DebugFilter
  6. 5PreDecorationFilter
  7. routing过滤器
  8. 10RibbonRoutingFilter
  9. 100SimpleHostRoutingFilter
  10. 500SendForwardFilter
  11. post过滤器
  12. 900LocationRewriteFilter
  13. 1000SendResponseFilter
  14. error过滤器
  15. 0SendErrorFilter

Zuul源码阅读开始

1. 一切的开始从注解 @EnableZuulProxy 开始

@EnableZuulProxy 主要干2件事:

  1. 启用一个zuul server,这个东西可以接收所有的http请求,都会被他给拦截,所以这块我们可以想象一下,这里的zuul一定是搞了一个类似与servlet、filter、spring boot拦截器的东西
  2. 就是给那个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 />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615859413817-94897f12-6fd3-4c60-bdac-ca3f42b83676.png#align=left&display=inline&height=916&margin=%5Bobject%20Object%5D&name=image.png&originHeight=916&originWidth=1585&size=162674&status=done&style=none&width=1585)<br />图2<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615859979652-76318b30-c56b-40a2-96cc-07121cb26dea.png#align=left&display=inline&height=473&margin=%5Bobject%20Object%5D&name=image.png&originHeight=473&originWidth=774&size=38342&status=done&style=none&width=774)

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();
 }

由此翻译出相关的流程图如下:
image.png
通过上面的流程图可以知道:

  1. request在经过pre过滤器、route过滤器、post过滤器之后,才到达指定的controller接口
  2. 如果上述过滤器中出错,会有对应的流程处理流程(上图分析几种流程处理)

    3-2.Pre拦截器加载流程

    同上图中,本小节主要分析以下过程处理:
    image.png
    执行代码如下: ```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拦截器:

![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615901878288-1540a16a-0fed-4865-9fd2-fafa0be4e69f.png#align=left&display=inline&height=662&margin=%5Bobject%20Object%5D&name=image.png&originHeight=662&originWidth=1027&size=88445&status=done&style=none&width=1027)
<a name="Cffte"></a>
### 3-3. 多个pre拦截器 `run()` 方法处理业务
通过上面了解到加载了5个pre拦截器,下面,具体分析主要处理的业务:
<a name="2pD9I"></a>
#### a.`ServletDetectionFilter` 分析
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615902387428-7201caba-9995-4c5b-bdb3-984e479059b6.png#align=left&display=inline&height=289&margin=%5Bobject%20Object%5D&name=image.png&originHeight=289&originWidth=766&size=24270&status=done&style=none&width=766)
> ServletDetectionFilter#run方法主要是在RequestContext对应的 `IS_DISPATCHER_SERVLET_REQUEST_KEY` 设置为true
> RequestContext:isDispatcherServletRequest = true

<a name="K0Kqr"></a>
#### b.`Servlet30WrapperFilter`分析
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615904582488-e5fff024-e368-4862-80f9-f3debac8d46c.png#align=left&display=inline&height=320&margin=%5Bobject%20Object%5D&name=image.png&originHeight=320&originWidth=1162&size=38935&status=done&style=none&width=1162)
> 当中 `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 />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615905551908-4654ea37-885e-41f0-9560-b57849a35ce0.png#align=left&display=inline&height=473&margin=%5Bobject%20Object%5D&name=image.png&originHeight=473&originWidth=693&size=36353&status=done&style=none&width=693)
<a name="IeHkJ"></a>
#### d. `DebugFilter` 分析
这里也是执行`shouldFilter() ` 方法<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1642324/1615905930390-6ed90cb6-a128-453b-b7d7-9fdda32266fb.png#align=left&display=inline&height=184&margin=%5Bobject%20Object%5D&name=image.png&originHeight=184&originWidth=818&size=14592&status=done&style=none&width=818)
> 你必须在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中可知道:
image.png

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 去匹配执行下列方法流程:
image.png
知道执行getSimpleMatchingRoute 方法中的 getRoutesMap() 方法和 getZuulRoute(adjustedPath) 方法
image.png

getRoutesMap():获取到所有路由器的地址 image.png 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中:6. Zuul源码分析 - 图7

3-4. Route拦截器加载及业务流程

同上图中,本小节主要分析以下过程处理:
image.png
执行代码如下:

//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分析

image-20210309221714011
上图中,通过forward()方法就获取到了response,说明这个方法是极为重要的!
在forward方法中,先创建了一个RibbonCommand,然后执行了它的execute()方法得到了response。我们来看看创建RibbonCommand的方法和execute的方法吧:
6. Zuul源码分析 - 图10
创建RibbonCommand:
6. Zuul源码分析 - 图11
根据一系列参数创建了一个HttpClientRibbonCommand的类,这个类最终还是继承自HystrixCommand!
6. Zuul源码分析 - 图12
在AbstractRibbonCommand类中有一个run()方法,重写自HystrixCommand,此方法会调用一个executeWithLoadBalancer方法来实现最终的调用。
6. Zuul源码分析 - 图13
在executeWithLoadBalancer方法中,先创建了一个LoadBalancerCommand,在调用submit方法使用ILoadBalancer选取一个server。
6. Zuul源码分析 - 图14
6. Zuul源码分析 - 图15
6. Zuul源码分析 - 图16
6. Zuul源码分析 - 图17
上图中,其实就已经进入到了ribbon的源码中了。
获取到合适的server之后,就使用http组件发送了http请求。
6. Zuul源码分析 - 图18
最后,把返回来的response结果放入到了RequestContext中去。
6. Zuul源码分析 - 图19

b.SimpleHostRoutingFilter分析

修改配置:
image.png
发送请求:
6. Zuul源码分析 - 图21
在这个过滤器里面,说白了就是获取到匹配规则,把url截取替换。用http组件进行转发了!

c.SendForwardFilter

修改配置:
6. Zuul源码分析 - 图22

直接使用RequestDispatcher进行了请求转发。
6. Zuul源码分析 - 图23

3-5.Post过滤器中的逻辑

同上图中,本小节主要分析以下过程处理:
image.png
执行代码如下:

//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 分析

6. Zuul源码分析 - 图25

6. Zuul源码分析 - 图26

说白了就是从inputStream输入流中读取json串,然后写到outStream输出流中,写给客户端,如图:6. Zuul源码分析 - 图27

3-6.error 拦截器

这个过滤器只要一个filter:SendErrorFilter

a. SendErrorFilter 分析

6. Zuul源码分析 - 图28

在这个filter中进行了异常转发,我们可以自己写一个controller,专门来处理zuul的所有异常报错,继承另外一个controller的基类就可以了,这样的话就可以实现统一的异常处理。