08-Gateway服务网关

三、Gateway服务网关

3.1、网关的意义

  • Gateway网关是我们服务的守门神,所有微服务的统一入口
  • 网关的核心功能特性,如下所示
    • 请求路由
    • 权限控制
    • 限流
  • 网关的架构图如下所示
    • 微服务之间通过Feign来访问,外部的访问直接访问微服务不安全,需要通过网关来控制
  • 权限控制
    • 网关作为微服务的入口,需要校验用户是否有请求资格,如果没有则进行拦截
  • 路由和负载均衡
    • 一切请求都必须先经过Gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标有多个实例的时候,还需要做负载均衡,用于选中哪个实例
  • 限流
    • 类似于节假日的公园等场所限流。当请求流量过高的时候,在网关中按照下游的微服务能够接受的速度来放行请求,避免服务压力过大,对微服务起到保护作用
  • 在SpringCloud中网关的实现包括两种
    • ①、gateway
    • ②、zuul
  • Zuul是基于Servlet实现的,属于阻塞式编程;而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能

3.2、Gateway快速入门

3.2.1、创建gateway服务,引入依赖

  • 创建服务

  • 引入依赖

    • <dependencies>
      <!--gateway-->
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-gateway</artifactId>
      </dependency>
      <!--nacos服务发现依赖-->
      <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
      <version>2.1.0.RELEASE</version>
      </dependency>
      </dependencies>

3.2.2、编写启动类

  • 启动类内容如下所示

    • package cn.coolman.gateway;
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      @SpringBootApplication
      public class GatewayApplication {
      public static void main(String[] args) {
      SpringApplication.run(GatewayApplication.class, args);
      }
      }

