Spring Cloud Gateway实践体验

总体介绍

Spring Cloud Gateway是Spring Cloud官方推出的第二代网关框架,从官网给出的对比分析结果来看,Gateway比Zuul的性能要好不少,并且功能也更加丰富。 如下是官方对比Gateway、Zuul、Linkered的分析结果,能够看到Gateway是三个钟效果性能最好的。 html


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

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

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

predicate

predicate在JDK8中的定义以下:正则表达式

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

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

(图片来源网络)

环境准备

准备三个工程网络

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

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

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

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

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

@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错误,

而后咱们修改时间为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接口,并可以收到正确返回信息

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进行测试以下。

若是参数值不对一样会报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名称的参数,而且值要知足正则规则,必须为数字

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
复制代码

3.7 Method Route Predicate Factory

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

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

此时全部的GET请求都会转发,若是是POST请求就会404

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的请求,将会命中匹配,并成功转发。

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=leon八、name=leono、name=leon静均可以匹配

也能够只加一个参数,表明只验证参数名称,不验证参数值。只要包含指定名称的参数便可经过转发。

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.一、172.17.153.二、172.17.153.三、。。。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中发送请求,同时知足两个条件:

结果可见是按照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接口。

固然若是设置多个路由谓词,第一个知足优先转发,若是第一个不知足会继续往下判断,遇到知足的进行转发,咱们把请求条件改为不合适,则准发第二个接口。

以上的配置相似"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中配置便可。这里选择几个经常使用介绍,更多的配置可参考官方文档: cloud.spring.io/spring-clou…

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参数,可是咱们在转发后服务处理中是能够获取到的。

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

Global Filters

全局过滤器,不须要在配置文件中配置,做用在全部的路由上。Gatewqy内置的GlobalFilter以下:

(图片来源网络)

若是想要本身实现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参数,则没法经过转发。

项目综合使用

使用过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 #设置获取服务能够经过小写形式
复制代码

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

相关文章
相关标签/搜索