SpringGateway网关
作者:mmseoamin日期:2023-12-25

SpringGateway

  • SpringGateway网关
    • 奈非框架简介
    • 什么是网关
    • Spring Gateway简介
    • 为什么选择 Gateway
      • Gateway 的特性
      • Gateway 与 Zuul 的区别
      • Gateway 的三大核心概念
        • Route (路由)
        • Predicate (断言)
        • Filter (过滤)
        • 总结
        • Gateway 的工作流程
        • 简单网关演示
        • 网关多路由配置
        • Gateway配置路由的两种方式
        • 动态路由
        • 内置断言
          • **时间相关**
          • **要求指定参数的请求**
          • **带 Cookie 参数的断言**
          • 带 Header 参数的断言
          • Host 断言
          • Method 断言
          • 内置过滤器
            • AddRequestParameter过滤器
            • 自定义过滤器
            • 路由配置的设计规则
            • csmall项目网关
              • 网关项目的knife4j配置
              • Gateway和SpringMvc依赖冲突问题和解决

                SpringGateway网关

                奈非框架简介

                早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎

                这些框架和SpringCloud Alibaba的对应关系我们要了解

                现在还有很多旧项目维护是使用奈非框架完成的微服务架构

                Nacos对应Eureka都是注册中心

                Dubbo对应Ribbon+feign都是实现微服务远程RPC调用的组件

                Sentinel对应Hystrix都是做项目限流熔断降级的组件

                SpringGateway对应Zuul都是网关组件

                Gateway框架不是阿里写的,是Spring提供的

                什么是网关

                "网"指网络,"关"指关口或关卡

                网关:就是指网络中的关口\关卡

                网关就是当前微服务项目的"统一入口"

                程序中的网关就是当前微服务项目对外界开放的统一入口

                所有外界的请求都需要先经过网关才能访问到我们的程序

                提供了统一入口之后,方便对所有请求进行统一的检查和管理

                SpringGateway网关,在这里插入图片描述,第1张

                网关的主要功能有

                • 将所有请求统一经过网关
                • 网关可以对这些请求进行检查
                • 网关方便记录所有请求的日志
                • 网关可以统一将所有请求路由到正确的模块\服务上

                  “路由"的近义词就是"分配”

                  Spring Gateway简介

                  我们使用Spring Gateway作为当前项目的网关框架

                  Spring Gateway是Spring自己编写的,也是SpringCloud中的组件

                  SpringGateway网关,在这里插入图片描述,第2张

                  SpringGateway网关,在这里插入图片描述,第3张

                  SpringGateway网关,在这里插入图片描述,第4张

                  Spring Gateway官网

                  https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

                  网关实例项目git地址

                  https://gitee.com/jtzhanghl/gateway-demo.git

                  为什么选择 Gateway

                  SpringGateway网关,在这里插入图片描述,第5张

                  Gateway 的特性

                  SpringGateway网关,在这里插入图片描述,第6张

                  Gateway 与 Zuul 的区别

                  SpringGateway网关,在这里插入图片描述,第7张

                  SpringGateway网关,在这里插入图片描述,第8张

                  Gateway 的三大核心概念

                  Route (路由)

                  SpringGateway网关,在这里插入图片描述,第9张

                  Predicate (断言)

                  SpringGateway网关,在这里插入图片描述,第10张

                  Filter (过滤)

                  SpringGateway网关,在这里插入图片描述,第11张

                  总结

                  SpringGateway网关,在这里插入图片描述,第12张

                  Gateway 的工作流程

                  SpringGateway网关,在这里插入图片描述,第13张

                  SpringGateway网关,在这里插入图片描述,第14张

                  SpringGateway网关,在这里插入图片描述,第15张

                  简单网关演示

                  SpringGateway网关是一个依赖,不是一个软件

                  所以我们要使用它的话,必须先创建一个SpringBoot项目

                  这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分

                  beijing和shanghai是编写好的两个项目

                  gateway项目就是网关项目,需要添加相关配置

                  
                      
                      
                          org.springframework.cloud
                          spring-cloud-starter-gateway
                      
                      
                      
                          com.alibaba.cloud
                          spring-cloud-starter-alibaba-nacos-discovery
                      
                      
                      
                          org.springframework.cloud
                          spring-cloud-starter-loadbalancer
                      
                  
                  

                  我们从yml文件配置开始添加

                  server:
                    port: 9000
                  spring:
                    application:
                      name: gateway
                    cloud:
                      nacos:
                        discovery:
                          server-addr: localhost:8848
                      gateway:
                        # route就是路由的意思,下面就是配置路由信息
                        # 一个网关项目大多会配置很多路由
                        # 所以这个网关配置是一个List集合类型
                        routes:
                          # List类型元素赋值时,每个元素都要以"-"开头,在这个"-"之后,
                          # 编写的所有内容,都是同一个对象的属性值
                          # id设置当前路由的名称,也是唯一标识,和其它配置没有对应关系,注意不能和之后的id名称重复即可
                          - id: gateway-beijing
                            # uri属性配置的是路由目标服务器的名称,"beijing"指注册到Nacos名称为"beijing"的模块
                            # lb就是负载均衡LoadBalance的缩写,标识路由支持负载均衡
                            uri: lb://beijing
                            # predicate是断言的意思,断言指某些条件满足时,执行某些操作
                            # predicates配置也是一个List类型的属性,所以它赋值也要以"-"开头
                            predicates:
                              # 下面是断言的内容,Path表示判断路径,"/bj/**"表示判断路径是否以"/bj/"开头
                              # 当断言条件满足时,就会按上面uri的配置,路由到该服务器模块
                              # ↓   P是大写的!!!!!
                              - Path=/bj/**
                  

                  先启动nacos

                  再启动beijing

                  最后启动gateway

                  网关多路由配置

                  上面只配置了一个beijing的路由设置

                  下面我们修改yml文件也实现shanghai的路由设置

                  gateway:
                    # route就是路由的意思,下面就是配置路由信息
                    # 一个网关项目大多会配置很多路由
                    # 所以这个网关配置是一个List集合类型
                    routes:
                      - id: gateway-shanghai
                        uri: lb://shanghai
                        predicates: 
                          - Path=/sh/**
                     # beijing配置略
                  

                  在保证nacos启动的情况下

                  beijing服务器如果启动无需重启

                  启动shanghai项目

                  最后重启网关

                  测试网关路由到两个模块的效果

                  http://localhost:9000/bj/show可以访问beijing服务器的资源

                  http://localhost:9000/sh/show可以访问shanghai服务器的资源

                  以此类推,再有很多服务器时,我们都可以仅使用9000端口号来将请求路由到正确的服务器

                  就实现了gateway成为项目的统一入口的效果

                  Gateway配置路由的两种方式

                  • 在配置文件yml中配置
                  • 代码中注入RouteLocator的Bean

                    以第二种为例, 业务需求 - 通过端口为9527网关访问到百度网址

                    百度国内新闻网址,需要外网 - http://baidu.com

                    编码: 创建 config.GateWayConfig 类

                    SpringGateway网关,在这里插入图片描述,第16张

                    package com.atguigu.springcloud.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 GateWayConfig {
                        @Bean
                        public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
                            RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
                            routes.route("path_route_atguigu",
                                    r -> r.path("/guonei")
                                            .uri("http://baidu.com")).build();
                            return routes.build();
                        }
                    }
                    

                    浏览器输入http://localhost:9527/guonei,返回http://baidu.com相同的页面。

                    动态路由

                    网关项目的配置会随着微服务模块数量增多而变得复杂,维护的工作量也会越来越大

                    所以我们希望gateway能够设计一套默认情况下自动路由到每个模块的路由规则

                    这样的话,不管当前项目有多少个路由目标,都不需要维护yml文件了

                    这就是我们SpringGateway的动态路由功能

                    SpringGateway网关,在这里插入图片描述,第17张

                    配置文件中开启即可

                    gateway:
                      discovery:
                        locator:
                          # 开启动态路由功能,默认值是关闭的
                          # 动态路由规则:在网关端口号后,先编写要路由目标服务器注册到Naocs的名称
                          # 在编写访问这个服务器的具体路径
                          # 例如要访问 localhost:9001/bj/show  -> localhost:9000/beijing/bj/show
                          enabled: true
                    

                    按上面修改完配置之后

                    我们可以重启gateway来测试动态路由路径是否生效

                    动态路由生成规则为:在网关端口号后先写要路由到的目标服务器在nacos注册的名称,再编写具体路径

                    内置断言

                    我们上面章节在网关配置中使用了predicates(断言)的配置

                    断言的意思就是判断某个条件是否满足

                    我们之前使用了Path断言,判断请求的路径是不是满足条件,例如是不是/sh/** /bj/**

                    如果路径满足这个条件,就路由到指定的服务器

                    但是Path实际上只是SpringGateway提供的多种内置断言中的一种

                    SpringGateway网关,在这里插入图片描述,第18张

                    还有很多其它断言

                    • After
                    • Before
                    • Between
                    • Cookie
                    • Header
                    • Host
                    • Method
                    • Path
                    • Query
                    • Remoteaddr

                      时间相关

                      After,Before,Between

                      判断当前时间在指定时间之前,之后或之间的操作

                      如果条件满足可以执行路由操作,否则拒绝访问

                      表示时间的格式比较特殊,先使用下面代码获得时间

                      ZonedDateTime.now()
                      

                      运行程序输出,可获得当前时间,这个时间的格式可能是

                      2023-04-21T11:13:54.967+08:00[Asia/Shanghai]
                      

                      下面在yml配置中添加新的断言配置

                      使用After设置必须在指定时间之后访问

                      routes:
                        - id: gateway-shanghai
                          uri: lb://shanghai
                          predicates:
                            - Path=/sh/**
                            # After是时间断言,判断当前请求访问时的时间是否晚于配置的时间
                            # 如果成立正常访问,如果判断不成立,返回404错误,多个断言之间是"与"的关系
                            - After=2023-04-21T11:21:30.967+08:00[Asia/Shanghai]
                      

                      使用Before设置必须在指定时间之前才能访问服务

                      否则发生404错误拒绝访问

                      predicates:
                        - Path=/sh/**
                        - Before=2023-04-21T11:23:20.967+08:00[Asia/Shanghai]
                      

                      使用Between设置必须在指定时间之间访问

                      predicates:
                        - Path=/sh/**
                        - Between=2023-04-21T11:25:25.967+08:00[Asia/Shanghai],2023-04-21T11:25:45.967+08:00[Asia/Shanghai]
                      

                      要求指定参数的请求

                      Query断言,判断是否包含指定的参数名称,包含参数名称才能通过路由

                      SpringGateway网关,在这里插入图片描述,第19张

                      predicates:
                        - Path=/sh/**
                        # Query断言判断请求中是否包含指定参数名称,这里设置为username,如果没有发生404错误
                        - Query=username
                      

                      重启gateway测试

                      必须是包含username参数的请求才能访问到指定的页面

                      例如:http://localhost:9000/sh/show?username=tom

                      带 Cookie 参数的断言

                      SpringGateway网关,在这里插入图片描述,第20张

                      spring:
                        cloud:
                          gateway:
                            routes:
                            - id: cookie_route
                              uri: https://example.org
                              predicates:
                              - Cookie=username, yyx
                      

                      不带 Cookie 访问

                      SpringGateway网关,在这里插入图片描述,第21张

                      携带 Cookie 访问

                      SpringGateway网关,在这里插入图片描述,第22张

                      带 Header 参数的断言

                      SpringGateway网关,在这里插入图片描述,第23张

                      spring:
                        cloud:
                          gateway:
                            routes:
                            - id: header_route
                              uri: https://example.org
                              predicates:
                              - Header=X-Request-Id, \d+  # 请求头要有 X-Request-Id 属性并且值为整数的正则表达式
                      

                      SpringGateway网关,在这里插入图片描述,第24张

                      Host 断言

                      SpringGateway网关,在这里插入图片描述,第25张

                      Method 断言

                      SpringGateway网关,在这里插入图片描述,第26张

                      内置过滤器

                      Gateway还提供的内置过滤器

                      不要和我们学习的filter混淆

                      内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理

                      SpringGateway网关,在这里插入图片描述,第27张

                      常见过滤器也有一些

                      AddRequestParameter过滤器

                      我们给大家演示一下AddRequestParameter过滤器

                      它的作用是在请求中添加参数和它对应的值

                      routes:
                        - id: gateway-shanghai
                          uri: lb://shanghai
                          filters:
                            # 如果路由顺利成功,这个内置过滤器会自动在请求中添加下面的参数
                            # 属性名称为age,默认值18,如果请求中包含age的值,这个值就不会生效了
                            - AddRequestParameter=age,18
                          predicates:
                            - Path=/sh/**
                            # Query断言判断请求中是否包含指定参数名称,这里设置为username,如果没有发生404错误
                            - Query=username
                      

                      在shanghai的控制器方法中添加代码接收username,age的值

                      @GetMapping("/show")
                      public String show(String username,Integer age){
                          // 2023-04-21T11:13:54.967+08:00[Asia/Shanghai]
                          System.out.println(ZonedDateTime.now());
                          return "这里是上海!username:"+username+",age:"+age;
                      }
                      

                      重启shanghai和gateway进行测试

                      http://localhost:9000/sh/show?username=jerry

                      因为过滤器的存在,控制器可以获取网关过滤器添加的参数值

                      SpringGateway网关,在这里插入图片描述,第28张

                      自定义过滤器

                      创建类 filter.MyLogGateWayFilter

                      package com.atguigu.springcloud.filter;
                      import lombok.extern.slf4j.Slf4j;
                      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;
                      import java.util.Date;
                      @Component
                      @Slf4j
                      public class MyLogGateWayFilter implements GlobalFilter, Ordered {
                          @Override
                          public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                              log.info("*********** come in MyLogGateWayFilter:  " + new Date());
                              String uname = exchange.getRequest().getQueryParams().getFirst("uname");
                              if(uname == null)
                              {
                                  log.info("******* 用户名为null,非法用户,o(╥﹏╥)o");
                                  exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
                                  return exchange.getResponse().setComplete();
                              }
                              return chain.filter(exchange);
                          }
                          @Override
                          public int getOrder() {
                              return 0;  // 数字越小,过滤器优先级越高
                          }
                      }
                      

                      启动项目,正常访问携带参数,不携带报错

                      正常访问如:http://localhost:9527/payment/lb?uname=123

                      错误访问如:http://localhost:9527/payment/lb

                      路由配置的设计规则

                      路由规则解释

                      路由规则一定是在开发之前就设计好的

                      一般可以使用约定好的路径开头来实现的

                      例如

                      gateway项目

                      如果路径以 /bj开头,就是要访问beijing项目

                      如果路径以 /sh开头.就是养访问shanghai项目

                      csmall项目

                      如果路径是 /base/business开头的, 就去找nacos-business服务器

                      如果路径是 /base/cart开头的, 就去找nacos-cart服务器

                      如果路径是 /base/order开头的, 就去找nacos-order服务器

                      如果路径是 /base/stock开头的, 就去找nacos-stock服务器

                      SpringGateway网关,在这里插入图片描述,第29张

                      csmall项目网关

                      创建网关项目,然后父子相认

                      修改子项目pom文件和依赖

                      
                      
                          4.0.0
                          
                              cn.tedu
                              csmall
                              0.0.1-SNAPSHOT
                               
                          
                          cn.tedu
                          gateway
                          0.0.1-SNAPSHOT
                          gateway
                          Demo project for Spring Boot
                          
                              
                              
                                  org.springframework.boot
                                  spring-boot-starter-web
                              
                              
                              
                                  com.alibaba.cloud
                                  spring-cloud-starter-alibaba-nacos-discovery
                              
                              
                                  org.springframework.cloud
                                  spring-cloud-starter-gateway
                              
                              
                                  org.springframework.cloud
                                  spring-cloud-starter-loadbalancer
                              
                              
                                  com.github.xiaoymin
                                  knife4j-spring-boot-starter
                              
                          
                      
                      

                      也删除test测试文件夹

                      application.properties换为yml

                      配置如下

                      server:
                        port: 19000
                      spring:
                        application:
                          name: gateway-server
                        cloud:
                          nacos:
                            discovery:
                              server-addr: localhost:8848
                          gateway:
                            discovery:
                              locator:
                                # 开启动态路由
                                enabled: true
                        main:
                          # 防止SpringMVC和SpringGateway依赖冲突的配置
                          web-application-type: reactive
                      

                      网关项目的knife4j配置

                      我们希望配置网关之后,在使用knife4j测试时

                      就不来回切换端口号了

                      我们需要在网关项目中配置Knife4j才能实现

                      而这个配置是固定的,

                      只要是网关项目配置各个子模块的knife4j功能,就直接复制这几个类即可

                      csmall-finish中直接复制config\controller\filter

                      cn.tedu.gateway.config

                      SwaggerProvider

                      @Component
                      public class SwaggerProvider implements SwaggerResourcesProvider {
                          /**
                           * 接口地址
                           */
                          public static final String API_URI = "/v2/api-docs";
                          /**
                           * 路由加载器
                           */
                          @Autowired
                          private RouteLocator routeLocator;
                          /**
                           * 网关应用名称
                           */
                          @Value("${spring.application.name}")
                          private String applicationName;
                          @Override
                          public List get() {
                              //接口资源列表
                              List resources = new ArrayList<>();
                              //服务名称列表
                              List routeHosts = new ArrayList<>();
                              // 获取所有可用的应用名称
                              routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
                                      .filter(route -> !applicationName.equals(route.getUri().getHost()))
                                      .subscribe(route -> routeHosts.add(route.getUri().getHost()));
                              // 去重,多负载服务只添加一次
                              Set existsServer = new HashSet<>();
                              routeHosts.forEach(host -> {
                                  // 拼接url
                                  String url = "/" + host + API_URI;
                                  //不存在则添加
                                  if (!existsServer.contains(url)) {
                                      existsServer.add(url);
                                      SwaggerResource swaggerResource = new SwaggerResource();
                                      swaggerResource.setUrl(url);
                                      swaggerResource.setName(host);
                                      resources.add(swaggerResource);
                                  }
                              });
                              return resources;
                          }
                      }
                      

                      cn.tedu.gateway.controller

                      SwaggerController类

                      @RestController
                      @RequestMapping("/swagger-resources")
                      public class SwaggerController {
                          @Autowired(required = false)
                          private SecurityConfiguration securityConfiguration;
                          @Autowired(required = false)
                          private UiConfiguration uiConfiguration;
                          private final SwaggerResourcesProvider swaggerResources;
                          @Autowired
                          public SwaggerController(SwaggerResourcesProvider swaggerResources) {
                              this.swaggerResources = swaggerResources;
                          }
                          @GetMapping("/configuration/security")
                          public Mono> securityConfiguration() {
                              return Mono.just(new ResponseEntity<>(
                                      Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
                          }
                          @GetMapping("/configuration/ui")
                          public Mono> uiConfiguration() {
                              return Mono.just(new ResponseEntity<>(
                                      Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
                          }
                          @GetMapping("")
                          public Mono swaggerResources() {
                              return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
                          }
                      }
                      

                      cn.tedu.gateway.filter

                      SwaggerHeaderFilter类

                      @Component
                      public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
                          private static final String HEADER_NAME = "X-Forwarded-Prefix";
                          private static final String URI = "/v2/api-docs";
                          @Override
                          public GatewayFilter apply(Object config) {
                              return (exchange, chain) -> {
                                  ServerHttpRequest request = exchange.getRequest();
                                  String path = request.getURI().getPath();
                                  if (!StringUtils.endsWithIgnoreCase(path,URI )) {
                                      return chain.filter(exchange);
                                  }
                                  String basePath = path.substring(0, path.lastIndexOf(URI));
                                  ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
                                  ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
                                  return chain.filter(newExchange);
                              };
                          }
                      }
                      

                      gateway项目删除test文件夹

                      测试网关路由效果,和knife4j效果

                      启动Nacos\Seata\Sentinel

                      启动cart\stock\order\business

                      最后启动gateway

                      可以通过19000端口测试各个业务模块的功能

                      http://localhost:19000/nacos-stock/doc.html

                      http://localhost:19000/nacos-cart/doc.html

                      http://localhost:19000/nacos-order/doc.html

                      http://localhost:19000/nacos-business/doc.html

                      如果不使用网关一切正常,但是启动网关访问失败的话,就是gateway项目配置问题

                      Gateway和SpringMvc依赖冲突问题和解决

                      之前网关的演示项目我们添加的网关依赖

                      
                      
                          org.springframework.cloud
                          spring-cloud-starter-gateway
                      
                      

                      当前csmall项目需要配置knife4j的路由配置,需要编写一个控制器

                      所以我们添加了SpringMVC的依赖

                      
                          org.springframework.boot
                          spring-boot-starter-web
                      
                      

                      这两个依赖在同一个项目中时,默认情况下启动会报错

                      SpringMVC框架依赖中自带一个Tomcat服务器

                      而SpringGateway框架中自带一个Netty的服务器

                      在启动项目时,两个框架中包含的服务器都想占用相同端口,因为争夺端口号的主动权而发生冲突

                      导致启动服务时报错

                      要想能够正常启动必须在yml文件配置

                      spring:
                        main:
                          web-application-type: reactive
                      

                      reactive:反应的

                      添加这个配置之后,会Tomcat服务器会变成非阻塞的运行