Filter过滤器
一、过滤器简介
微服务网关经常需要对请求进行一些过滤操作,比如:鉴权之后添加Header携带令牌等。在过滤器中可以
- 为为请求增加请求头、增加请求参数 、增加响应头等等功能
- 鉴权、记录审计日志、统计请求响应时长等共性服务操作
微服务系统中有很多的服务,我们不希望在每个服务上都去开发鉴权、记录审计日志、统计请求响应时长等共性服务操作。所以对于这样的重复开发或继承类工作,放在gateway上面统一去做是最好不过了。
二、Filter的生命周期
Spring Cloud Gateway 的 Filter 的生命周期很简单,只有两个:“pre” 和 “post”。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
三、Filter的分类
Spring Cloud Gateway 的 Filter 从作用范围可分为:
- GatewayFilter:应用到单个路由或者一个分组的路由上。
- GlobalFilter:应用到所有的路由上
笔者并不建议你去花很多的时间去学习下面的这些Filter都是如何使用,下面的这些Filter笔者几乎没有用到过。因为我已经介绍过了,Filter的作用就是在某些需求场景下去修改HTTP的请求头、路径、参数等等,只要你对HTTP协议足够的熟悉,所有的过滤器需求你都可以自定义实现,比起使用内置的Filter往往更加灵活。
3.1.Gateway filter
过滤器工厂 | 作用 | 参数 |
---|---|---|
AddRequestHeader | 为原始请求添加Header | Header的名称及值 |
AddRequestParameter | 为原始请求添加请求参数 | 参数名称及值 |
AddResponseHeader | 为原始响应添加Header | Header的名称及值 |
DedupeResponseHeader | 剔除响应头中重复的值 | 需要去重的Header名称及去重策略 |
Hystrix | 为路由引入Hystrix的断路器保护 | HystrixCommand 的名称 |
FallbackHeaders | 为fallbackUri的请求头中添加具体的异常信息 | Header的名称 |
PrefixPath | 为原始请求路径添加前缀 | 前缀路径 |
PreserveHostHeader | 为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host | 无 |
RequestRateLimiter | 用于对请求限流,限流算法为令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始请求重定向到指定的URL | http状态码及重定向的url |
RemoveHopByHopHeadersFilter | 为原始请求删除IETF组织规定的一系列Header | 默认就会启用,可以通过配置指定仅删除哪些Header |
RemoveRequestHeader | 为原始请求删除某个Header | Header名称 |
RemoveResponseHeader | 为原始响应删除某个Header | Header名称 |
RewritePath | 重写原始的请求路径 | 原始路径正则表达式以及重写后路径的正则表达式 |
RewriteResponseHeader | 重写原始响应中的某个Header | Header名称,值的正则表达式,重写后的值 |
SaveSession | 在转发请求之前,强制执行WebSession::save 操作 | 无 |
SecureHeaders | 为原始响应添加一系列起安全作用的响应头 | 无,支持修改这些安全响应头的值 |
SetPath | 修改原始的请求路径 | 修改后的路径 |
SetResponseHeader | 修改原始响应中某个Header的值 | Header名称,修改后的值 |
SetStatus | 修改原始响应的状态码 | HTTP 状态码,可以是数字,也可以是字符串 |
StripPrefix | 用于截断原始请求的路径 | 使用数字表示要截断的路径的数量 |
Retry | 针对不同的响应进行重试 | retries、statuses、methods、series |
RequestSize | 设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回413 Payload Too Large | 请求包大小,单位为字节,默认值为5M |
ModifyRequestBody | 在转发请求之前修改原始请求体内容 | 修改后的请求体内容 |
ModifyResponseBody | 修改原始响应体的内容 | 修改后的响应体内容 |
Default | 为所有路由添加过滤器 | 过滤器工厂名称及值 |
每个过滤器工厂都对应一个实现类,并且这些类的名称必须以
GatewayFilterFactory
结尾,这是Spring Cloud Gateway的一个约定,例如AddRequestHeader
对应的实现类为AddRequestHeaderGatewayFilterFactory
。对源码感兴趣的小伙伴就可以按照这个规律拼接出具体的类名,以此查找这些内置过滤器工厂的实现代码
Filter并不如Predicate那么常用,更多的时候我们需要自定义Filter,所以官方内置的Filter我们就不一一介绍了。我们选两个例子来说明一下:
- AddRequestHeader GatewayFilter Factory
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: http://httpbin.org:80/get
filters:
- AddRequestHeader=X-Request-Foo, Bar
predicates:
- Method=GET
过滤器工厂会在匹配的HTTP的请求加上一个Header,名称为X-Request-Foo,值为Bar。
- RewritePath GatewayFilter Factory
在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能。
spring:
cloud:
gateway:
routes:
- id: rewritepath_route
uri: http://httpbin.org
predicates:
- Path=/foo/**
filters:
- RewritePath=/foo/(?<segment>.*), /${segment}
根绝predicates的定义所有的/foo/**开始的路径都会命中路由。
请求gateway路径http://httpbin.org/foo/get
,gateway通过过滤器对路径进行重写,将请求转发至http://httpbin.org/get
。
3.2.Global filter
Spring Cloud Gateway框架内置的GlobalFilter如下:
全局过滤器 | 作用 |
---|---|
Forward Routing Filter | 用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务 |
LoadBalancerClient Filter | 整合Ribbon实现负载均衡 |
Netty Routing Filter | 使用Netty的HttpClient 转发http、https请求 |
Netty Write Response Filter | 将代理响应写回网关的客户端侧 |
RouteToRequestUrl Filter | 将从request里获取的原始url转换成Gateway进行请求转发时所使用的url |
Websocket Routing Filter | 使用Spring Web Socket将转发 Websocket 请求 |
Gateway Metrics Filter | 整合监控相关,提供监控指标 |
每种Global filter的使用需要具体问题具体分析,通常遇到特殊情况,内置Global filter满足不了我们的需求,还可以自定义GlobalFilter。
自定义过滤器Filter
一、自定义全局过滤器-统计接口api响应时长
我们用一个常见的需求:api接口服务的响应时长的计算,这个需求的实现对请求访问链路的优化很有意义。具体实现看下文的代码及注释:
@Configuration
public class GlobalGatewayFilterConfig
{
@Bean
@Order(-100)
public GlobalFilter apiGlobalFilter()
{
return (exchange, chain) -> {
//获取请求处理之前的时间
Long startTime = System.currentTimeMillis();
//请求处理完成之后
return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
//获取请求处理之后的时间
Long endTime = System.currentTimeMillis();
//这里可以将结果进行持久化存储,我们暂时简单处理打印出来
System.out.println(
exchange.getRequest().getURI().getRawPath() +
", cost time : "
+ (endTime - startTime) + "ms");
}));
};
}
}
@Order
注解值越小,表示过滤器执行的优先级越高
我们使用《自定义PredicateFactory》那一节同样的测试用例,进行一下测试。zimug-server-gateway后台的打印结果如下:
通过上面的方法,可以在一个配置类里面写多个函数,每一个函数代表一个全局过滤器。
二、以class类的形式书写全局过滤器
上面的方法,当过滤器函数的实现内容比较复杂的时候,会导致单个类的代码行数过多,我们可以一个类写一个过滤器。
@Component
public class ApiGlobalFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return -100;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//获取请求处理之前的时间
Long startTime = System.currentTimeMillis();
//请求处理完成之后
return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
//获取请求处理之后的时间
Long endTime = System.currentTimeMillis();
//这里可以将结果进行持久化存储,我们暂时简单处理打印出来
System.out.println(
exchange.getRequest().getURI().getRawPath() +
", cost time : "
+ (endTime - startTime) + "ms");
}));
}
}
三、自定义局部过滤器-指定IP访问
在我们的系统中可能有几个功能是专门给系统管理员使用的,并不广泛开放。我们假设这样一个需求:只让某个ip(管理员操作的PC的IP)的客户端访问aservice-rbac权限管理服务,其他的ip不可以。
- 自定义Filter工厂需要继承 AbstractGatewayFilterFactory类,重写 apply 方法的逻辑。
- 在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。
- 在 apply 方法中可以通过chain操作过滤器链
@Component
@Order(99)
public class IPForbidGatewayFilterFactory
extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config> {
public IPForbidGatewayFilterFactory()
{
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder()
{
return Arrays.asList("permitIp"); //对应config类的参数
}
@Override
public GatewayFilter apply(Config config)
{
return (exchange, chain) -> {
//获取服务访问的客户端ip
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
if (config.getPermitIp().equals(ip)) {
//如果客户端ip=permitIp,继续执行过滤器链允许继续访问
return chain.filter(exchange);
}
//否则返回,拒绝请求
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
};
}
static public class Config
{
private String permitIp;
public String getPermitIp() {
return permitIp;
}
public void setPermitIp(String permitIp) {
this.permitIp = permitIp;
}
}
}
- 类的命名需要以 GatewayFilterFactory结尾,比如 IPForbidGatewayFilterFactory,那么在配置文件中使用该Filter的时候 IPForbid就是这个Filter工厂的名称。
- Config类可以定义一个或多个属性,要重写List shortcutFieldOrder()这个方法指定属性名称。
配置文件,因为只有一个参数,所以下图中的192.168.1.6
将赋值给config类的唯一参数:permitIp
如果我们从不是192.168.1.6
的这个客户端ip进行接口访问测试,将得到如下的结果:
如何为GatewayFilterFactory配置多个参数?
首先Config要有多个成员变量,如:permitIp、xxxx,其次配置文件进行如下配置
参考:www.kancloud.cn/qingshou/aa… www.kancloud.cn/qingshou/aa…