ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

08-Gateway服务网关

2022-07-12 19:04:55  阅读:223  来源: 互联网

标签:网关 请求 08 gateway 过滤器 userservice 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  # 这次跨域检测的有效期
    

③、查看结果

  • 配置完成以后,重启服务器,查看浏览器的控制台

标签:网关,请求,08,gateway,过滤器,userservice,Gateway,路由
来源: https://www.cnblogs.com/OnlyOnYourself-lzw/p/16458209.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有