3.2.3、编写基础配置和路由规则

  • 创建application.yml文件,内容如下

    • server:
      port: 10010
      spring:
      application:
      name: gateway
      cloud:
      gateway:
      routes:
      - id: userservicegateway # 一个路由的id,可以是随意的
      uri: lb://userservice # 使用负载均衡,把请求转发到userservice这个微服务里面
      predicates:
      - Path=/user/** # 断言(如果用户访问的路径是: /user/下面的任意路径,就把这个请求路由到lb://userservice这个微服务里面
      - id: orderservicegateway # 一个路由的id,可以是随意的
      uri: lb://orderservice
      predicates:
      - Path=/order/** # 断言
      nacos:
      discovery:
      # 这里的nacos配置,本次使用的是单机nacos,没有使用nacos集群,如果使用的是nacos集群,那么这里的nacos地址就应该写nginx的地址(因为nacos集群是靠nginx负载均衡实现的请求转发)
      server-addr: localhost:8848
      namespace: 88335441-8805-441a-9964-3628d17634ac # 保证该微服务与其他微服务处于同一个命名空间
    • 我们将符合Path规则的一切请求,都代理到uri参数指定的地址

    • 本例中,我们将/user/**开头的请求,代理到lb://userservice,lb是LoadBalance负载均衡的缩写,根据服务名拉取服务列表,实现负载均衡。(PS:Path后面是等于,不是冒号,这只是一个字符串)

    • routes对应的是一个List集合,每个元素是RouteDefinition

3.2.4、测试

3.2.5、网关路由的流程图

  • 整个访问的流程如下所示
  • 上图是单机nacos的访问流程图,如果是nacos集群,那么在服务注册和发现步骤是还需要一层Nignx实现nacos节点的负载均衡

3.2.6、小结

  • 网关搭建的步骤
    • 1)创建项目,引入nacos服务发现和gateway依赖
    • 2)配置application.yml,包括服务端口号,应用名称,nacos地址,路由
    • 3)创建启动类
  • 路由配置包括哪些内容
    • 1)路由id:路由的唯一标识
    • 2)路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
    • 3)路由断言(predicates):判断路由的规则
    • 4)路由过滤器(filters):对请求或响应做处理

3.3、断言工厂

3.3.1、断言工厂介绍

  • 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件

  • 例如Path=/usr/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的,像这样的断言工厂在SpringCloudGateway还有很多个,如下所示

  • 名称 说明 示例
    After 是某个时间点后的请求 - After=2037-01-20T17:42:47.789-07:00[America/Denver]
    Before 是某个时间点之前的请求 - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
    Between 是某两个时间点之前的请求 - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
    Cookie 请求必须包含某些cookie - Cookie=chocolate, 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 权重处理

3.3.2、断言工厂的示例

①、在某个时间点之后才能访问

  • predicates:
    - After=2030-01-20T17:42:47.789-07:00[Asia/Shanghai]
  • 如果某个请求无法路由,就回出现404错误

②、在某个时间点之前才能访问

  • predicates:
    - Before=2030-01-20T17:42:47.789-07:00[Asia/Shanghai]
  • 一般情况下,Path这种路由工程是最常用的

3.4、过滤器工厂

  • GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理,其架构如下图所示

3.4.1、路由过滤器的种类

  • Spring提供了31中不同的路由过滤器工厂,例如:

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

3.4.2、请求头过滤器

  • 官网文档

  • 以AddRequestHeader为例

    • 需求:给所有进入userservice的请求添加一个请求头Truth=coolman is so freaking awesome !!!
  • 1)修改gateway服务的application文件,添加路由过滤器

    • PS:键和值使用逗号分割,而不是等于号

    • gateway:
      routes:
      - id: userservicegateway # 一个路由的id,可以是随意的
      uri: lb://userservice # 使用负载均衡,把请求转发到userservice这个微服务里面
      predicates:
      - Path=/user/** # 断言(如果用户访问的路径是: /user/下面的任意路径,就把这个请求路由到lb://userservice这个微服务里面
      filters:
      - AddRequestHeader=Truth,coolman is so freaking awesome !!!
      - id: orderservicegateway # 一个路由的id,可以是随意的
      uri: lb://orderservice
      predicates:
      - Path=/order/** # 断言
    • 当前过滤器卸载userservice路由下,因此仅仅对访问userservice的请求有效

  • 2)在userservice这个微服务中使用@RequestHeader注解在方法中获取名为Truth的请求头,为了避免没有这个请求头而报错,将它的required设置为false

    • @GetMapping("/header")
      public String getHeader(@RequestHeader(name = "Truth", required = false) String truth) {
      return truth;
      }
  • 3)重启userservice和gateway两个微服务,在浏览器上查看结果

3.4.3、默认过滤器

  • 如果要对所有的路由都生效,则可以将过滤器工厂写道default-filters下,修改gateway服务的application.yml文件

    • 1)把上面的局部过滤器注释或者去掉

    • 2)配置与routes在同一个级别

    • 3)进行以下配置

      • gateway:
        routes:
        - id: userservicegateway # 一个路由的id,可以是随意的
        uri: lb://userservice # 使用负载均衡,把请求转发到userservice这个微服务里面
        predicates:
        - Path=/user/** # 断言(如果用户访问的路径是: /user/下面的任意路径,就把这个请求路由到lb://userservice这个微服务里面
        # filters:
        # - AddRequestHeader=Truth,coolman is so freaking awesome !!!
        - id: orderservicegateway # 一个路由的id,可以是随意的
        uri: lb://orderservice
        predicates:
        - Path=/order/** # 断言
        default-filters:
        - AddRequestHeader=Truth,coolman is so freaking awesome !!!
    • 4)order-service中的controller也添加getHeader方法,使用@RequestHeader注解

      • @GetMapping("/header")
        public String getHeader(@RequestHeader(name = "Truth", required = false) String truth) {
        return truth;
        }
    • 5)浏览器访问

      • 运行效果是一样的,如下所示

3.4.4、小结

  • 过滤器的作用
    • ①、对路由的请求或者响应做加工处理,比如添加请求头
    • ②、配置在路由下的过滤器支队当前路由的请求生效
    • ③、defaultFilters的作用是什么
      • 对所有都生效的过滤器

3.5、全局过滤器

  • 上一节学习的过滤器,网关提供了31中,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现

3.5.1、全局过滤器的作用

  • 全局过滤器的作用也是处理一切进入网关的请求和微服务响应

  • 与GatewayFilter的作用一样;区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现

  • 定义方式是实现Global接口,Spring中定义的接口代码如下所示

    • public interface GlobalFilter {
      /**
      * 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
      *
      * @param exchange 请求上下文,里面可以获取Request、Response等信息
      * @param chain 用来把请求委托给下一个过滤器
      * @return {@code Mono<Void>} 返回标示当前过滤器业务结束
      */
      Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
      }
  • 在filter中编写自定义逻辑,可以实现下列功能

    • ①、登录状态判断
    • ②、权限校验
    • ③、请求限流
    • ...

3.5.2、自定义全局过滤器

需求

  • 定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件
    • 参数中是否由authorization
    • authorization参数值是否为admin
  • 如果同时满足则放行,否则拦截

