SpringCloud教程五:Zuul(路由+过滤)
一、概述
zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。
二、原理
zuul的核心逻辑都是由一系列filter过滤器链实现的,但是filter的类型不同,执行的时机也不同,效果自然也不一样,主要特点如下:
- filter的类型:filter的类型,决定了它在整个filter链中的执行顺序,可能在端点路由前执行,也可能在端点路由时执行,还有可能在端点路由后执行,甚至是端点路由发生异常时执行。
- filter的执行顺序:同一种类型的filter,可以通过filterOrder()方法设置执行顺序,一般都是根据业务场景自定义filter执行顺序。
- filter执行条件:filter运行所需的标准,或条件。
- filter执行效果:符合某个filter执行条件,产生执行效果。
zuul内部有一套完整的机制,可以动态读取编译运行filter机制,filter与filter之间不直接通信,在请求线程中会通过RequestContext来共享状态,它内部是用ThreadLocal实现的,例如HttpServletRequest、HttpServletResponse、异常信息等。
zuul一共有4种不同的生命周期:
- pre:在zuul网关按照规则路由到下级服务之前执行,如果需要对请求进行预处理,可以使用这种类型的过滤器。如:认证鉴权,限流等。
- route:这种过滤器是zuul路由动作的执行者,是Apache HttpClient或Ribbon构建和发送原始HTTP请求的地方,现在也支持OKHTTP。
- post:这种过滤器是在端点请求完毕,返回结果或者发生异常后执行的filter。如果需要对返回的结果进行再次处理,可以在这种过滤中处理逻辑。
- 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
四、参考资料
五、源码
加入qq群:Spring全家桶技术交流①群196165973,免费获取源码。