Zuul 可以通过定义过滤器来实现请求的拦截和过滤,而它本身的大部分功能也是通过过滤器实现的
在 Zuul 中自定义过滤器需要继承抽象类 ZuulFilter
,需要实现以下4个方法:
String filterType(); // 过滤器类型
int filterOrder(); // 执行顺序,数值越小优先级越高
boolean shouldFilter(); // 执行过滤器的条件
Object run() throws ZuulException; // 具体的过滤操作
从名字我们也能知道各个方法的作用,下面来了解下过滤器类型和过滤器的生命周期,以及自定义过滤器的使用
Zuul 定义了4种不同的过滤器类型,对应着请求的典型生命周期
PRE
: 在请求被路由之前执行。可以用于请求身份验证、选择源服务器和记录调试信息ROURING
: 该过滤器将请求路由到服务。用于构建和发送给微服务的请求(使用 Apache HttpClient 或 Netflix Ribbon 请求服务)POST
: 在请求被路由到服务之后执行。可以用于向响应添加标准的 HTTP Header、收集统计数据和指标,以及将响应从源服务发送到客户端ERROR
: 该过滤器在其他阶段发生错误时执行除了默认的过滤器流,Zuul 还允许我们创建自定义过滤器类型并显式地执行它们。例如,我们可以自定义一个 STATIC
类型的过滤器,它在Zuul 中生成响应,而不是将请求转发到后端的服务
下面是 Zuul 的生命周期图,描述着各种类型的过滤器的执行顺序(图片来源于 Zuul Wiki)
Spring Cloud Zuul 作为服务网关的大部分功能都是通过过滤器实现的,它在请求的各个阶段实现了一系列的过滤器,在 Spring Cloud Zuul 网关服务启动时自动加载和启用。实现的这些过滤器是在 spring-cloud-netflix-zuul
模块中的 org.springframework.cloud.netflix.zuul.filters
包下面
下面介绍部分过滤器的功能
Pre filters
filter | order | 说明 |
---|---|---|
ServletDetectionFilter |
-3 | 检测请求是否通过 Spring 调度程序,即判断请求是交由 Spring DispatcherServlet 处理,还是 ZuulServlet 处理(主要是用于大文件上传) |
Servlet30WrapperFilter |
-2 | 把原始 HttpServletRequest 包装成 Servlet30RequestWrapper 对象 |
FormBodyWrapperFilter |
-1 | 解析表单数据并为下游服务重新编码 |
DebugFilter |
1 | 如果设置 debug 请求参数,则此过滤器将RequestContext.setDebugRouting() 和 RequestContext.setDebugRequest() 设置为true |
PreDecorationFilter |
5 | 根据提供的 RouteLocator 确定路由的位置和方式,它还为下游请求设置各种与代理相关的头文件 |
Route filters
filter | order | 说明 |
---|---|---|
RibbonRoutingFilter |
10 | 使用 Ribbon、Hystrix 和 可插拔 HTTP客户机发送请求。只对 RequestContext 存在 serviceId 参数的请求进行处理,即只对通过 serviceId配置路由规则的请求路由。可以使用不同的 HTTP 客户端:HttpClient、OkHttpClient、Netflix Ribbon HTTP client |
SimpleHostRoutingFilter |
100 | 通过 Apache HttpClient 发送请求到预定的 url,这些 url 可以在 RequestContext.getRouteHost() 中找到,即只对通过 url 配置路由规则的请求路由 |
SendForwardFilter |
500 | 通过使用 Servlet RequestDispatcher 转发请求。用于转发请求到当前应用的端点 |
Post filters
filter | order | 说明 |
---|---|---|
LocationRewriteFilter |
900 | 负责将 Location header 重写为 Zuul URL |
SendResponseFilter |
1000 | 将代理请求的响应写入当前响应 |
Error filters
filter | order | 说明 |
---|---|---|
SendErrorFilter |
0 | 利用请求上下文中的错误信息来组织成一个 forward 到 /error 错误端点的请求来产生错误响应 |
禁用过滤器
默认情况下,这些过滤器在代理和服务器模式下都是启用的。如果在某些场景下,禁用某个过滤器,可以设置
zuul.
。例如,要禁用. .disable=true org.springframework.cloud.netflix.zuul.filter.post.sendresponsefilter
,设置zuul.SendResponseFilter.post.disable=true
创建一个 Spring Boot 项目 zuul-filters
,Zuul 的服务网关路由配置可以见 Spring Cloud Zuul 构建微服务网关
spring:
application:
name: zuul-filters
server:
port: 8090
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
zuul:
ignoredServices: '*'
routes:
product:
path: /product/**
serviceId: product-service
management:
endpoints:
web:
exposure:
include: '*'
自定义一个过滤器 AccessFilter
,对请求中没有 accessToken 参数的请求,返回 401拒绝访问
@Log4j2
public class AccessFilter extends ZuulFilter {
@Override
public int filterOrder() {
// run before PreDecoration
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
// a filter has already forwarded
// a filter has already determined serviceId
return !ctx.containsKey(FilterConstants.FORWARD_TO_KEY)
&& !ctx.containsKey(FilterConstants.SERVICE_ID_KEY);
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("send {} request to {}", request.getMethod(), request.getRequestURL().toString());
String token = request.getParameter("accessToken");
if(StringUtils.isBlank(token)) {
log.warn("access token is empty");
// 过滤该请求,不对其进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info("access token ok");
return null;
}
}
实现自定义过滤器后,把它添加到 Spring 的 Beans 中
@SpringBootApplication
@EnableZuulProxy
public class ZuulFiltersApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulFiltersApplication.class, args);
}
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
下面我们来测试下结果,启动项目 eureka-server
、product-serivce
(作为代理的服务)、zuul-filters
访问 http://localhost:8090/product/product/1
返回 401
访问 http://localhost:8090/product/product/1?accessToken=111
会正常路由到 product-service 的 /product/1
参考代码见:demo
@EnableZuulProxy
注解配合 Spring Boot Actuator,Zuul 会暴露额外的两个管理端点:Routes
和 Filters
。分别是关于路由和过滤器的端点(服务路由的端点在这里介绍 Spring Cloud Zuul 构建微服务网关)
spring-cloud-starter-netflix-zuul
已经依赖了 spring-boot-starter-actuator
,所以上面的工程已经包含了路由管理的功能。关于过滤器的管理端点的路径为 /filters
访问路径 http://localhost:8090/actuator/filters
{
"error": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter",
"order": 0,
"disabled": false,
"static": true
}
],
"post": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
"order": 1000,
"disabled": false,
"static": true
}
],
"pre": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.DebugFilter",
"order": 1,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.FormBodyWrapperFilter",
"order": -1,
"disabled": false,
"static": true
},
{
"class": "com.turbosnail.zuul.filter.AccessFilter",
"order": 4,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter",
"order": -2,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.ServletDetectionFilter",
"order": -3,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter",
"order": 5,
"disabled": false,
"static": true
}
],
"route": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter",
"order": 100,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
"order": 10,
"disabled": false,
"static": true
},
{
"class": "org.springframework.cloud.netflix.zuul.filters.route.SendForwardFilter",
"order": 500,
"disabled": false,
"static": true
}
]
}
访问404是因为没有暴露端点,可以设置 management.endpoints.web.exposure.include: ‘*’