原创

Spring Cloud Gateway

温馨提示:
本文最后更新于 2023年12月24日,已超过 396 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

关于Spring Cloud Gateway

网关作为流量的入口,常用的功能包括路由转发、鉴权、限流、日志监控等等;
Spring Cloud Gateway 是Sping Cloud官方推出的第二代网关框架,基于WebFlux + Netty + Reactor 实现的响应式API网关,目的是替代Netfix Zuul

官方出过一个测试项目,创建了一个 Benchmark 的测试项目,如下:

Proxy Avg Latency Avg Req/Sec/Thread
gateway 6.61ms 3.24k
linkered 7.62ms 2.82k
zuul 12.56ms 2.09k
none 2.09ms 11.77k

Spring Cloud Gateway 功能特征

  • 基于Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建
  • 动态路由:能够匹配任何请求属性
  • 可以集成 Spring Cloud 服务发现功能
  • 可以对路由指定 Predicate(断言)Filter(过滤器)
  • 可以集成 HystrixSentinel 流控降级功能
  • 支持路径重写

核心概念

Route(路由)

路由是网关的基本构件。它包括ID、目标URI、断言工厂和过滤器集合定义。如果断言为真,则匹配路由

Predicate(断言)

参照Java8的新特性Predicate。允许开发人员匹配HTTP请求中的任何内容,比如头或参数

Filter(过滤器)

过滤器分为 Gateway FilterGlobal Filter,可以对请求和响应前后进行自定义处理逻辑

核心流程图如下如所示:
核心流程图

  • 图片来源于网络,侵权请联系删除

快速开始

创建服务并添加Gateway依赖

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

编写路由配置

路由的配置包括:

路由id:路由的唯一标识
路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
路由断言(predicates):判断请求是否符合路由规则,符合则进行转发
路由过滤器(filters):对请求或响应做处理

server:
    port:8080

