Spring Cloud Gateway实践体验

整体介绍

Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,从官网给出的对比分析结果来看,Gateway比Zuul的性能要好很多,而且功能也更加丰富。
以下是官方对比Gateway、Zuul、Linkered的分析结果,可以看到Gateway是三个钟效果性能最好的。
[图片上传失败...(image-e737d0-1560949508627)]


从官网给出的图中可以看到客户端向Spring Cloud Gateway发出请求,然后网关转发给代理的服务,然后在将服务响应的结果返回给客户端。而且Gateway内部还有一系列的处理。

image

请求进来后,会首先由Gateway Handler Mapping进行处理,这里处理的过程中用到 predicate,通过的请求才发送到Gateway web handler做进一步处理。然后又会经过一系列的过滤器。过滤器和Zuul的类似,也有"pre"、"post"分类。

  • "pre"代表在请求前之前进行过滤处理
  • "post"代表在请求之后进行过滤处理
    一般我们在执行“pre”过滤器时,会进行鉴权、限流、日志输出等功能,以及请求头的更改、协议的转换;在请求之后执行“post”过滤器时,会对数据进行修改,比如响应头、协议的转换等。
    整个过程中有两个比较重要的概念就是predicatefilter,filter比较好理解,下面来介绍一下predicate。

predicate

predicate在JDK8中的定义如下:

Predicate<T> 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。add--与、or--或、negate--非

predicate这种输入类型属于Spring体系中的ServerWebExchange,它允许我们匹配HTTP请求中的任何内容,比如请求头或参数。而且Spring Cloud Gateway已经内置了很多Predict,这些Predict的源码在org.springframework.cloud.gateway.handler.predicate包中。具体可参考如下:

(图片来源网络)

环境准备

准备三个工程

  • leon-eureka 注册中心项目
  • leon-consumer 微服务项目
  • leon-gateway 网关项目

因为spring cloud gateway项目只支持spring boot 2.0以上版本,所以对spring cloud的版本也有要求。本文中使用的整体版本环境为:

  • spring boot :2.0.5
  • spring cloud : Finchley.SR1

(以上项目创建不在赘述,可参考案例工程),运行效果后,保证leon-gateway、leon-consumer都已经注册在leon-eureka服务上。


image

在leon-consumer中提供login接口,进行简单的登录验证:

@GetMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
    if ("leon".equals(username) && "888".equals(password)) {
        return "登录成功";
    }
    return "登录失败";
}

注意问题

在此版本上,添加注册中心客户端以来的包和版本

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

另外在leon-gateway中不能添加 web 依赖包,如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

添加后启动会报错:

Description:
Parameter 0 of method modifyRequestBodyGatewayFilterFactory in org.springframework.cloud.gateway.config.GatewayAutoConfiguration required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' that could not be found.

Action:
Consider defining a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' in your configuration.

因为Spring Cloud Gateway 是使用 netty+webflux实现,webflux与web是冲突的。

接下来我们在项目中实际使用gateway,通过gateway转发路由,将请求转发到leon-consumer中。

Route Predicate

Predicate的分类比较多,接下来在项目总一一使用。

3.1 After Route Predicate Factory

接收一个日期类型参数,在这个日期之后可以通过。
在配置文件(application.yml)中添加

spring:
  application:
    name: leon-gateway
  cloud:
    gateway:
      routes:
      - id: dev
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - After=2020-05-20T17:42:47.789-07:00[America/Denver]
  • id 为路由route的唯一标识。(这次测试不添加也可以,后续测试多个route是否必须添加)
  • uri 为转发请求的地址
  • predicates 为请求谓词,此处是在指定时间之后

现在时间为2019-05,所以是在指定时间之前,此时启动服务,我们访问http://localhost:8085/
可以看到转发请求后出现404错误,

image

然后我们修改时间为2018

spring:
  application:
    name: leon-gateway
  cloud:
    gateway:
      routes:
      - id: dev
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - After=2018-05-20T17:42:47.789-07:00[America/Denver]

