一、概述

zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。

Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。

所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。

二、原理

zuul的核心逻辑都是由一系列filter过滤器链实现的,但是filter的类型不同,执行的时机也不同,效果自然也不一样,主要特点如下:

  1. filter的类型:filter的类型,决定了它在整个filter链中的执行顺序,可能在端点路由前执行,也可能在端点路由时执行,还有可能在端点路由后执行,甚至是端点路由发生异常时执行。
  2. filter的执行顺序:同一种类型的filter,可以通过filterOrder()方法设置执行顺序,一般都是根据业务场景自定义filter执行顺序。
  3. filter执行条件:filter运行所需的标准,或条件。
  4. filter执行效果:符合某个filter执行条件,产生执行效果。

zuul内部有一套完整的机制,可以动态读取编译运行filter机制,filter与filter之间不直接通信,在请求线程中会通过RequestContext来共享状态,它内部是用ThreadLocal实现的,例如HttpServletRequest、HttpServletResponse、异常信息等。

zuul一共有4种不同的生命周期:

  1. pre:在zuul网关按照规则路由到下级服务之前执行,如果需要对请求进行预处理,可以使用这种类型的过滤器。如:认证鉴权,限流等。
  2. route:这种过滤器是zuul路由动作的执行者,是Apache HttpClient或Ribbon构建和发送原始HTTP请求的地方,现在也支持OKHTTP。
  3. post:这种过滤器是在端点请求完毕,返回结果或者发生异常后执行的filter。如果需要对返回的结果进行再次处理,可以在这种过滤中处理逻辑。
  4. error: 这种过滤器是在整个生命周期内,如果发生异常,就执行该filter,可以做全局异常处理。

三、实践

3.1路由演示

这个实践代码基于:SpringCloud教程四。

创建新的module,命名service-zuul,其pom.xml如下:

1 <?xml version="1.0" encoding="UTF-8"?>
 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 3          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 4     <modelVersion>4.0.0</modelVersion>
 5 
 6     <groupId>com.wyma</groupId>
 7     <artifactId>service-zuul</artifactId>
 8     <version>0.0.1-SNAPSHOT</version>
 9     <packaging>jar</packaging>
10 
11     <name>service-zuul</name>
12     <description>Demo project for Spring Boot</description>
13 
14     <parent>
15         <groupId>org.springframework.boot</groupId>
16         <artifactId>spring-boot-starter-parent</artifactId>
17         <version>1.5.2.RELEASE</version>
18         <relativePath/> <!-- lookup parent from repository -->
19     </parent>
20 
21     <properties>
22         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
23         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
24         <java.version>1.8</java.version>
25     </properties>
26 
27     <dependencies>
28         <dependency>
29             <groupId>org.springframework.cloud</groupId>
30             <artifactId>spring-cloud-starter-eureka</artifactId>
31         </dependency>
32         <dependency>
33             <groupId>org.springframework.cloud</groupId>
34             <artifactId>spring-cloud-starter-zuul</artifactId>
35         </dependency>
36         <dependency>
37             <groupId>org.springframework.boot</groupId>
38             <artifactId>spring-boot-starter-web</artifactId>
39         </dependency>
40 
41         <dependency>
42             <groupId>org.springframework.boot</groupId>
43             <artifactId>spring-boot-starter-test</artifactId>
44             <scope>test</scope>
45         </dependency>
46     </dependencies>
47 
48     <dependencyManagement>
49         <dependencies>
50             <dependency>
51                 <groupId>org.springframework.cloud</groupId>
52                 <artifactId>spring-cloud-dependencies</artifactId>
53                 <version>Dalston.RC1</version>
54                 <type>pom</type>
55                 <scope>import</scope>
56             </dependency>
57         </dependencies>
58     </dependencyManagement>
59 
60     <build>
61         <plugins>
62             <plugin>
63                 <groupId>org.springframework.boot</groupId>
64                 <artifactId>spring-boot-maven-plugin</artifactId>
65             </plugin>
66         </plugins>
67     </build>
68 
69     <repositories>
70         <repository>
71             <id>spring-milestones</id>
72             <name>Spring Milestones</name>
73             <url>https://repo.spring.io/milestone</url>
74             <snapshots>
75                 <enabled>false</enabled>
76             </snapshots>
77         </repository>
78     </repositories>
79 
80 
81 </project>

在入口applicaton类加上注解@EnableZuulProxy,开启zuul的功能:

1 @EnableZuulProxy
 2 @EnableEurekaClient
 3 @SpringBootApplication
 4 public class ServiceZuulApplication {
 5 
 6     public static void main(String[] args) {
 7         SpringApplication.run(ServiceZuulApplication.class, args);
 8     }
 9 
10 
11 
12 }

配置文件application.yml如下:

1 eureka:
 2   client:
 3     serviceUrl:
 4       defaultZone: http://localhost:8761/eureka/
 5 server:
 6   port: 8769
 7 spring:
 8   application:
 9     name: service-zuul
10 zuul:
11   routes:
12     api-a:
13       path: /api-a/**
14       serviceId: service-ribbon
15     api-b:
16       path: /api-b/**
17       serviceId: service-feign

首先指定服务注册中心的地址为http://localhost:8761/eureka/,服务的端口为8769,服务名为service-zuul;以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务;

依次运行这五个工程;打开浏览器访问:http://localhost:8769/api-a/hello?name=51ma;浏览器显示:

hello 51ma,i am from port:8763

打开浏览器访问:http://localhost:8769/api-b/hi?name=51ma;浏览器显示:

hello 51ma,i am from port:8763

说明zuul起到路由的作用。

3.2过滤演示

添加MyFilter过滤器,如下:

1 import com.netflix.zuul.ZuulFilter;
 2 import com.netflix.zuul.context.RequestContext;
 3 import org.slf4j.Logger;
 4 import org.slf4j.LoggerFactory;
 5 import org.springframework.stereotype.Component;
 6 
 7 import javax.servlet.http.HttpServletRequest;
 8 
 9 /**
10  * Created by 51ma on 2019/5/13
11  */
12 @Component
13 public class MyFilter extends ZuulFilter{
14 
15     private static Logger log = LoggerFactory.getLogger(MyFilter.class);
16     @Override
17     public String filterType() {
18         return "pre";
19     }
20 
21     @Override
22     public int filterOrder() {
23         return 0;
24     }
25 
26     @Override
27     public boolean shouldFilter() {
28         return true;
29     }
30 
31     @Override
32     public Object run() {
33         RequestContext ctx = RequestContext.getCurrentContext();
34         HttpServletRequest request = ctx.getRequest();
35         log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString()));
36         Object accessToken = request.getParameter("token");
37         if(accessToken == null) {
38             log.warn("token is empty");
39             ctx.setSendZuulResponse(false);
40             ctx.setResponseStatusCode(401);
41             try {
42                 ctx.getResponse().getWriter().write("token is empty");
43             }catch (Exception e){}
44 
45             return null;
46         }
47         log.info("ok");
48         return null;
49     }
50 }
  • filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
  • pre:路由之前
  • routing:路由之时
  • post: 路由之后
  • error:发送错误调用
  • filterOrder:过滤的顺序
  • shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
  • run:过滤器的具体逻辑。可用很复杂,包括查sql,nosql去判断该请求到底有没有权限访问。

这时访问:http://localhost:8769/api-a/hello?name=51ma ;网页显示:

token is empty

访问 http://localhost:8769/api-a/hello?name=51ma&token=22 ; 网页显示:

hello 51ma,i am from port:8763