提供了一个构建在 Spring 生态系统之上的 API 网关,包括:Spring 6、Spring Boot 3 和 Project Reactor。Spring Cloud Gateway 旨在提供一种简单而有效的方法来路由到 API 并为其提供横切关注点,例如:安全性、监控/指标和弹性。
官网:https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/
1. 工作原理
客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射确定请求与路由匹配,则会将其发送到网关 Web 处理程序。该处理程序通过特定于请求的过滤器链运行请求。过滤器被虚线分开的原因是过滤器可以在发送代理请求之前和之后运行逻辑。执行所有“预”过滤器逻辑。然后发出代理请求。发出代理请求后,将运行“post”过滤器逻辑。
2. 微服务中网关的位置
3. 官网的作用
- 反向代理
- 鉴权
- 流量控制
- 熔断
- 日志监控
4. 三大核心
- 路由:网关的基本构建块。它由 ID、目标 URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。
- 断言:这是一个Java 8 函数谓词。输入类型是Spring FrameworkServerWebExchange。这使您可以匹配 HTTP 请求中的任何内容,例如标头或参数。
- 过滤:这些是GatewayFilter使用特定工厂构建的实例。在这里,您可以在发送下游请求之前或之后修改请求和响应。
5. 入门案例
5.1 创建SpringBoot工程
5.2 添加pom文件
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
5.3 添加 yml 内容
spring:
application:
name: cloud-gateway #以微服务注册进consul或nacos服务列表内
cloud:
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service # lb:服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
5.4 修改启动类
@SpringBootApplication
@EnableDiscoveryClient //服务注册和发现
public class Main9527
{
public static void main(String[] args)
{
SpringApplication.run(Main9527.class,args);
}
}
5.5 测试
以上一个简单的 gateway 网关服务搭建完成。启动服务,访问 http://localhost:9527/pay/gateway/get/1
。即可访问到cloud-payment-service服务的方法。
6. 核心特性
接下来,我们对 gateway 的三大核心进行详细讲解。
6.1 路由 Route-URI
在 yml 配置文件中 uri 属性,在日常应用中都不会写死 ip:port,这样无法做到动态获取,而是配合注册中心使用,如:
cloud:
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: lb://cloud-payment-service # lb:服务名
predicates:
- Path=/pay/gateway/get/** # 断言,路径相匹配的进行路由
在案例中,uri 写的是 lb://注册到注册中心的微服务名
6.2 断言 Predicate
6.2.1 断言配置方式
配置断言有两种方式:快捷方式(Shortcut Configuration)和完全扩展的参数(Fully Expanded Arguments)。
- 快捷方式:配置由过滤器名称识别,后跟等号 ( =),然后是用逗号 ( ,) 分隔的参数值。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- Cookie=mycookie,mycookievalue
Cookie使用两个参数定义 Route Predicate Factory:cookie 名称mycookie和要匹配的值mycookievalue
- 完全扩展的参数看起来更像是带有名称/值对的标准 yaml 配置。通常,会有name和args关键字。键args是用于配置谓词或过滤器的键值对的映射。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- name: Cookie
args:
name: mycookie
regexp: mycookievalue
6.2.2 内置的断言
- The After Route Predicate Factory:匹配指定日期时间之后发生的请求
spring:
cloud:
gateway:
routes:
- id: after_route
uri: https://example.org
predicates:
- After=2024-03-27T20:45:49.677486+08:00[Asia/Shanghai]
时间获取方式:ZonedDateTime zbj = ZonedDateTime.now(); // 默认时区
- The Before Route Predicate Factory:匹配在指定的之前发生的请求
spring:
cloud:
gateway:
routes:
- id: before_route
uri: https://example.org
predicates:
- Before=2024-03-27T20:45:49.677486+08:00[Asia/Shanghai]
- The Between Route Predicate Factory:匹配指定两个日期之间发生的请求
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
- The Cookie Route Predicate Factory:匹配具有给定名称且其值与正则表达式匹配的 cookie
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: https://example.org
predicates:
- Cookie=chocolate, ch.p
匹配带 cookie 且 cookie 的名称为chocolate,cookie 的值为ch.p 的请求
- The Header Route Predicate Factory:与具有给定名称且其值与正则表达式匹配的标头匹配
spring:
cloud:
gateway:
routes:
- id: header_route
uri: https://example.org
predicates:
- Header=X-Request-Id, \d+
如果请求的请求头中具有名为 X-Request-Id 其值与 \d+ 正则表达式匹配的标头(即,它具有一位或多位数字的值),则此路由匹配
- The Host Route Predicate Factory:匹配参数中的主机地址
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://example.org
predicates:
- Host=**.somehost.org,**.anotherhost.org
若请求是 curl http://localhost:9527/pay/gateway/get/3 -H "Host:java.somehost.org"
则请求匹配。
- The Method Route Predicate Factory:匹配请求的 http 方法
spring:
cloud:
gateway:
routes:
- id: method_route
uri: https://example.org
predicates:
- Method=GET,POST
- The Path Route Predicate Factory:匹配请求路径
spring:
cloud:
gateway:
routes:
- id: path_route
uri: https://example.org
predicates:
- Path=/red/{segment},/blue/{segment}
若请求地址是:/red/1 则匹配
这要是我们常用的匹配方式
- The Query Route Predicate Factory:匹配请求参数
spring:
cloud:
gateway:
routes:
- id: query_route
uri: https://example.org
predicates:
- Query=username, \d+
参数名username并且值还要是整数才能路由
- The RemoteAddr Route Predicate Factory:匹配 ip 地址
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: https://example.org
predicates:
- RemoteAddr=192.168.1.1/24
部访问我的IP限制,最大跨度不超过32,目前是1~24它们是 CIDR 表示法
大白话翻译:如 192.168.31.1/24 此 ip 一共 32 位,用符号.间隔,每个间隔是 8 位,后面的/24表示前 24 位不变,也就是 ip 范围是 192.168.31.1~192.168.31.255
- The Weight Route Predicate Factory:按照权重转发流量
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% 的流量转发到Weighthigh.org,将约 20% 的流量转发到Weightlow.org
- The XForwarded Remote Addr Route Predicate Factory:根据 HTTP 标头过滤请求X-Forwarded-For
spring:
cloud:
gateway:
routes:
- id: xforwarded_remoteaddr_route
uri: https://example.org
predicates:
- XForwardedRemoteAddr=192.168.1.1/24
若请求头包含 X-Forwarded-For 且 值为192.168.1.1/24,则匹配
6.2.3 自定义断言
6.2.3.1 自定义断言方式
- 继承AbstractRoutePredicateFactory
- 实现RoutePredicateFactory接口
- 自定义断言类型必须以RoutePredicateFactory结尾
6.2.3.2 自定义断言步骤
- 新建类,名称以RoutePredicateFactory后缀,并且继承AbstractRoutePredicateFactory
- 重写 apply 方法
- 新建 apply 方法需要的静态内部类Config
- 新增空参构造,调用 super
- 重写shortcutFieldOrder方法,为了支持快捷方式
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config>
{
public MyRoutePredicateFactory()
{
super(MyRoutePredicateFactory.Config.class);
}
@Validated
public static class Config{
@Setter
@Getter
@NotEmpty
private String userType; //钻、金、银等用户等级
}
@Override
public Predicate<ServerWebExchange> apply(MyRoutePredicateFactory.Config config)
{
return new Predicate<ServerWebExchange>()
{
@Override
public boolean test(ServerWebExchange serverWebExchange)
{
//检查request的参数里面,userType是否为指定的值,符合配置就通过
String userType = serverWebExchange.getRequest().getQueryParams().getFirst("userType");
if (userType == null) return false;
//如果说参数存在,就和config的数据进行比较
if(userType.equals(config.getUserType())) {
return true;
}
return false;
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("userType");
}
}
使用:
spring:
cloud:
gateway:
routes:
- id: mytest
uri: https://example.org
predicates:
- My=diamond #快捷写法
#- name: My # 完全扩展写法
# args:
# userType: diamond
6.3 过滤器 Filter
路由过滤器允许以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。路由过滤器的范围仅限于特定路由。Spring Cloud Gateway 包含许多内置的 GatewayFilter Factory。
只挑选一部分讲解,其他的可以参考官网
https://docs.spring.io/spring-cloud-gateway/docs/4.0.4/reference/html/#gatewayfilter-factories
gateway 内置了很多过滤器,大概可以分成几大类:请求头相关、请求参数相关、相应头相关、前缀和路径相关、其他。
6.3.1 请求头相关
- The AddRequestHeader GatewayFilter Factory
在请求头添加参数:
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
filters:
- AddRequestHeader=X-Request-red, blue
也可以使用请求参数
spring:
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://example.org
predicates:
- Path=/red/{segment}
filters:
- AddRequestHeader=X-Request-Red, Blue-{segment}
- The RemoveRequestHeader GatewayFilter Factory
删除请求头中的参数
spring:
cloud:
gateway:
routes:
- id: removerequestheader_route
uri: https://example.org
filters:
- RemoveRequestHeader=X-Request-Foo
- The SetRequestHeader GatewayFilter Factory
将请求头中的参数值更新
spring:
cloud:
gateway:
routes:
- id: setrequestheader_route
uri: https://example.org
filters:
- SetRequestHeader=X-Request-Red, Blue
也可以使用请求参数
spring:
cloud:
gateway:
routes:
- id: setrequestheader_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- SetRequestHeader=foo, bar-{segment}
6.3.2 请求参数相关
- The AddRequestParameter GatewayFilter Factory
添加请求参数
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
filters:
- AddRequestParameter=red, blue
也可以设置请求的其他参数或者host 中的参数
spring:
cloud:
gateway:
routes:
- id: add_request_parameter_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- AddRequestParameter=foo, bar-{segment}
- The RemoveRequestParameter GatewayFilter Factory
删除请求参数
spring:
cloud:
gateway:
routes:
- id: removerequestparameter_route
uri: https://example.org
filters:
- RemoveRequestParameter=red
6.3.3 相应头相关
- The AddResponseHeader GatewayFilter Factory
添加响应头参数
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
filters:
- AddResponseHeader=X-Response-Red, Blue
也可以使用响应头其他的参数
spring:
cloud:
gateway:
routes:
- id: add_response_header_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- AddResponseHeader=foo, bar-{segment}
- The RemoveResponseHeader GatewayFilter Factory
删除响应头参数
spring:
cloud:
gateway:
routes:
- id: removeresponseheader_route
uri: https://example.org
filters:
- RemoveResponseHeader=X-Response-Foo
- The SetResponseHeader GatewayFilter Factory
设置相应头参数
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: https://example.org
filters:
- SetResponseHeader=X-Response-Red, Blue
spring:
cloud:
gateway:
routes:
- id: setresponseheader_route
uri: https://example.org
predicates:
- Host: {segment}.myhost.org
filters:
- SetResponseHeader=foo, bar-{segment}
6.3.4 前缀和路径相关
- The PrefixPath GatewayFilter Factory:添加路径前缀
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- PrefixPath=/mypath
若请求是/hello则发送至/mypath/hello
2. The RedirectTo GatewayFilter Factory:重定向
spring:
cloud:
gateway:
routes:
- id: prefixpath_route
uri: https://example.org
filters:
- RedirectTo=302, https://acme.org
请求执行 302 重定向到https://acme.org,status 必须是 3xx
- The SetPath GatewayFilter Factory:访问路径修改
spring:
cloud:
gateway:
routes:
- id: setpath_route
uri: https://example.org
predicates:
- Path=/red/{segment}
filters:
- SetPath=/{segment}
对于的请求路径/red/blue,在发出下游请求之前将路径设置为/blue
6.3.5 其他
- Default Filters:默认过滤自动应用于所有路由
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
以上配置说明:所有请求的相应头中添加X-Response-Default-Red的键,值为Default-Blue;所有请求的路径前缀添加/httpbin
6.3.6 自定义过滤器
6.3.6.1 自定义全局过滤器
实现GlobalFilter接口,重写filter方法即可。
@Component
@Slf4j
public class MyGlobalFilter implements GlobalFilter, Ordered
{
/**
* 数字越小优先级越高
* @return
*/
@Override
public int getOrder()
{
return 0;
}
private static final String BEGIN_VISIT_TIME = "begin_visit_time";//开始访问时间
/**
* @param exchange
* @param chain
* @return
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//先记录下访问接口的开始时间
exchange.getAttributes().put(BEGIN_VISIT_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(()->{
Long beginVisitTime = exchange.getAttribute(BEGIN_VISIT_TIME);
if (beginVisitTime != null){
log.info("访问接口主机: " + exchange.getRequest().getURI().getHost());
log.info("访问接口端口: " + exchange.getRequest().getURI().getPort());
log.info("访问接口URL: " + exchange.getRequest().getURI().getPath());
log.info("访问接口URL参数: " + exchange.getRequest().getURI().getRawQuery());
log.info("访问接口时长: " + (System.currentTimeMillis() - beginVisitTime) + "ms");
log.info("我是美丽分割线: ###################################################");
System.out.println();
}
}));
}
}
6.3.6.2 自定义条件过滤器
- 继承AbstractGatewayFilterFactory类
- 重写apply方法
- 重写shortcutFieldOrder方法
- 定义内部静态类Config
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config>
{
public MyGatewayFilterFactory()
{
super(MyGatewayFilterFactory.Config.class);
}
@Override
public GatewayFilter apply(MyGatewayFilterFactory.Config config)
{
return new GatewayFilter()
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
ServerHttpRequest request = exchange.getRequest();
System.out.println("进入了自定义网关过滤器MyGatewayFilterFactory,status:"+config.getStatus());// abc
if(request.getQueryParams().containsKey("zhang")){
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
return exchange.getResponse().setComplete();
}
}
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("status");
}
public static class Config
{
@Getter@Setter
private String status;//设定一个状态值/标志位,它等于多少,匹配和才可以访问
}
}
使用:
spring:
cloud:
gateway:
routes:
- id: customFilter
uri: https://example.org
filters:
- My=abc
#- name:My # 全名称
# args:
# status:abc
请求:http://ip:port/xxx?zhang=abc