Spring Cloud Gateway 之 Filter

文章首发于公众号《程序员果果》

地址:https://mp.weixin.qq.com/s/CFqKtIzP1ZzjioVZyGugeQ

简介

网关经常需要对路由请求进行过滤,进行一些操作,如鉴权之后构造头部之类的,过滤的种类很多,如增加请求头、增加请求 参数 、增加响应头和断路器等等功能,这就用到了Spring Cloud Gateway 的 Filter。

作用

当我们有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。

对于这样重复的工作,可以在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gateway服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。

生命周期

Spring Cloud Gateway 的 Filter 的生命周期不像 Zuul 的那么丰富,它只有两个:“pre” 和 “post”。

  • PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
  • POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的 HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

分类

Spring Cloud Gateway 的 Filter 从作用范围可分为另外两种GatewayFilter 与 GlobalFilter。

  • GatewayFilter:应用到单个路由或者一个分组的路由上。
  • GlobalFilter:应用到所有的路由上。

Gateway filter

过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器的作用域为特定路由。Spring Cloud Gateway包含许多内置的GatewayFilter工厂。

3-4.png

官方文档都给出了这些过滤器工厂详细的使用案例,在这里我们讲解2个来演示下使用。

AddRequestHeader GatewayFilter Factory

application.yml如下:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: http://httpbin.org:80/get
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
        predicates:
        - Method=GET

过滤器工厂会在匹配的请求头加上一对请求头,名称为X-Request-Foo,值为Bar。

###RewritePath GatewayFilter Factory

在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能,这个功能是Zuul没有。

application.yml如下:

spring:
  cloud:
    gateway:
      routes:
      - id: rewritepath_route
        uri: http://httpbin.org
        predicates:
        - Path=/foo/**
        filters:
        - RewritePath=/foo/(?<segment>.*), /$\{segment}

所有的/foo/**开始的路径都会命中配置的router。

请求http://httpbin.org/foo/get ,会转到http://httpbin.org/get,结果如下:

{
  "args": {
    
  },
  "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate, br",
    "Accept-Language": "zh-CN,zh;q=0.9,zh-TW;q=0.8",
    "Cache-Control": "max-age=0",
    "Cookie": "Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068=1550127915; sensorsdata2015jssdkcross=%7B%22distinct_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22%24device_id%22%3A%22168eada0ded53b-0d5d8c3ba9b7a2-10316653-1296000-168eada0dee3ad%22%2C%22props%22%3A%7B%7D%7D; _gauges_unique_month=1; _gauges_unique_year=1; _gauges_unique=1",
    "Forwarded": "proto=http;host=\"127.0.0.1:8080\";for=\"127.0.0.1:62278\"",
    "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
    "X-Forwarded-Host": "127.0.0.1:8080",
    "X-Forwarded-Prefix": "/foo"
  },
  "origin": "127.0.0.1, 124.74.78.150, 127.0.0.1",
  "url": "https://127.0.0.1:8080/get"
}

自定义GatewayFilter

Spring Cloud Gateway内置了的过滤器工厂,足够是大部分场景使用,而且我们可以实现GatewayFilter和Ordered 这两个接口来自定义过滤器。代码如下:

/**
 * 统计某个或者某种路由的处理时长
 */
public class CustomerGatewayFilter implements GatewayFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilter.class );
    private static final String COUNT_START_TIME = "countStartTime";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        exchange.getAttributes().put(COUNT_START_TIME, Instant.now().toEpochMilli() );
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    long startTime = exchange.getAttribute(COUNT_START_TIME);
                    long endTime=(Instant.now().toEpochMilli() - startTime);
                    log.info(exchange.getRequest().getURI().getRawPath() + ": " + endTime + "ms");
                })
        );
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

上述代码中,getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。需要将自定义的GatewayFilter 注册到router中,代码如下:

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/customer/**")
                    .filters(f -> f.filter(new CustomerGatewayFilter())
                            .addResponseHeader("X-Response-test", "test"))
                    .uri("http://httpbin.org:80/get")
                    .id("customer_filter_router")
            )
            .build();
}

使用 curl 测试,命令行输入:

curl http://localhost:8080/customer/555

控制台输出如下:

2019-02-22 10:39:47.840  INFO 1310 --- [ctor-http-nio-3] com.gf.config.CustomerGatewayFilter      : /customer/555: 314ms

自定义过滤器工厂

自定义GatewayFilter又有两种实现方式,一种是上面的直接 实现GatewayFilter接口,另一种是 自定义过滤器工厂(继承AbstractGatewayFilterFactory类) , 选择自定义过滤器工厂的方式,可以在配置文件中配置过滤器了。

@Component
public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomerGatewayFilterFactory.Config> {

    private static final Logger log = LoggerFactory.getLogger( CustomerGatewayFilterFactory.class );
    private static final String COUNT_START_TIME = "countStartTime";

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("enabled");
    }

    public CustomerGatewayFilterFactory() {
        super(Config.class);
        log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!config.isEnabled()) {
                return chain.filter(exchange);
            }
            exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        Long startTime = exchange.getAttribute(COUNT_START_TIME);
                        if (startTime != null) {
                            StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath())
                                    .append(": ")
                                    .append(System.currentTimeMillis() - startTime)
                                    .append("ms");
                            sb.append(" params:").append(exchange.getRequest().getQueryParams());
                            log.info(sb.toString());
                        }
                    })
            );
        };
    }

    public static class Config {
        /**
         * 控制是否开启统计
         */
        private boolean enabled;

        public Config() {}

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }

}