重启服务后再次访问:http://localhost:8085/
可以看到已经转发到leon-consumer的login接口,并能够收到正确返回信息

image

3.2 Before Route Predicate Factory

在指定时间之前才能通过转发,具体效果类似,不在赘述

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Before=2019-01-20T17:42:47.789-07:00[America/Denver]

3.3 Between Route Predicate Factory

在指定范围时间之内的请求才能通过转发。接收两个日期参数,并且参数的形式,必须是较早的日期在前,较晚的日期在后,具体如下

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

3.4 Cookie Route Predicate Factory

cookiel谓词工厂接收两个参数,Cookie名称和值(也可以是正则表达式)。请求中必须包含给定名称的cookie,并且cookie值要符合给定的值(正则规则)才能通过转发。
通过postman添加cookie进行测试如下。


image

如果参数值不对同样会报404问题。

3.5 Header Route Predicate Factory

同样需要2个参数,一个是header名称,另外一个header值(可以为正则表达式),匹配通过后才转发

      - id: header_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Header=X-Request-Id, \d+

在Header中必须要有X-Request-Id名称的参数,并且值要满足正则规则,必须为数字

image

3.6 Host Route Predicate Factory

只接收一个参数,就是host name,可以使用"."来进行匹配,此参数在head中添加

       - id: host_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Host=**.leon.cn
image

3.7 Method Route Predicate Factory

接收一个参数,代表请求的类型。

      - id: method_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Method=GET

此时所有的GET请求都会转发,如果是POST请求就会404


image
image

3.8 Path Route Predicate Factory

接收一个参数,就是路径地址(可以为正则表达式)

      - id: path_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Path=/leon/{segment}

所有的请求路径满足/leon/{segment}的请求将会匹配并被路由,比如/leon/1 、/leon/bar的请求,将会命中匹配,并成功转发。


image

3.9 Query Route Predicate Factory

需要两个参数,一个是参数名,一个是参数值(正则表达式)。

      - id: query_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Query=name, leon.

上面配置了请求中含有参数name,并且name的值匹配leon.,则进行转发
比如请求参数为name=leon8、name=leono、name=leon静都可以匹配


image

也可以只加一个参数,代表只验证参数名称,不验证参数值。只要包含指定名称的参数即可通过转发。

3.10 RemoteAddr Route Predicate Factory

接收一个字符串参数,此字符串代表地址列表(最少1个),只有是以上要求的IP地址发来的请求才通过转发

      - id: remoteaddr_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - RemoteAddr=172.17.153.1/24

以上配置表明只有IP地址为172.17.153.1、172.17.153.2、172.17.153.3、。。。172.17.153.24可以通过转发。

3.11 组合

可以同时配置多个predicates,如果一个请求满足多个路由的谓词条件时,请求只会被首个成功匹配的路由转发。
我们在leon-consumer项目中添加接口方法

@GetMapping("/info")
public String info() {
    return "获取信息成功";
}

然后在leon-gatewa重视添加两个

spring:
  application:
    name: leon-gateway
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Header=X-Request-Id, \d+
      - id: query_route
        uri: http://localhost:8081/info
        predicates:
        - Query=name, leon.

这里我们同时添加了header_route、query_route,然后我们在postman中发送请求,同时满足两个条件:


image

结果可见是按照header_route来进行转发。

我们把两个条件顺序互换:

  cloud:
    gateway:
      routes:
      - id: query_route
        uri: http://localhost:8081/info
        predicates:
        - Query=name, leon.
      - id: header_route
        uri: http://localhost:8081/login?username=leon&password=888
        predicates:
        - Header=X-Request-Id, \d+

重启服务,然后再次访问,相同的请求这次被转发到info接口。


image

当然如果设置多个路由谓词,第一个满足优先转发,如果第一个不满足会继续往下判断,遇到满足的进行转发,我们把请求条件改成不合适,则准发第二个接口。


