SpringCloud 之 Gateway 入门案例

入门案例

首先创建一个简单的 SpringBoot 工程,pom 依赖如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>club.lacerate</groupId>
    <artifactId>lacerate-gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>lacerate-gateway</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>2020.0.3</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </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>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.5.3</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Java 流式 API 自定义 RouteLocator 的方式

创建 Gateway 的配置类:

package club.lacerate.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("gateway_test", r -> r.path("/test")
                .uri("https://www.jd.com/")
                ).build();
    }
}

上面这段配置的意思是,配置了一个 id 为 gateway_test的路由规则,当访问地址 http://localhost:8080/test 时会自动转发到地址:https://www.jd.com/

访问测试:(访问 http://localhost:8080/test)

### YML 配置文件的方式

在 application.yml 中配置如下参数:

server:
  port: 8080
spring:
  cloud:
    gateway:
      routes:
        - id: gateway_test_2
          uri: https://www.jd.com/
          predicates:
            - Path=/test_2

上面配置的作用:

  • id:自定义的路由 ID,等同于 Gateway 的配置类中 route() 方法的第一个参数;
  • uri:需要转发的目标服务地址;
  • predicates:路由条件,predicate 接受一个输入参数,返回一个布尔值结果;该接口包含多种默认方法来将 predicate 组合成其他复杂的逻辑。


访问测试:(访问 http://localhost:8080/test_2)

路由断言

Spring Cloud Gateway 的路由匹配的功能是以 Spring WebFlux 中的 Handler Mapping 为基础实现的。Spring Cloud Gateway 也是由许多的路由断言工厂组成的,当 Http Request 请求进入 Spring Cloud Gateway 的时候,网关中的路由断言工厂会根据配置的路由规则,对 Http Request 请求进行断言匹配;匹配成功则进行下一步处理,否则断言失败直接返回错误信息。

After 路由断言工厂

After 路由断言工厂中会取一个 UTC 时间格式的时间参数,当请求进来的当前时间在配置的 UTC 时间之后,则会成功匹配,否则不能成功匹配。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        // 生成比当前时间早一个小时的UTC时间
        ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault());
        return builder.routes()
                .route("after_route", r ->r.after(minusTime).uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: after_route
          uri: https://www.jd.com/
          predicates:
            - After=2021-08-03T12:59:04.878+08:00[Asia/Shanghai]

若更改 UTC 时间为当前时间一个小时后的 UTC 时间,然后再启动应用,访问 http://127.0.0.1,页面会返回 404 错误

Before 路由断言工厂

Before 路由断言工厂会取一个 UTC 时间格式的时间参数,当请求进来的当前时间在配置的 UTC 时间之前,则会成功匹配,否则不能成功匹配。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        // 生成比当前时间晚一个小时的UTC时间
        ZonedDateTime plusTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
        return builder.routes()
                .route("before_route", r -> r.before(plusTime).uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: before_route
          uri: https://www.jd.com/
          predicates:
            - Before=2021-08-03T14:59:04.878+08:00[Asia/Shanghai]

Between 路由断言工厂

Between 路由断言工厂会取一个 UTC 时间格式的时间参数,当请求进来的当前时间在配置的 UTC 时间之间,则会成功匹配,否则不能成功匹配。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault());
        ZonedDateTime plusTime = LocalDateTime.now().plusHours(1).atZone(ZoneId.systemDefault());
        return builder.routes()
                .route("between_route", r -> r.between(minusTime, plusTime).uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: between_route
          uri: https://www.jd.com/
          predicates:
            - Between=2021-08-03T12:59:04.878+08:00[Asia/Shanghai], 2021-08-03T14:59:04.878+08:00[Asia/Shanghai]

Cookie 路由断言工厂

Cookie 路由断言工厂会取两个参数,分别是 cookie 名称对应的 key 和 value。当请求中携带的 cookie 和 Cookie 断言工厂中配置的 cookie 一致,则路由匹配成功,否则匹配不成功。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("cookie_route", r -> r.cookie("key", "value").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: cookie_route
          uri: https://www.jd.com/
          predicates:
            - Cookie=key, value

若 Cookie 包含book=java,则访问 http://127.0.0.1,可以成功跳转到京东的首页

Header 路由断言工厂

Header 路由断言工厂用于根据配置的路由 header 信息进行断言匹配路由,匹配成功进行转发,否则不进行转发。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("header_route", r -> r.header("X-Request-Id", "Foley").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: header_route
          uri: https://www.jd.com/
          predicates:
            - Header=X-Request-Id, Foley

若 Header 中包含X-Request-Id=Peter,则然后访问 http://127.0.0.1,可以成功跳转到京东的首页

Host 路由断言工厂

Host 路由断言工厂根据配置的 Host,对请求中的 Host 进行断言处理,断言成功则进行路由转发,否则不转发。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("host_route", r -> r.host("**.lacerate.club").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: host_route
          uri: https://www.jd.com/
          predicates:
            - Host=**.lacerate.club

若系统的 hosts 配置文件中包含域名映射:127.0.0.1 www.lacerate.club,则访问 http://www.lacerate.club,可以成功跳转到京东的首页

Method 路由断言工厂

Method 路由断言工厂会根据路由信息配置的 method 对请求方法是 Get 或者 Post 等进行断言匹配,匹配成功则进行转发,否则处理失败。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("method_route", r -> r.method("GET").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: method_route
          uri: https://www.jd.com/
          predicates:
            - Method=GET

Query 路由断言工厂

Query 路由断言工厂会从请求中获取两个参数,将请求中参数和 Query 断言路由中的配置进行匹配,比如 http://127.0.0.1?query=value 中的 query=value和下面的r.query(“query”,“value”)` 配置一致,则转发成功,否则转发失败。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("query_route", r -> r.query("query", "value").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: query_route
          uri: https://www.jd.com/
          predicates:
            - Query=query, value

Path 路由断言工厂

Path 路由断言工厂接收一个参数,根据 Path 定义好的规则来判断访问的 URI 是否匹配。在下述配置中,如果请求路径为 /path/test/,则此路由将匹配;也可以使用表达式,例如 /path/test/** 表示匹配 /path/test/ 开头的多级 URI。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("path_route", r -> r.path("/path/test/").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: https://www.jd.com/
          predicates:
            - Path=/path/test/

注意:上述的 URI 如果不以 / 结尾,那么转发后的 URI 为 https://www.jd.com/path/test/;若以 / 结尾,转发后的 URI 则为 https://www.jd.com/

Weight 路由断言工厂

Weight 路由断言工厂,在 Spring Cloud Gateway 中可以使用它对 URL 进行权重路由,只需在配置时指定分组和权重值即可。

示例

spring:
  cloud:
    gateway:
      routes:
        - id: weight_route_v1
          uri: http://127.0.0.1:8080/v1/
          predicates:
            - Path=/test
            - Weight=group, 95
        - id: weight_route_v2
          uri: http://127.0.0.1:8080/v2/
          predicates:
            - Path=/test
            - Weight=group, 5

上述配置中,添加了两个针对 /test 路径转发的路由定义配置,这两个路由属于同一个权重分组,权重的分组名称为 group。最终的效果是把 /test 接口的 95% 的请求流量分发给服务的 V1 版本,把剩余 5% 的流量分发给服务的 V2 版本。

RemoteAddr 路由断言工厂

RemoteAddr 路由断言工厂配置一个 IPv4 或 IPv6 网段的字符串或者 IP。当客户端的 IP 地址在网段之内或者和配置的 IP 相同,则成功转发,否则不能转发。

示例

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("remoteaddr_route", r -> r.remoteAddr("127.0.0.1").uri("https://www.jd.com/"))
                .build();
    }
}

spring:
  cloud:
    gateway:
      routes:
        - id: remoteaddr_route
          uri: https://www.jd.com/
          predicates:
            - RemoteAddr=127.0.0.1

过滤器 Filter

Spring Cloud Gateway 路由过滤器允许以某种方式修改进来的 HTTP 请求或返回的 HTTP 响应。路由过滤器主要作用于需要处理的特定路由,Spring Cloud Gateway 提供了很多种的过滤器工厂,过滤器的实现类将近二十多个。总得来说,可以分为七类:Header、Parameter、Path、Status、Redirect 跳转、Hytrix 熔断和 RateLimiter 限流。

AddRequestHeader 过滤器

AddRequestHeader 过滤器工厂用于对匹配上的请求加上 Header:

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("add_request_header_route", r -> r.path("/addRequestHeader") .filters(f -> f.addRequestHeader("X-Request-Id", "Foley")) .uri("http://127.0.0.1:8080/addRequestHeader/")
                ).build();
    }
}

AddRequestParameter 过滤器

AddRequestParameter 过滤器作用是对匹配上的请求添加请求参数:

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("add_request_parameter_route", r -> r.path("/addRequestParameter").filters(f -> f.addRequestParameter("key", "value")).uri("http://127.0.0.1:8080/addRequestParameter/")
                ).build();
    }
}

RewritePath 过滤器

Spring Cloud Gateway 可以使用 RewritePath 替换 Zuul 的 StripPrefix 功能,而且功能更强大。

在 Zuul 中使用如下配置后,所有 /example/xxxx 的请求会转发给 http://example.com/xxxx,同时去除掉了 example 前缀:

zuul:
  routes:
    example:
      path: /example/**
      stripPrefix: true
      uri: http://example.com

Spring Cloud Gateway 实现了类似的功能,使用的是 RewritePath 过滤器工厂:

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("rewrite_path_route", r -> r.path("/example/**").filters(f -> f.rewritePath("/example/(?<segment>.*)", "/$\\{segment}")).uri("https://www.jd.com/")
                ).build();
    }
}

注意:上述的 URI 是不以 / 结尾的,否则仅仅会直接跳转到 https://www.jd.com/

AddResponseHeader 过滤器

AddResponseHeader 过滤器工厂的作用是对从网关返回的响应添加 Header:

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("add_response_header_route", r -> r.path("/addResponseHeader").filters(f -> f.addResponseHeader("X-Request-Id", "Foley")).uri("https://www.jd.com/")
                ).build();
    }
}

StripPrefix 过滤器

StripPrefixGatewayFilterFactory 是一个对针对请求 URL 前缀进行处理的 Filter 工厂,用于去除前缀,而 PrefixPathGatewayFilterFactory 是用于增加前缀。

spring:
  cloud:
    gateway:
      routes:
        - id: strip_prefix_route
          uri: https://www.jd.com/
          predicates:
            - Path=/test/**
          filters:
            - StripPrefix=1

上述的配置,访问 http://127.0.0.1:8080/test,会跳转到 https://www.jd.com/,即去除了前缀 /test/

Retry 过滤器

网关作为所有请求流量的入口,网关对路由进行协议适配和协议转发处理的过程中,如果出现异常或网络抖动,为了保证后端服务请求的高可用,一般处理方式会对网络请求进行重试,接口必须需要做幂等处理。

@Configuration
public class CommonConfiguration {

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("retry_route", r -> r.path("/test/retry").filters(f -> f.retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR))).uri("http://127.0.0.1:8080/retry?key=value&count=2"))
                .build();
    }
}

config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR) 表示设置重试次数为两次,当服务调用失败时设置返回的状态码为 500,即服务器内部错误。

Hystrix 过滤器

Hystrix 可以提供熔断、服务降级和快速失败等功能。Spring Cloud Gateway 对 Hystrix 进行集成提供路由层面的服务熔断和降级,最简单的使用场景是当通过 Spring Cloud Gateway 调用后端服务,后端服务一直出现异常、服务不可用的状态。此时为了提高用户体验,就需要对服务降级,返回友好的提示信息给服务消费者,在保护网关自身可用的同时保护后端服务高可用。

示例:

引入spring-cloud-starter-netflix-hystrix依赖:

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

Fallback 控制器:

@RestController
public class FallbackController {

    @GetMapping("/fallback")
    public String fallback() {
        return "Spring Cloud Gateway Fallback!";
    }
}

配置 Hystrix 过滤器:

spring:
  cloud:
    gateway:
      routes:
        - id: hystrix_route
          predicates:
            - Path=/test
          filters:
            - name: Hystrix                         # Hystrix Filter 的名称
              args:                                 # Hystrix 配置参数
                name: fallbackcmd                   # HystrixCommand 的名字
                fallbackUri: forward:/fallback      # 表示触发熔断机制后的跳转请求url
          uri: http://127.0.0.1:9000/hystrix

# Hystrix 配置
hystrix:
 default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000        # 默认的熔断时间
    fallbackcmd:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 20000        # hystrix_route 的熔断时间

上面这段配置的意思是,配置了一个 id 为 hystrix_route 的路由规则,当访问地址 http://localhost:8080/test 时会自动转发到地址:http://127.0.0.1:9000/hystrix,当关闭 fallbackcmd 服务后会自动转发到地址:http://127.0.0.1:8080/fallback

Actuator API

添加开启端点的配置信息,Spring Cloud Gateway 提供了一个 Gateway Actuator,该 EndPiont 提供了关于 Filter 及 Routes 的信息查询以及指定 Route 信息更新的 Rest API 接口:

公开网关端点:

management:
  endpoint:
    gateway:
      enabled: true
  endpoints:
    web:
      exposure:
        include: gateway

Spring Cloud Gateway执行器端请求总结:

请求路径 请求类型 描述 备注
/actuator/gateway/globalfilters GET 检索应用于所有路由的全局过滤器
/actuator/gateway/routefilters GET 检索应用于路由的GatewayFilter工厂
/actuator/gateway/refresh POST 清除路由缓存
/actuator/gateway/routes GET 检索网关中定义的路由
/actuator/gateway/routes/{id} GET 检索有关单个路由的信息
/gateway/routes/ {id_route_to_create} POST 创建路由 使用指定路由字段的JSON body
/gateway/routes/{id_route_to_delete} DELETE 删除路由