Spring Cloud Gateway-Filter过滤器

608 阅读8分钟

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为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
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…