## SpringCloud Gateway
SpringCloud Gateway 是一个基于 Spring 生态系统的 API 网关。基于 Spring Boot 2.x, Spring WebFlux 和 Reactor 构建。因此,使用 Spring Cloud Gateway 时,许多熟悉的同步库(例如,Spring Data 和 Spring Security )和模式可能不适用。
Spring Cloud Gateway 需要Spring Boot 和 Spring Webflux 提供的 Netty 运行时环境。它不能在传统的 Servlet 容器中或作为 WAR 包构建。
## 几个重要概念
### 路由(Route)
路由网关的基本模块。它由 ID,目标地址URI,谓词集合和过滤器集合定义。如果聚合谓词为true,则匹配路由。
### 谓词(Predicate)
[ Java 8 Function谓词](https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html)。输入类型为 [ Spring Framework `ServerWebExchange`](https://docs.spring.io/spring/docs/5.0.x/javadoc-api/org/springframework/web/server/ServerWebExchange.html)。这使开发人员可以匹配HTTP请求中的任何内容,例如请求头或参数。
### 过滤器(Filter)
这些是使用特定工厂构造的 [ Spring Framework `GatewayFilter`](https://docs.spring.io/spring/docs/5.0.x/javadoc-api/org/springframework/web/server/GatewayFilter.html) 实例。在此,可以在发送下游请求之前或之后修改请求和响应。
## 工作原理

客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射(Gateway Handler Mapping )确定请求与路由匹配,则将其发送到网关Web处理程序(Gateway Web Handler)。该处理程序通过特定于请求的过滤器链运行请求。过滤器由虚线分隔的原因是,过滤器可以在发送代理请求之前或之后执行逻辑。执行所有“前置”过滤器逻辑,然后发出代理请求。发出代理请求后,将执行“后置”过滤器逻辑。
## 配置方式
有两种配置谓词和过滤器的方法:快捷配置和完全扩展参数配置。
### 快捷配置
快捷配置通过过滤器名称标识,后跟等号(`=`),再跟由逗号分隔的参数值(`,`)。
```yaml
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://zysite.top
predicates:
- Cookie=mycookie,mycookievalue
```
上面的示例使用两个参数定义了`Cookie` 路由谓词工厂,即 cookie 名称与`mycookie`匹配以及 cookie 的值与`mycookievalue`匹配的请求。
### 完全扩展参数配置
完全扩展的参数配置看起来更像是 name/value 对的标准Yaml 配置。通常,将有一个`name`键和一个`args`键。`args`键是用于配置谓词或过滤器的键值对的映射。
```yaml
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://zysite.top
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
```
上面显示的`Cookie`谓词的快捷配置的完整配置。`regexp`是 Java 正则表达式。
## 路由谓词工厂
### After
`After`路由谓词工厂只有一个参数,即`datetime`(就是Java `ZonedDateTime`)。该谓词匹配在当前日期时间之后发生的请求。
```yaml
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://zysite.top
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
```
该路由与2017年1月20日17:42山区时间(丹佛)之后的所有请求匹配。
与之类似的还有`Before`,`Between`。Between 接收两个 datetime,用逗号隔开。
还有上一部分的示例`Cookie`。
### Header
`Header` 路由谓词工厂具有两个参数,请求头`name`和`regexp`。该谓词与具有给定名称请求头,并且值与正则表达式匹配的请求匹配。
```yaml
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://zysite.top
predicates:
- Header=X-Request-Id, \d+
```
如果请求具有名为`X-Request-Id`的请求头,且其值与`\d+`正则表达式匹配(具有一个或多个数字的值),则此路由匹配。
### Host
`Host`路由谓词工厂只有一个参数:主机名`patterns`的列表。模式是 Ant 样式的模式,以`.`作为分隔符。该谓词匹配请求头中的 Host。
```yaml
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://zysite.top
predicates:
- Host=**.spring.io,**.netty.io
```
上面的路由匹配任意主机以`spring.io`、`netty.io`结尾发出的请求。如`aa.spring.io`、`bb.netty.io`。
还支持 `URI 模版变量`,如 `Host = {sub}.spring.io`,其中`sub`即为 URI 模版变量。可通过 `ServerWebExchange.getAttributes()`获得 Map,再通过 Map 的 get("sub") 获取。
```java
Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);
String sub = uriVariables.get("sub");
```
### Method
`Method`路由谓词工厂只有一个`methods`参数,该参数是一个或多个要匹配的 HTTP 方法。
```yaml
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://zysite.top
predicates:
- Method=GET,POST
```
该路由匹配请求方法为 GET 或 POST 的请求。
### Path
`Path`路由谓词工厂具有两个参数:Spring `PathMatcher` `patterns`的列表和`matchOptionalTrailingSeparator`的可选标志。
```yaml
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://zysite.top
predicates:
- Path=/red/{segment},/blue/{segment}
```
该路由匹配请求路径以`/red`或`/blue`开头的请求。如`/red/aa`、`/blue/bb`。也支持 URI 模版变量。
### Query
`Query` 路由谓词工厂具有两个参数:必需的`param`和可选的`regexp`。
```yaml
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://zysite.top
predicates:
- Query=color,gree.
```
该路由匹配包含请求参数`color`,其值与`gree.`正则匹配的请求。
### RemoteAddr
`RemoteAddr`路由谓词工厂只有一个参数:`sources`的列表(最小为 1),它是 CIDR 表示法(IPv4或IPv6)字符串,例如`192.168.0.1/16`(其中`192.168.0.1`是 IP 地址, `16`是子网掩码)。
```yaml
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://zysite.top
predicates:
- RemoteAddr=192.168.1.1/24
```
如果请求的远程地址为`192.168.1.10`、`192.168.1.100`等,则此路由将匹配。
### Weight
`Weight` 路由谓词工厂具有两个参数`group`和`weight`(一个int)。权重是按组计算的。
```yaml
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://weighthigh.org
predicates:
- Weight=group1, 8
- id: weight_low
uri: https://weightlow.org
predicates:
- Weight=group1, 2
```
此路由会将约80%的流量转发到`https://weighthigh.org`并将约20%的流量转发到`https://weightlow.org`。
## 过滤器工厂
路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路由过滤器适用于特定路由。Spring Cloud Gateway 包括许多内置的 `GatewayFilter` 工厂。下面示例几个常见的,完整的可以参考 [SpringCloud Gateway](https://docs.spring.io/spring-cloud-gateway/docs/2.2.5.RELEASE/reference/html/#gatewayfilter-factories)。
### Hystrix
[Hystrix](https://github.com/Netflix/Hystrix) 是 Netflix 的一个库,它实现了[断路器模式](https://martinfowler.com/bliki/CircuitBreaker.html) 。`Hystrix` 过滤器允许您将断路器引入网关路由,保护服务免受级联故障的影响,并允许您在下游故障的情况下提供后备响应(fallback)。
注意:要启用 Hystrix 过滤器,需引入 `spring-cloud-starter-netflix-hystrix`的依赖。
`Hystrix` 过滤器工厂只有一个必选`name`参数,它是`HystrixCommand`的名称。
```yaml
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: https://zysite.top
filters:
- Hystrix=myCommandName
```
还可以接受可选的`fallbackUri`参数。当前,仅支持`forward:`策略的URI。如果调用了 fallback ,则请求将被转发到与URI 相匹配的控制器。
```yaml
spring:
cloud:
gateway:
routes:
- id: hystrix_route
uri: lb://backing-service:8088
predicates:
- Path=/consumingserviceendpoint
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
- RewritePath=/consumingserviceendpoint, /backingserviceendpoint
```
当 Hystrix fallback 被调用时,会将请求转发到 Gateway 内部程序的`/fallback` URI 上(也可以转发到外部程序的 URI 上)。上述 `lb`前缀表示可以整合 Ribbon 的负载均衡。
其实 SpringCloud 还包括 `CircuitBreake `过滤器工厂,可以指定断路器模式的实现,如:上面的 Hystrix、Resilience4j 等。另外,还包括`FallbackHeaders`过滤器工厂,可以在 fallback 被调用是将一些信息添加到头部后在将请求转发到对应的`fallbackUri`。
信息包括:
- `executionExceptionTypeHeaderName` (`"Execution-Exception-Type"`)
- `executionExceptionMessageHeaderName` (`"Execution-Exception-Message"`)
- `rootCauseExceptionTypeHeaderName` (`"Root-Cause-Exception-Type"`)
- `rootCauseExceptionMessageHeaderName` (`"Root-Cause-Exception-Message"`)
### Prefix
`PrefixPath`过滤器工厂只有一个`prefix`参数。
```yaml
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://zysite.top
filters:
- PrefixPath=/pathprefix
```
将会把所有请求的路径添加`/pathprefix`前缀后转发,如请求 URI 为`/hello`将变为`/pathprefix/hello`。
### StripPrefix
`StripPrefix` 过滤器工厂只有一个参数`parts`。`parts`参数指示在向下游发送请求之前,要从请求路径中跳过的地址段数。
```yaml
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: https://nameservice
predicates:
- Path=/name/**
filters:
- StripPrefix=2
```
当请求地址为`/name/blue/red`时,发送到`nameservice`的地址将变为`https://nameservice/red`,即`/name/blue`这两段地址被跳过了。
### RedirectTo
`RedirectTo` 过滤器工厂包括一个`status`和一个`url`参数。状态应该是 300 系列重定向 http 代码,例如301。URL应该是有效的URL 。这将是`Location`响应头的值。
```yaml
spring:
cloud:
gateway:
routes:
- id: redirect_route
uri: https://zysite.top
filters:
- RedirectTo=302, https://spring.io
```
这将发送状态码为 302 且带有`Location:https://spring.io`头部的响应以执行重定向。
### Retry
`Retry`过滤器工厂支持以下参数:
- `retries`:尝试重试的次数
- `statuses`:应重试的 HTTP 状态码,用`org.springframework.http.HttpStatus`表示
- `methods`:应重试的 HTTP 方法,使用`org.springframework.http.HttpMethod`表示
- `series`:要重试的一系列状态码,使用`org.springframework.http.HttpStatus.Series`表示
- `exceptions`:引发重试的异常列表
- `backoff`:为重试配置了补偿指数。重试在补偿间隔`firstBackoff * (factor ^ n)`之后执行,其中`n`是迭代。如果配置了`maxBackoff`,则应用的最大补偿将被限制为`maxBackoff`。如果`basedOnPreviousValue`为true,将使用`prevBackoff * factor`计算补偿。
```yaml
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: http://localhost:8080/flakey
predicates:
- Host=*.retry.com
filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
```
### Redis RateLimiter
redis 实现基于 [Stripe](https://stripe.com/blog/rate-limiters) 所做的工作。它需要使用`spring-boot-starter-data-redis-reactive` starter 依赖。
使用的算法是`令牌桶算法`。
```yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://zysite.top
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
redis-rate-limiter.requestedTokens: 1
```
`redis-rate-limiter.replenishRate`是希望用户每秒允许多少个请求,而没有任何丢弃的请求。这是令牌桶被填充的速率。
`redis-rate-limiter.burstCapacity`是允许用户在一秒钟内执行的最大请求数。这是令牌桶可以容纳的令牌数。
`redis-rate-limiter.requestedTokens`是一个请求耗费的 token 数量,默认为1。
通过在`replenishRate`和`burstCapacity`中设置相同的值可以达到稳定的速率。通过将`burstCapacity`设置为高于`replenishRate`,可以允许临时突发。
速率限制器也可以定义为实现`RateLimiter`接口的bean。在配置中,使用SpEL通过名称引用bean。`#{@myRateLimiter}`是一个SpEL表达式,引用名称为`myRateLimiter`的bean。
```yaml
spring:
cloud:
gateway:
routes:
- id: requestratelimiter_route
uri: https://zysite.top
filters:
- name: RequestRateLimiter
args:
rate-limiter: "#{@myRateLimiter}"
key-resolver: "#{@userKeyResolver}"
```
还有很多过滤器工厂没有提到,像 添加请求头/响应头,添加/删除请求参数,删除响应头,重写请求路径,修改响应体等等。官方文档有详细解释。
## 全局过滤器(GlobalFilter)
`GlobalFilter`接口具有与`GatewayFilter`类似的含义,只不过`GatewayFilter`只针对匹配了特定路由的请求进行处理,而`GlobalFilter`则对所有请求都进行处理。
当一个请求到达 Gateway,网关Web处理程序(Gateway Web Handler)将会把`GlobalFilter`和匹配的`GatewayFilter`组成一个过滤器链(根据优先级,即 Order 接口返回的值,越小优先级越高,可以为负值)。
一个简单的全局过滤器实现如下:
```java
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("custom global filter");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return -1;
}
}
```
SpringCloud 还有一些内置的全局过滤器,如:`LoadBalancerClientFilter`、`NettyRoutingFilter`等等。可在官网或者源码`org.springframework.cloud.gateway.filter`包中查看。

SpringCloud Gateway 路由网关