Spring Cloud学习记录 06Spring Cloud Gateway 网关

Gateway 网关

Spring Cloud Gateway 网关 组件核心是一系列的过滤器,通过这些过滤器可以将客户端发送的请求转发(路由)到对 应的服务。

补充说明&优点

  • Gateway 替换了 Netflix Zuul 的一套解决方案
  • 隐藏 服务的 IP/端口 等信息
  • 提供统一的API路由管理方式 (url管理)
  • Gateway 核心功能 过滤/路由/断言
  • Gateway 也是个微服务,需要注册到 Eureka

核心功能

  • Route (路由) :路由信息由 ID、目标URL、一组断言、一组Filter 组成 (一般情况会通过断言进行判断 路由匹配)
  • Predicate (断言) :定义匹配的 HTTP 中的任何信息(如:请求头/参数等…
  • Filter (过滤器) :可在 请求/响应 进行一些业务上的处理

工作流程

说明:

  • 客户端 发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关 的微服务中进行处理,此时处理运行特定的请求过滤器链

  • 过滤器虚线分开的原因:会在发送代理请求的 请求/响应 执行逻辑(所有 pre 过滤器逻辑先执行,然后执行 代理请求;代理请求完成后,执行 post 过滤器逻辑,进行响应)

示例

代码在 01Spring Cloud 的基础上进行编辑

  1. 创建新Maven工程(无骨架)
    我创建的工程名称 gateway

  2. gateway 依赖配置 pom.xml

    <!-- gateway -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!-- eureka -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    
  3. gateway 启动类

    @SpringBootApplication
    @EnableDiscoveryClient // Eureka客户端
    public class GatewayApplication {
        public static void main(String[] args) {
            SpringApplication.run(GatewayApplication.class, args);
        }
    }
    
  4. gateway 配置 application.yml

    server :
        port : 10010
    spring :
        application :
            name : api-gateway
        cloud :
            gateway :
                routes :
                    # 路由id (随意)
                    - id : user-service-route
                      # 代理服务的地址
    #                 uri: http://localhost:9091  # 方式1 固定代理
                      uri : lb://user-service  # 方式2 动态代理
                      # 路由断言,可以配置的映射路径
                      predicates:
                        - Path=/user/**
    eureka :
        client :
            service-url :
                defaultZone : http://localhost:10086/eureka
        instance :
            # 优先应用ip,并非 host
            prefer-ip-address : true
    

    注意:

    • 填写路由断言的路径 不能存在空格 - Path=/user/**

    说明:

    • 代理路径 http://localhost:10010/user/1 ==> http://localhost:9091/user/1
    • 代理路由 有两种方式写,但一般不会写死,因此应用第二种方式
    • 路由配置中uri所用的协议为lb时,gateway 将使用 LoadBalancerClient 把 user-service 通过 eureka 解析为 实际IP 和 端口,并进行ribbon负载均衡
  5. 测试

    1. 依次打开 Eureka、server、gateway 三个服务
    2. 访问 http://localhost:10010/user/1(返回 数据代表成功)

过滤器

通过以上工作流程可得知 ,Gateway 的路由 过滤器允许 修改传入 请求/响应 的HTTP,只作用于特定的路由

过滤器生命周期

Spring Cloud Gateway 的 Filter 有两种: pre(请求被执行前调用)、post(请求被执行后调用) ,以上的工作流程可看出

使用场景

  • 请求鉴权:过滤器链式 调用到 filter()方法 前,如果发现无权限,返回Null
  • 异常处理:过滤器链式 调用到 filter()方法 后,记录异常并返回
  • 服务调用时长统计:过滤器链式 调用到 filter()方法 前后,根据时长统计

常用过滤器工厂

Gateway 中有很多内置的过滤器工厂,我们只需了解常用的过滤器

过滤器工厂名称说明
AddRequestHeader对匹配上的请求加上Header(请求头)
RemoveRequestHeader对匹配上的请求去除Header(请求头)
AddRequestParameter对匹配上的请求路由添加参数
SetStatus设置 HTTP请求 的响应码
AddResponseHeader对从网关返回的响应添加Header(响应头)
StripPrefix对匹配上的请求路径去除前缀
PrefixPath对匹配上的请求路径添加前缀

更多详细参考 官网链接

过滤器示例

AddRequestHeader 添加请求头

符合规则匹配成功的请求,将添加 X-Request-Foo:bar 请求头,将其传递到后端服务中

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                - id : user-service-route
                  uri : lb://user-service
                  predicates:
                      - Path=/user/**
                  filters:
                  	  # 参数:请求头,值
					  - AddRequestHeader=X-Request-Foo,Bar

RemoveRequestHeader 移除请求头

可以在请求转发到后端服务之前进行 Header 的移除操作

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                - id : user-service-route
                  uri : lb://user-service
                  predicates:
                      - Path=/user/**
                  filters:
                  	  # 参数:移除请求头 X-Request-Foo
					  - RemoveRequestHeader=X-Request-Foo

AddRequestParameter 请求添加参数

符合规则匹配成功的请求,将会附加 MyName=Sans 参数

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                - id : user-service-route
                  uri : lb://user-service
                  predicates:
                      - Path=/user/**
                  filters:
                  	  # 参数:名称,值 (类似于 K , V)
					  - AddRequestParameter=MyName,Sans

SetStatus 响应返回码

用于设置 HTTP请求 的响应码 ; 支持 整数/枚举 (org.springframework.http.HttpStatus类 查看枚举)

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                - id : user-service-route
                  uri : lb://user-service
                  predicates:
                      - Path=/user/**
                  filters:
					  - SetStatus=401

AddResponseHeader 添加响应头

网关响应添加响应头

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
        	default-filters:
        		- AddResponseHeader=X-Request-Foo,Bar

StripPrefix 去除路由前缀

对请求地址前缀去除后,再作为 代理地址

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                # 路由id (随意)
                - id : user-service-route
                  # 代理服务的地址
#                  uri: http://localhost:9091  # 方式1
                  uri : lb://user-service  # 方式2
                  # 路由断言,可以配置的映射路径
                  predicates:
                    - Path=/api/user/**
                  filters:
                    - StripPrefix=1
···

​ 如: http://localhost:10010/api/user/1 ==> http://localhost:9091/user/1 (去除/api)

PrefixPath 添加路由前缀

对请求地址添加前缀后,再作为 代理地址

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            routes :
                # 路由id (随意)
                - id : user-service-route
                  # 代理服务的地址
#                  uri: http://localhost:9091  # 方式1
                  uri : lb://user-service  # 方式2
                  # 路由断言,可以配置的映射路径
                  predicates:
                    - Path=/**
                  filters:
                    - PrefixPath=/user
···

​ 如: http://localhost:10010/1 ==> http://localhost:9091/user/1 (添加/user)

测试可自行在以下的自定义过滤器工厂测试检查属性变化

自定义局部过滤器

Spring Cloud Gateway 过滤器工厂 ,在请求传递的过程中,对请求进行 编辑/验证。(应用场景一般有:身份验证、选择集群中指定的微服务 等…

示例

说明:

  • 自定义过滤器工厂需要继承 AbstractGatewayFilterFactory类,重写 apply()方法 , 且过滤器类命名以 GatewayFilterFactory 为后缀
  • 测试 请求参数 在控制台展示
  1. 创建 自定义局部过滤器 (我创建的MyParamGatewayFilterFactory

    package com.sans.gateway.filter;
    
    import org.springframework.cloud.gateway.filter.GatewayFilter;
    import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
    import org.springframework.http.server.reactive.ServerHttpRequest;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    import java.util.List;
    
    @Component
    public class MyParamGatewayFilterFactory extends AbstractGatewayFilterFactory<MyParamGatewayFilterFactory.Config> {
        
        /**构造函数*/
        public MyParamGatewayFilterFactory() {
            super(Config.class);
        }
        /** 初始化
         * 读取配置文件中的参数,赋值到配置类中
         * */
        @Override
        public List<String> shortcutFieldOrder() {
            //一个参数
            return Arrays.asList("params");
        }
        
        /**过滤器逻辑*/
        @Override
        public GatewayFilter apply(Config config) {
            // 实例化 GatewayFilter.filter()
            //      exchange - 请求属性
            //      chain - 提供一种委托给下一个过滤器的方法
            return (exchange , chain) -> {
                // 前置
                ServerHttpRequest request = exchange.getRequest();
                // 路由包含 name参数 则执行
                if (request.getQueryParams().containsKey(config.params)) {
                    request.getQueryParams().get(config.params).forEach((v)->{
                        System.out.printf("局部过滤器 => %s = %s\n",config.params,v);
                    });
                }
        
        /**读取过滤器配置的参数 (以对象形式存储*/
        public static class Config {
            // 对应配置在 application.yml 配置文件中的过滤参数
            private String params;
        
            public String getParams() {
                return params;
            }
        
            public void setParams(String params) {
                this.params = params;
            }
        }
        
        
    }
    
  2. 配置 application.yml 添加 自定义过滤器

    spring :
        cloud :
            gateway :
                routes :
                    # 路由id (随意)
                    - id : user-service-route
                      # 代理服务的地址
    #                  uri: http://localhost:9091  # 方式1
                      uri : lb://user-service  # 方式2
                      # 路由断言,可以配置的映射路径
                      predicates:
                          - Path=/user/**
                      filters:
                          # 添加自定义过滤器
                          - MyParam=name
    

    说明:

    • 在配置中 过滤器指定可省略 GatewayFilterFactory 后缀
    • 以上对应参数是 shortcutFieldOrder()方法 返回指定参数数量的List (以上我指定返回一个参数params 的List)
  3. 测试

    1. 依次打开 Eureka、server、gateway 三个服务
    2. 访问 http://localhost:10010/user/1?name=Sans

    访问后,控制台会打印,若参数名name不匹配,则不打印

自定义全局过滤器

全局过滤器作用于所有路由, 无需其他配置!!!一般情况用于权限统一验证,安全验证等功能…

示例

说明:

  • 全局过滤器 需要实现 GlobalFilter接口
  • 过滤器顺序 需要实现 Ordered接口 指定数值(值越小越快
  • 测试 请求是否授权 。判断是否包含 token 的请求头
  1. 创建 自定义全局过滤器 (我创建的MyGlobalFilter

    package com.sans.gateway.filter;
    
    import org.apache.commons.lang.StringUtils;
    import org.springframework.cloud.gateway.filter.GatewayFilterChain;
    import org.springframework.cloud.gateway.filter.GlobalFilter;
    import org.springframework.core.Ordered;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    import org.springframework.web.server.ServerWebExchange;
    import reactor.core.publisher.Mono;
    
    @Component
    public class MyGlobalFilter implements GlobalFilter, Ordered {
        
        @Override
        public Mono<Void> filter(ServerWebExchange exchange , GatewayFilterChain chain) {
            System.out.println("=================全局过滤器MyGlobalFilter=================");
            
            String token = exchange.getRequest().getHeaders().getFirst("token");
            if (StringUtils.isBlank(token)) {
                // 设置响应状态码为未授权 401
                exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
                return exchange.getResponse().setComplete();
            }
            
            return chain.filter(exchange);
        }
        
        /**设置过滤器优先顺序*/
        @Override
        public int getOrder() {
            return 1;
        }
    }
    
  2. 测试 (由于需要为请求添加请求头)

    1. 依次打开 Eureka、server、gateway 三个服务
    2. 通过 以下的IDEA内置的HTTP测试 以下有示例

    说明: 请求成功会 返回数据 ;请求失败会返回401 (未授权)

IDEA内置HTTP调试

  1. 创建测试 HTTP请求 草稿文件

  2. 设置请求url测试

    有示例查看使用方式等功能…

跨域配置

跨域:当一个请求URL的 协议/域名/端口 之间任意一个与当前页面URL不同,称为跨域

当前 URL被请求URL是否跨域原因比较
http://www.test.com/http://www.test.com/index.htmlN--
http://www.test.com/https://www.test.com/index.htmlY协议不同http/https
http://www.test.com/http://www.baidu.com/Y主域名不同test/baidu
http://www.test.com/http://blog.test.com/Y子域名不同www/blog
http://www.test.com:8080/http://www.test.com:7001/Y端口号不同8080/7001

这一跨域问题可通过 Gateway 网关服务器中配置即可。配置如下:

spring :
    application :
        name : api-gateway
    cloud :
        gateway :
            globalcors :
                cors-configurations :
                    # 代表所有访问到网关服务器上的地址
                    '[/**]' :
                    	# 允许指定服务器地址访问 
#                        allowedOrigins : *     # *代表全部支持访问
                        allowedOrigins :
                            - "http://demo.sans.top"
                        allowedMethods :
                            - GET

上述配置 允许来自 http://demo.sans.top 的 GET请求 获取服务数据

官方具体了解:CORS Configuration (spring.io)

负载均衡&熔断器

Gateway 默认集成 Ribbon、Hystrix (配置策略也如此)。如有其它策略配置可点击一下链接了解相关配置

Gateway与Feign的区别

GatewayFeign
接口入口统一服务入口单一微服务入口
接口安全接口隐蔽应用接口暴露应用
路由处理可以处理固有。不能处理
应用场景权限检查、控制流量微服务内部调用更方便

仓库代码 : https://gitee.com/Sanscan12/spring-cloud-examples.git


版权声明:本文为weixin_45963193原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。