实现

  • 在gateway中的cn.coolman.gateway.filters包下定义一个过滤器

    • 1)编写一个类实现GlobalFilter接口,重写filter方法

    • 2)使用@Order注解指定执行顺序,值越小,越先执行

    • 3)使用@Component将过滤器对象放到Spring容器中

      • ①、获取请求对象
      • ②、获取响应对象
      • ③、获取所有的请求参数,封装成键和值
      • ④、获取第一个匹配的键
      • ⑤、如果等于admin,则放行,返回消息处理完毕
      • ⑥、否则设置状态码为未登录,返回消息处理完毕
    • package cn.coolman.gateway.filter.custom;
      import cn.coolman.gateway.filter.GlobalFilter;
      import org.springframework.cloud.gateway.filter.GatewayFilterChain;
      import org.springframework.core.annotation.Order;
      import org.springframework.http.HttpStatus;
      import org.springframework.http.server.reactive.ServerHttpRequest;
      import org.springframework.http.server.reactive.ServerHttpResponse;
      import org.springframework.stereotype.Component;
      import org.springframework.util.MultiValueMap;
      import org.springframework.web.server.ServerWebExchange;
      import reactor.core.publisher.Mono;
      @Order(-1)
      @Component
      public class AuthorizeFilter implements GlobalFilter {
      @Override
      public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      // 1. 获取请求对象
      ServerHttpRequest request = exchange.getRequest();
      // 2. 获取响应对象
      ServerHttpResponse response = exchange.getResponse();
      // 3. 获取所有的请求参数,封装成键和值
      MultiValueMap<String, String> params = request.getQueryParams();
      // 4. 获取第一个匹配的键
      String authorization = params.getFirst("authorization");
      // 5. 如果等于admin,则放行
      if ("admin".equals(authorization)) {
      // 方法的返回值表示消息处理完毕
      return chain.filter(exchange);
      } else {
      // 6. 否则设置状态码未登录
      response.setStatusCode(HttpStatus.UNAUTHORIZED);
      return response.setComplete();
      }
      }
      }

测试

  • 1)重启网关服务
  • 2)直接访问userservice的数据,会出现401错误,如下图所示
  • 3)请求参数如果带一个authorization=admin,则可以正常访问

3.5.3、过滤器执行顺序

过滤器的分类

  • 请求进入网关回碰到三类过滤器:DefaultFilter、路由过滤器、GlobalFilter
  • 请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器,如下所示

排序的规则

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高 ,执行顺序就越靠前
    • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
    • 路由过滤器(局部)和DefaultFilter(全局),它的执行顺序默认是按照声明顺序从1递增
    • 执行顺序:defaultFilter > 路由过滤器(局部)> GlobalFilter的顺序执行

3.6、跨域问题

3.6.1、跨域问题的概念

  • 跨域概念
    • 域名不一致就是跨域,主要包括以下几种类型
    • 域名不同
      • www.taobao.com 和 www.jd.com
    • 端口不同
      • localhost:8080和localhost:8081
    • 协议不同
      • https://localhost:8080和http://localhost:8080
  • 跨域问题
    • 浏览器禁止请求的发起者与服务器发生跨域的Ajax请求,请求被浏览器拦截的问题

3.6.2、模拟跨域问题

①、创建一个新的Java Web工程

②、自定义一个带有ajax请求的web页面,复制到webapp目录下

  • pre标签里面的内容可以自定义,作用是当页面加载成功后才正常显示

③、部署到Tomcat服务器中,启动并访问,指定端口为8090

④、可以在浏览器控制台看到下面的错误

3.6.3、解决跨域问题

①、参数说明

  • 因为所有的微服务都要经过网关,所有不需要每个微服务都去处理,在网关中处理就可以
  • CORS方案是浏览器想服务器询问是否允许本次请求跨域,这次询问是options请求,所以要让options请求通过
  • 但是如果每次跨域请求都询问,则会导致性能下降;于是设置一个有效期,在有效期内的请求不再询问,而是直接允许跨域请求

②、添加配置

  • 在gateway服务的application.yml文件中,gateway的下一级中,添加如下配置

  • # 全局的跨域处理
    globalcors:
    add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
    corsConfigurations:
    '[/**]': # 哪些访问地址做跨域处理
    allowedOrigins: # 允许哪些网站的跨域请求
    - "http://localhost:8090"
    allowedMethods: # 允许的跨域ajax的请求方式
    - "GET"
    - "POST"
    - "DELETE"
    - "PUT"
    - "OPTIONS"
    allowedHeaders: "*" # 允许在请求中携带的头信息
    allowCredentials: true # 是否允许携带cookie
    maxAge: 360000 # 这次跨域检测的有效期

③、查看结果

  • 配置完成以后,重启服务器,查看浏览器的控制台
posted @   OnlyOnYourself-Lzw  阅读(84)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示