SpringCloud-gateway 的使用

初步使用

  1. 引入依赖

    gateway 是一个独立的 web 服务器,他使用的是 netty 的服务器,所以我们的项目不能包含 spring-boot-starter 依赖。为了避免被继承依赖,所以我们新建一个项目,自建依赖。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    <exclusions>
    <exclusion>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    </dependencies>

    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-dependencies</artifactId>
    <version>${spring-cloud.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>${spring-boot.version}</version>
    <type>pom</type>
    <scope>import</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
  2. 配置属性

    这里使用了 predicates 里面的 Path Router

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    spring:
    application:
    name: spring-cloud-gateway

    cloud:
    gateway:
    routes:
    - predicates:
    - Path=/gateway/**
    filters:
    - StripPrefix=1
    uri: http://localhost:8166/

    server:
    port: 8184

    需要注意的是 ,没有提示,大小写一定要区分(Path,StripPrefix),Path 和等号之间不能有空格 !!!。

  3. 测试

    上面配置的意思是,对于请求地址是以/gateway 开头的地址,全部转发到 http://localhost:8166/这个地址上面去。

    1
    2
    ## 访问 http://localhost:8184/gateway/orderId
    qaq

Predicate

Predicate 断言,可以配置多个,且的关系。通过判断条件来决定请求是否被路由到。

增加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: spring-cloud-gateway

cloud:
gateway:
routes:
- id: path_route
predicates:
- Path=/gateway/**
filters:
- StripPrefix=1
uri: http://localhost:8166/
- id: cookie_route
predicates:
- Cookie=name,zsf
uri: http://www.baidu.com
server:
port: 8184

多个 router 可以通过 id 区分,每个 router 之间不会相互干扰。

新增的 cookie_router 的意思是,如果请求中带有 cookie,key 是 name,value 是 zsf 的,将会被转发到http://www.baidu.com的请求上。

1
2
curl --location --request POST 'http://localhost:8184' \
--header 'Cookie: name=zsf; BDSVRTM=0'

自定义 Predicate

Route Predicate Factories

SpringCloud 的官网上面默认提供了 11 种 predicate,如果我们需要自定义 Predicate,主要继承 AbstractRoutePredicateFactory 类就可以了

image-20201230161811682

需要注意的是 predicate 的命令是有规律的,前缀为 predicate 的名字,后面必须跟上 RoutePredicateFactory

  1. 创建 AbstractRoutePredicateFactory 新的子类 AuthRoutePredicateFactory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Component
    public class AuthRoutePredicateFactory extends AbstractRoutePredicateFactory<Config> {

    public AuthRoutePredicateFactory() {
    super(Config.class);
    }

    private static final String NAME_KEY = "name";
    private static final String NAME_VALUE = "value";

    @Override
    public List shortcutFieldOrder() {
    return Arrays.asList(NAME_KEY, NAME_VALUE);
    }

    @Override
    public Predicate apply(Config config) {
    return (exchange -> {
    HttpHeaders headers = exchange.getRequest().getHeaders();
    List list = headers.get(config.getName());
    return !list.isEmpty();
    });
    }

    public static class Config {

     private String name;
     private String value;
    
     public String getName() {
       return name;
     }
    
     public void setName(String name) {
       this.name = name;
     }
    
     public String getValue() {
       return value;
     }
    
     public void setValue(String value) {
       this.value = value;
     }

    }
    }

    1
    2
    3

    需要加上 spring 的 bean 注解 component,才能被加载到

  2. 添加配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    spring:
    application:
    name: spring-cloud-gateway

    cloud:
    gateway:
    routes:
    - id: auth_route
    predicates:
    - Path=/auth/**
    - Auth=Authorization
    uri: http://www.google.com
    server:
    port: 8184

    上述配置意思是,对于请求路径以 auth 开头,包含以 Authorization 为 key 的请求头的请求,将会被转发到http://www.google.com

  3. 使用

    1
    2
    curl --location --request POST 'http://localhost:8184/auth' \
    --header 'Authorization: xxxx'

Filter

filter 过滤器,可以配置多个,且的关系。

image-20201230170300061

SpringCloud 默认提供了 31 种 filter 的实现来我们选择。

StripPrefix 主要用来做路径转发,Retry 用来做重试,RequestRateLimiter 用来做限流

自定义过滤器

  1. 继承 AbstractGatewayFilterFactory

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    @Component
    public class DiyGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {

    private static final String NAME_KEY = "name";
    private static final String NAME_VALUE = "value";

    public DiyGatewayFilterFactory() {
    super(Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
    return Arrays.asList(NAME_KEY, NAME_VALUE);
    }

    @Override
    public GatewayFilter apply(Config config) {
    return ((exchange, chain) -> {
    System.out.println("[pre] Filter Requests,name:" + config.getName());
    return chain.filter(exchange).then(Mono.fromRunnable(() -> {
    System.out.println("[post] Filter Requests");
    }));
    });
    }

    public static class Config {

    private String name;
    private String value;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public String getValue() {
    return value;
    }

    public void setValue(String value) {
    this.value = value;
    }
    }
    }
  2. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    spring:
    application:
    name: spring-cloud-gateway

    cloud:
    gateway:
    routes:
    - id: filter_router
    predicates:
    - Path=/filter/**
    filters:
    - StripPrefix=1
    - Diy=Hello Filter
    uri: http://localhost:8166/

    port: 8184

    这个过滤器没有实际作用,主要是对于请求地址以 filter 开头的请求打印日志

  3. 调用

    1
    curl --location --request GET 'http://localhost:8184/filter/orderId'

    结果

    1
    2
    [pre] Filter Requests,name:Hello Filter
    [post] Filter Requests

RequestRateLimiter 的使用

  1. 实现 KeyResolver

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    public class IpAddressKeyResolve implements KeyResolver {

    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
    String hostAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
    System.out.println(hostAddress);
    return Mono.just(hostAddress);
    }
    }
  2. 引入依赖

    1
    2
    3
    4
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
  3. 配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    spring:
    application:
    name: spring-cloud-gateway

    cloud:
    gateway:
    routes:
    - id: ratelimiter_route
    predicates:
    - Path=/ratelimiter/**
    filters:
    - StripPrefix=1
    - name: RequestRateLimiter
    args:
    deny-empty-key: true
    keyResolver: '#{@ipAddressKeyResolve}'
    redis-rate-limiter.replenishRate: 1
    redis-rate-limiter.burstCapacity: 1
    uri: http://localhost:8166/

    redis:
    host: localhost
    port: 6379
    server:
    port: 8184

    需要配置 redis。

    keyResolver: ‘#{@ipAddressKeyResolve}’ 指定对应的限流的判定值,案例中是 ip 地址

    redis-rate-limiter.replenishRate: 1,每秒最多请求 1 次

    redis-rate-limiter.burstCapacity: 1 ,最多缓存 1 个请求数

  4. 访问

    1
    http://localhost:8184/ratelimiter/orderId

    超出频率后返回

    1
    2
    该网页无法正常运作如果问题仍然存在,请与网站所有者联系。
    HTTP ERROR 429
  5. 注意点

    这里面如果 redis 端口配置错误,报错:

    1
    io.lettuce.core.RedisException: Invalid first byte: 74

    如果 redis 没有启动,不会有任何报错信息,这 2 种情况需要自己注意。