spring:
    application:
        name: gateway
    cloud:
        nacos:
            server-addr: localhost:8848 #nacos地址
        gateway:
            routes: #网关路由配置
                - id: blog-service #路由id,自定义,唯一即可
                  uri: lb://blogservice #路由的目标地址, lb为负载均衡(loadBalance简写),后面时服务名称
                  # uri: http://127.0.0.1:8081 #路由的具体目标地址
                  predicates: #路由断言,也就是判断请求是否符合路由规则的条件
                      - Path=/blog/** #这个是按照路径匹配,只要以 /blog/ 开头就符合要求
                  filters: #过滤器
                      - AddrequestHeader=test
            default-filters: #给所有的请求都加上当前过滤器,即全局过滤器
                - AddrequestHeader=test

路由断言工厂Route Predicate Factory

Spring提供的路由断言工厂

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,匹配成功则进行路由转发,匹配失败则返回404

例如Path=/blog/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRouthPredicateFactory 类来处理的

Spring提供像这样的断言工厂在SpringCloudGateway还有十几个,如下:

名称 说明 示例
After 某个时间点后的请求 - After=2037-01-20T15:00:00.710-07:00[America/Denver]
Before 某个时间点之前的请求 - Before=2037-01-20T15:00:00.710+08:00[Asia/Shanghai]
Between 某两个时间点之间的请求 - Between=2037-01-20T15:00:00.710+08:00[Asia/Shanghai],2037-03-20T15:00:00.710+08:00[Asia/Shanghai]
Cookie 请求必须包含某些Cookie - Cookie=chocolkate, ch.p
Header 请求必须包含某header - Header=X-Request-Id,\d+
Host 请求必须是访问某个host(域名) - Host=** .somehost.org, **.anotherhost.org
Method 请求方式必须是指定方式 - Method=GET,POST
Path 请求路径必须符合指定规则 - Path=/red/{segment},/blue/**
Query 请求参数必须包含指定参数 - Query=name,Jack 或者 - Query=name
RemoteAddr 请求者的ip必须是指定范围 - RemoteAddr=192.168.1.1/24
Weight 权重处理 -Weight=group, 8

自定义路由断言工厂

自定义路由断言工厂要继承 AbstractRoutePredicateFactory 类,重新 apply 方法的逻辑。在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerWebExchange 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

必须是 Spring 组件 Bean
必须以RoutePredicateFactory为结尾
必须继承 AbstractRoutePredicateFactory
必须声明 Config 静态内部类,并声明属性来接收配置文件中配置信息

示例:

@Component
public class CheckRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckRoutePredicateFactory.Config> implements Serializable {
    private static final long serialVersionUID = 1232557711467284260L;
    private static final String NAME = "name";
    private static final String VALUE = "value";

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

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

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                return Objects.equals("test", config.getName()) && Objects.equals("test", config.getValue());
            }

            @Override
            public Object getConfig() {
                return config;
            }

            @Override
            public String toString() {
                return String.format("Name: %s, Value: %b", config.getName(), config.getValue());
            }
        };
    }

    @Validated
    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;
        }
    }
}

添加自定义断言配置

- Check=test,test

路由过滤器 GatewayFilter

Spring提供的路由过滤器

GatewayFilter是网关提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

过滤器工厂 GatewayFilterFactory

Spring提供了31种不同的过滤器工厂,例如:

名称 说明
AddRequestHeader 给当前请求添加一个请求头
RemoveRequestHeader 移除请求中的一个请求头
AddResponseHeader 给响应结果中添加一个响应头
RemoveResponseHeader 从响应结果中移除一个响应头
RequestRateLimiter 限制请求的流量
.... ....

自定义路由过滤器

继承 AbstractGatewayFilterFactory 且我们的自定义名称必须要以 GatewayFilterFactory 结尾并交给Spring管理;
示例:请看文章限流算法之令牌桶

全局过滤器: GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑要自己写代码实现。

定义方式是实现GlobalFilter接口:

public interface GlobalFilter {
    /**
    * 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
    * @param exchange 请求上下文,里面可以获取Request、Resopnse等信息
    * @param chain 用来把请求委托给下一个过滤器
    * @return {@code Mono<Void>} 返回表示当前过滤器业务结束
    */
    Mono<void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

示例:定义全局过滤器,拦截并判断用户身份

需求:定义全局过滤器,拦截请求,判断请求的参数书否同时满足下面条件:
1.参数中是否有authorization
2.authorization参数值是否为admin

@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String, String> params = request.getQueryParams();
        // 2.获取请求参数中的 authorization参数
        String auth = params.getFirst("authorization");
        // 3.判断参数值是否符合条件
        if(Objects.equals("admin", auth)){
            // 放行,进行下一个过滤器
            return chain.filter(exchange)                    ;
        }
        // 否,拦截
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        return exchange.getResponse().setComplete();
    }
}

过滤器的执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter合并到一个过滤器链(集合GatewayFilter)中,排序后依次执行每个过滤器

1.每个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
2.GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
3.路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
4.当过滤器的order值一样的时候,会按照以下顺序执行:

默认过滤器 --> 路由过滤器 --> 全局过滤器
可以参考下面几个类的源码来看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters() //方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()//方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组成过滤器链

跨域问题处理

浏览器禁止请求的发起者与服务端发生跨域(域名或者端口不一致导致跨域问题)ajax请求
解决方案:CORS

spring:
    cloud:
        gateway:
            globalcors: #全局的跨域处理
                add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
                corsConfigurations:
                    '[/**]': #拦截一切请求
                        allowedOrigins: #允许哪些网站的跨域请求
                            - "http://localhost:8888"
                            - "https://www.baidu.com"
                        allowedMethods: #允许的跨域ajax的请求方式
                            - "GET"
                            - "POST"
                            - "DELETE"
                            - "PUT"
                            - "OPTIONS"
                        allowedHeaders: "*" # 允许在请求中携带的头信息
                        allowCredentials: true # 是否允许携带cookie
                        maxAge: 360000 #这次跨域检测的有效期
正文到此结束
本文目录