application.yml 中 网关路由配置如下:

spring:
  profiles: elapse_route
  cloud:
    gateway:
      routes:
        - id: elapse_route
          uri: http://httpbin.org:80/get
          filters:
          - Customer=true
          predicates:
          - Method=GET

使用 curl 测试,命令行输入:

curl http://localhost:8080/customer?foo=1

控制台输出如下:

2019-02-23 13:37:08.769  INFO 1700 --- [ctor-http-nio-3] c.g.config.CustomerGatewayFilterFactory  : /customer: 585ms params:{foo=[1]}

Global filter

Spring Cloud Gateway框架内置的GlobalFilter如下:

内置的 GlobalFilter 能够满足大多数的需求了,但是如果遇到特殊情况,内置满足不了我们的需求,还可以自定义GlobalFilter。

自定义GlobalFilter

下面的我们自定义一个GlobalFilter,去校验所有请求的请求参数中是否包含“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。

/**
 * Token 校验全局过滤器
 */
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {

    private static final Logger log = LoggerFactory.getLogger( AuthorizeFilter.class );

    private static final String AUTHORIZE_TOKEN = "token";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN );
        if ( StringUtils.isBlank( token )) {
            log.info( "token is empty ..." );
            exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED );
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

}

使用 curl 测试,命令行输入:

curl http://localhost:8080/customer?foo=1

控制台输出如下:

token is empty ...

源码

https://github.com/gf-huanchupk/SpringCloudLearning/tree/master/chapter13/springcloud-gateway-filter

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Layabox

LayaAir 2.0.1新增3D动画文件压缩、内存与性能优化、开放域项目创建等功能

今天,LayaAir 2.0.1 beta版发布了,在这个版本里,有几个比较重要的更新,小编分别为大家介绍一下。

31020
来自专栏Java架构沉思录

分布式链路追踪系统原来是这么一回事

分布式系统变得日趋复杂,越来越多的组件开始走向分布式化,如微服务、分布式数据库、分布式缓存等,使得后台服务构成了一种复杂的分布式网络。在服务能力提升的同时,复杂...

39510
来自专栏ThoughtWorks

细说API - 认证、授权和凭证

遗憾的是依然有大量候选人答非所问,无法搞清楚 cookie 和 session 之间的区别。而在工作中也有让人惊讶的真实案例:把 user ID 存储到 loc...

35020
来自专栏人工智能头条

这个技术让我毛骨悚然后背发凉!

是不是足够可以以假乱真了!这样的视频用一款实时视频仿真软件 Face2Face 就可以达到,在软件中输入一个说话的人脸录像,通过算法生成对应的人脸模型,套用这个...

20820
来自专栏我的安全视界观

【基础安全】堡垒机的自动化功能实践4

这一章节,将结合实际情况(由于底层基础设施的差异,导致实现过程中会有差别),从以下三方面介绍架构调优。

11910
来自专栏Crossin的编程教室

这可能是我用过最“强大”的API

但以上这些都不是我今天要说的。今天要说的这个接口,之所以称为“强大”,因为它返回的内容里蕴含了未知的巨大能量,大到可以轻松毁灭宇宙 N 次……

19010
来自专栏闰土大叔

曾经热爱的Chrome,让我失业了

据我了解,很多前端从业者已经将 Google Chrome设置为他们电脑上默认的浏览器了。

10750
来自专栏Layabox

一篇读懂HTML5引擎性能之王LayaAir

性能是HTML5引擎最核心的指标,性能一旦出现瓶颈,就会限制策划的系统设计,限制美术的画面表现。譬如,近期有CP吐槽“本欲使用某引擎设计5 V 5的战斗,同屏...

18220
来自专栏闰土大叔

前端工作N年,你被年轻一届取代是有原因的

最近我司由于业务扩张,需要招一些即战力的coder,来为年后的项目做准备。作为面试官,一开始我让HR筛选简历的时候,优先选一些有3-5年工作经验的老鸟,毕竟需要...

14620
来自专栏机器之心

官方解读:TensorFlow 2.0中即将到来的所有新特性

作为最流行的深度学习框架,TensorFlow 已经成长为全球使用最广泛的机器学习平台。目前,TensorFlow 的开发者社区包括研究者、开发者和企业等。

13730

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励