08Zuul路由网关
一、Zuul路由网关概述
源码地址:https://github.com/Netflix/zuul
Zuul包含了请求的路由和过滤两大主要的功能:
- 路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一的路径入口基础。通俗的说就“保安”。
- 过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。Zuul与Eureka进行整合,是将Zuul自身注册为Eureka服务治理下的应用,同时Eureka中获取其他的微服务的信息,也即以后微服务的访问都是通过Zuul跳转后获取。
注意:
- Zuul服务最终还是会注册到Eureka中。
- Zuul还包含了hystrix,actuator,ribbon等功能。
二、路由基本配置
1. 新建路由网关模块
模块名称:springcloud-zuul-gateway
2. pom.xml
<dependencies>
<!--zuul Getway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3. yml文件
server:
port: 9527
spring:
application:
name: springcloud-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
instance-id: springcloud-zuul-gateway
prefer-ip-address: true
# 微服务信息的描述
info:
# 工程名称
app.name: newcapec-springcloud
# 公司名称
company.name: www.newcapec.com.cn
project.artifactId: springcloud-zuul-gateway
project.version: v1.0.1
4. hosts修改
127.0.0.1 zuul9527.com
5. 主启动类
添加新注解@EnableZuulProxy,开启Zuul相关代理
@SpringBootApplication
//开启路由代理
@EnableZuulProxy
public class ZuulGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApplication.class, args);
}
}
6. 测试
- 启动三个EurekaServer
- 启动一个服务提供者springcloud-provider-8001
- 启动路由网关springcloud-zuul-gateway
- 不用路由http://localhost:8001/dept/list
- 使用路由http://myzuul.com:9527/springcloud-dept/dept/list
三、路由访问映射规则
1. 现有问题
通过路由访问的地址http://myzuul.com:9527/springcloud-dept/dept/list,在这里把真实的微服务名称暴露出来,现在需要用一个虚拟的路径来替代真实的微服务名称。
2. 映射路由
映射路由通常采用两种方式:
2.1 url方式
直接使用path和url地址路由到具体服务。在application.yml文件中添加以下配置:
# 路由访问映射
zuul:
routes:
user-defined-a:
path: /mydept-a/**
url: http://localhost:8001
user-defined-b:
path: /mydept-b/**
url: http://localhost:8002
user-defined-a和user-defined-b是自定义key,/mydept-a/**
的请求会被路由到http://localhost:8001
服务,/mydept-b/**
的请求会被路由到http://localhost:8002
服务
访问8001路径:http://zuul9527.com:9527/mydept-a/dept/list
访问8002路径:http://zuul9527.com:9527/mydept-b/dept/list
注意:此种方式不会触发ribbon负载均衡和hystrix熔断功能
2.2 服务id方式
直接使用path和serviceId服务路由到具体服务,具有ribbon负载均衡和hystrix熔断功能。在application.yml文件中添加以下配置:
# 路由访问映射
zuul:
routes:
user-defined:
path: /mydept/**
serviceId: springcloud-dept
user-defined是自定义key,/mydept/**
的请求会被路由到服务名称是springcloud-dept
的微服务
访问路径:http://zuul9527.com:9527/mydept/dept/list
3. 原真实服务名忽略
此时问题:原路径访问依然可以访问
在application.yml文件中添加以下配置:
# 路由访问映射
zuul:
# 忽略微服务,禁止通过微服务名称访问
ignored-services: springcloud-dept
# 批量忽略
# ignored-services: "*"
routes:
springcloud-dept:
path: /mydept/**
serviceId: springcloud-dept
单个具体,多个可以用"*"。
4. 设置统一公共前缀
# 路由访问映射
zuul:
# 设置统一公共前缀
prefix: /api
# 忽略微服务,禁止通过微服务名称访问
ignored-services: springcloud-dept
# 批量忽略
# ignored-services: "*"
routes:
springcloud-dept:
path: /mydept/**
serviceId: springcloud-dept
访问路径:http://zuul9527.com:9527/api/mydept/dept/list
5. 头部过滤
在使用Zuul网关的时候可能会遇到Cookie丢失的情况,因为默认情况下Zuul会过滤掉HTTP请求头、响应头中的一些敏感信息,不会向后传播,默认值为:Cookie
,Set-Cookie
,Authorization
。
测试Cookie和HttpSession是否可以使用:
@RestController
public class CookieController {
@GetMapping("/set/cookie")
public String setCookie(HttpServletResponse response){
Cookie mycookie = new Cookie("mycookie", "zhangsan");
mycookie.setMaxAge(60*60*24);
mycookie.setPath("/");
response.addCookie(mycookie);
response.setHeader("testheader","lisi");
return "cookie设置成功";
}
@GetMapping("/get/cookie")
public String getCookie(HttpServletRequest request){
String str = "";
Cookie[] cookies = request.getCookies();
if(cookies!=null){
for (Cookie cookie : cookies) {
if(cookie.getName().equals("mycookie")){
str = "name:" + cookie.getName() + "--value:" + cookie.getValue();
}
}
return "cookie获取成功"+str;
}
return "cookie获取失败";
}
@GetMapping("/set/session")
public String setSession(HttpSession session){
session.setAttribute("mykey", "myval");
return "session设置成功";
}
@GetMapping("/get/session")
public String getSession(HttpSession session){
Object str = session.getAttribute("mykey");
if(str != null){
return "session获取成功" + str;
}
return "session获取失败";
}
}
敏感信息通过下面的配置设定,设置全局:
zuul:
sensitive-headers: Cookie,Set-Cookie,Authorization
设置某一个微服务:
zuul:
routes:
user-defined-a:
path: /mydept-a/**
url: http://localhost:8001
sensitiveHeaders: Cookie,Set-Cookie,Authorization
关闭头部过滤,sensitiveHeaders设置为空。
6. 开启路由监控
打开actuator的监控端口,默认只打开了health、info的监控监控点*代表全部打开,此举是用于查看/routes接口,返回此zuul代理的服务,以及路由规则。
# 打开所有监控
management:
endpoints:
web:
exposure:
include: '*'
zuul有对外暴露的actuator接口:
- /routes:获取zuul代理的服务列表
- /routes/details:zuul代理的服务详情
访问路径:http://zuul9527.com:9527/actuator/routes
{
"/api/mydept-a/**": "http://localhost:8001",
"/api/mydept-b/**": "http://localhost:8002",
"/api/mydept/**": "springcloud-dept"
}
访问路径:http://zuul9527.com:9527/actuator/routes/details
{
"/api/mydept-a/**": {
"id": "user-defined-a",
"fullPath": "/api/mydept-a/**",
"location": "http://localhost:8001",
"path": "/**",
"prefix": "/api/mydept-a",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
},
"/api/mydept-b/**": {
"id": "user-defined-b",
"fullPath": "/api/mydept-b/**",
"location": "http://localhost:8002",
"path": "/**",
"prefix": "/api/mydept-b",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
},
"/api/mydept/**": {
"id": "user-defined",
"fullPath": "/api/mydept/**",
"location": "springcloud-dept",
"path": "/**",
"prefix": "/api/mydept",
"retryable": false,
"customSensitiveHeaders": false,
"prefixStripped": true
}
}
四、Zuul的核心
1. 过滤器
Filter(过滤器)是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示。
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
2. 默认实现的Filter
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
pre | 1 | DebugFilter | 标记调试标志 |
pre | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
禁用指定的Filter,可以在application.yml中配置需要禁用的filter。格式:
zuul:
FormBodyWrapperFilter:
pre:
disable: true
3. 自定义Filter
实现自定义Filter,需要继承ZuulFilter的类,并覆盖其中的4个方法。
public class MyFilter extends ZuulFilter {
/**
* 定义filter的类型,有pre、route、post、error四种
*/
@Override
public String filterType() {
return "pre";
}
/**
* 定义filter的顺序,数字越小表示顺序越高,越先执行
*/
@Override
public int filterOrder() {
return 10;
}
/**
* 表示是否需要执行该filter,true表示执行,false表示不执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* filter需要执行的具体操作
*/
@Override
public Object run() {
return null;
}
}
方法解析:
- filterType():该方法需要返回一个字符串表示过滤器的类型,而这个类型就是在http请求过程中定义的各个阶段。在zuul中默认定义了4个不同的生命周期过程类型,具体如下:
- pre:在请求被路由之前调用,FilterConstants.PRE_TYPE
- routing:路由请求时被调用,FilterConstants.ROUTE_TYPE
- post:在routing和error过滤器之后被调用,FilterConstants.POST_TYPE
- error:处理请求时发生错误时被调用,FilterConstants.ERROR_TYPE
- filterOrder():通过int值来定义过滤器的执行顺序,数值越小优先级越高。
- shouldFilter():返回一个boolean值来判断该过滤器是否要执行。我们可以通过此方法来指定过滤器的有效范围。
- run():过滤器的具体逻辑。在该方法中,我们可以实现自定义的过滤逻辑,来确定是否要拦截当前的请求,不对其进行后续的路由,或是在请求路由返回结果之后,对处理结果做一些加工等。
配置过滤器生效,在启动类或配置类中添加以下代码:
@Bean
public MyFilter myFilter() {
return new MyFilter();
}
4. 请求身份认证案例
因为服务网关应对的是外部的所有请求,为了避免产生安全隐患,需要对请求做一定的限制。
比如请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示。
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre"; // 可以在请求被路由之前调用
}
@Override
public int filterOrder() {
return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
}
@Override
public boolean shouldFilter() {
return true;// 是否执行该过滤器,此处为true,说明需要过滤
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获取请求中的参数
String token = request.getParameter("token-key");
if (StringUtils.isEmpty(token)) {
//不对其进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(405);
ctx.setResponseBody("token is empty");
return null;
} else {
//对请求进行路由
ctx.setSendZuulResponse(true);
return null;
}
}
}
ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
//获取请求中的参数
String token = request.getParameter("token-key");
if (StringUtils.isEmpty(token)) {
//不对其进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(405);
ctx.setResponseBody("token is empty");
return null;
} else {
//对请求进行路由
ctx.setSendZuulResponse(true);
return null;
}
}
}