image

以上的配置类似"or"的条件,我们还可以配置组合使用,达到"and"的效果,要同时满足才能进行转发。

  cloud:
    gateway:
      routes:
      - id: zuhe_route
        uri: http://localhost:8081/info
        predicates:
        - Query=name, leon.
        - Header=X-Request-Id, \d+

此时如果有一个参数设置不对,那么就不会进行转发

Filter

Predict决定了请求由哪一个路由处理,在路由处理之前,需要经过“pre”类型的过滤器处理,处理返回响应之后,可以由“post”类型的过滤器处理。

在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer,全局的filter。现在来分别介绍。

GatewayFilter

GatewayFilter的使用同Predicate类似,都是在配置文件application.yml中配置即可。这里选择几个常用介绍,更多的配置可参考官方文档: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html

4.1 AddRequestHeader GatewayFilter Factory

在网关中添加Filter

   cloud:
    gateway:
      routes:
      - id: zuhe_route
        uri: http://localhost:8081/info
        predicates:
        - Query=name, leon.
        - Header=X-Request-Id, \d+
        filters:
        - AddRequestHeader=X-Request-Foo, Bar

这里我们添加了AddRequestHeader过滤器,在请求转发后给HEADER中添加参数X-Request-Foo,值为:Bar。

改造leon-consuemr工程

@GetMapping("/info")
public String info(HttpServletRequest request) {
    String header = request.getHeader("X-Request-Foo");
    return "获取信息成功:" + header;
}

可以看到,我们在路由转发后的处理方法中获取相关参数。然后发送请求,这个请求我们只满足路由转发条件,并没有添加X-Request-Foo的HEADER参数,但是我们在转发后服务处理中是可以获取到的。


image

其他更多的设置暂不涉及,请参考官方文档。

Global Filters

全局过滤器,不需要在配置文件中配置,作用在所有的路由上。Gatewqy内置的GlobalFilter如下:


image
image

(图片来源网络)

如果想要自己实现GlobalFilter也可以,实现GlobalFilter和Ordered接口即可。

public class GlobalFilter implements org.springframework.cloud.gateway.filter.GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("token");
        if (token == null || token.isEmpty()) {
            System.out.println("token为空");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

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

在启动类中添加配置,将配置类加入容器管理。

    @Bean
    public GlobalFilter globalFilter() {
        return new GlobalFilter();
    }

此时发情请求,可以发现如果HEADER中没有token参数,则无法通过转发。


image

项目综合使用

使用过Zuul的同学都了解,在Zuul中可以配置统一前置路由,比如现在我们想把所有路径中包含/user的都转发到leon-consumer工程去处理。
在以上的项目中继续改造,在leon-consumer项目中添加前缀映射:

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password) {
        if ("leon".equals(username) && "888".equals(password)) {
            return "登录成功";
        }
        return "登录失败";
    }

    @GetMapping("/info")
    public String info(HttpServletRequest request) {
        String header = request.getHeader("X-Request-Foo");
        return "获取信息成功:" + header;
    }
}

在leon-gateway中添加配置:

  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://leon-consumer #服务名,注意一定要以lb://开头
        predicates:
        - Path=/user/{segment}
        filters:
        - AddRequestHeader=X-Request-Foo, Bar
      discovery:
        locator:
          enabled: true #设置可以通过服务名获取服务
          lower-case-service-id: true #设置获取服务可以通过小写形式

在前面我们的uri都是直接写好的具体的地址,现在的服务已经注册到Eureka上,我们也可以通过服务名称找到具体的服务。
添加配置

      discovery:
        locator:
          enabled: true #设置可以通过服务名获取服务

通过lb:指定即可。默认配置的名称必须是全大写,想要通过小写识别,可添加配置

      discovery:
        locator:
          lower-case-service-id: true #设置获取服务可以通过小写形式

配置完成后,重启服务访问:


image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容