Gateway服务网关

一、Gateway简介

Spring Cloud Gateway 是 Spring Cloud 的一个子项目,它是 Spring Boot 2.x 版本中引入的一个新的 API 网关。Spring Cloud Gateway 基于 Spring Boot 2.x 的响应式编程模型,提供了更快的应用程序和 API 网关。与 Spring Cloud 其他项目一样,Spring Cloud Gateway 提供了简单、一致的编程模型,使得在分布式系统中构建 API 网关变得简单而直接

1. 主要特点

(1) 基于 Spring Boot 2.x:

Spring Cloud Gateway 是基于 Spring Boot 2.x 开发的,这使得它能够利用 Spring Boot 的强大功能和响应式编程模型。


(2)快的速度:

Spring Cloud Gateway 使用了高性能的 Netty 框架作为底层,这使得它的处理速度非常快。


(3)单的编程模型:

Spring Cloud Gateway 提供了简单、一致的编程模型,允许开发人员以最少的工作量来构建 API 网关。


(4) 与 Spring Cloud 集成:

Spring Cloud Gateway 是 Spring Cloud 生态系统的一部分,这意味着它与 Spring Cloud 的其他项目(如 Eureka、Hystrix 等)可以轻松集成。


(5) 支持多种协议:

Spring Cloud Gateway 不仅支持 HTTP 和 HTTPS,还支持 WebSocket、TCP、UDP 等其他协议。

2. 缺点

(1) 锁定到 Spring Cloud:

Spring Cloud Gateway 是 Spring Cloud 生态系统的一部分,这意味着它与 Spring Cloud 的其他项目紧密集成。虽然这使得它易于与 Spring Cloud 的其他项目集成,但也限制了其在其他环境中的使用。

(2) 相对较新的项目:

虽然 Spring Cloud Gateway 已经存在了一段时间,但它仍然是一个相对较新的项目。这意味着它可能没有在生产环境中经过充分的测试,也没有被广泛采用。

(3) 有限的社区支持:

由于 Spring Cloud Gateway 是 Spring Cloud 的一个子项目,因此其社区支持可能相对较少。这可能会限制其长期的发展和维护。

(4) 对复杂路由的支持有限:

Spring Cloud Gateway 的路由模型基于简单的 URL 匹配,对于复杂的 route 匹配,可能需要自定义过滤器或使用其他方法。

(5) 配置复杂性:

Spring Cloud Gateway 的配置比较复杂,尤其是在大型和中型的项目中。虽然它提供了一些简单的配置选项,但更多的配置可能需要深入的 Spring Cloud 知识。

3.基本概念

(1) Route:

路由是 Spring Cloud Gateway 的基本构建块。每个 Route 都由一个 ID、一个目标 URI、一个过滤器集合和一个限流器组成。


(2) Filter:

过滤器用于在请求处理过程中执行一些操作。Spring Cloud Gateway 允许你创建自定义的过滤器,用于在请求处理过程中执行各种任务。

(3) 限流器:

限流器用于限制请求的速率,以防止过度请求造成系统过载。

4. 结论

Spring Cloud Gateway 是一个高性能、简单易用、与 Spring Cloud 集成良好的 API 网关。它为构建可扩展的、响应式的 API 网关提供了一个理想的解决方案。

二、Gateway快速入门

1. 创建ateway工程

Gateway服务网关_第1张图片

2. 加入依赖 

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

3. 创建启动类

@SpringBootApplication
public class gateway {
    public static void main( String[] args ){
        SpringApplication.run(gateway.class,args);
    }
}

4. 创建配置文件

application.yml

server:
  port: 8000
spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
        - id: product_route   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
          uri: http://localhost:8083  # 被路由的地址
          predicates:                  #断言: 执行路由的判断条件
            - Path=/order/**
          filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
            - StripPrefix=1

5. 测试

分别启动nacos和sentinal 

 nacos启动命令
startup.cmd -m standalone  

            

sentinal 启动命令
java -jar sentinel-dashboard-1.8.1.jar

自定义端口等启动命令
java -Dserver.port=8888 -Dcsp.sentinel.dashboard.server=localhost:8888 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
 

Controller类

 Gateway服务网关_第2张图片

正常访问

Gateway服务网关_第3张图片

通过网关访问

 Gateway服务网关_第4张图片

三、增强版

1.  加入nacos依赖

        com.alibaba.cloud

        spring-cloud-starter-alibaba-nacos-discovery

根据在nacos中的名字 去选择调用的服务,由于在使用服务的名字的时候需要负载均衡,而2021版本的springcloudalibaba去除了对ribbon的依赖,所以,在选择2021版本的微服务架构的时候需要加上负载均衡的包 

        org.springframework.cloud

        spring-cloud-starter-loadbalancer

2. 在主启动类上加入服务发现的注解

@EnableDiscoveryClient

 Gateway服务网关_第5张图片

3. 修改application.yml的配置文件

加上nacos连接   

server:
  port: 8000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: product_route   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
          uri: lb://product
        # 被路由的地址
          predicates:                  #断言: 执行路由的判断条件
            - Path=/order/**
          filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
            - StripPrefix=1

 Gateway服务网关_第6张图片

 4. 更改负载均衡的策略

(1) 写一个配置类

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

public class LoadBalancerConfig {
    
    // 使用@Bean注解将randomLoadBalancer方法声明为一个bean
    @Bean
    ReactorLoadBalancer randomLoadBalancer(Environment environment,
                                                            LoadBalancerClientFactory loadBalancerClientFactory) {
        // 从环境变量中获取负载均衡器的名称
        String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        
        // 打印负载均衡器的名称
        System.out.println(name);
        
        // 创建一个RandomLoadBalancer对象,并传入负载均衡器的名称以及ServiceInstanceListSupplier
        return new RandomLoadBalancer(loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
    }
}

 (2) 启动类加注解

@LoadBalancerClients(defaultConfiguration= LoadBalancerConfig.class)

(3) 修改application.yml的配置文件

Gateway服务网关_第7张图片

四、简化版

1. 去掉关于路由的配置

server:
  port: 8000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          enabled: true

2. 启动项目,并通过网关去访问微服务

五、Gateway核心架构

1.  基本概念

路由(Route) 是 gateway 中最基本的组件之一,表示一个具体的路由信息载体。主要定义了下面的几个信息

(1)  id

路由标识符,区别于其他 Route。唯一 不写 默认的唯一的值

(2)  uri

路由指向的目的地 uri,即客户端请求最终被转发到的微服务。

(3)  order

用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。

(4)  predicate

断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。

(5)  fifilter

过滤器用于修改请求和响应信息。

2.  执行流程

Gateway服务网关_第8张图片

 执行流程大概如下

1. Gateway Client向Gateway Server发送请求

2. 请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文

3. 然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给 RoutePredicateHandlerMapping

4. RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用

5. 如果过断言成功,由FilteringWebHandler创建过滤器链并调用

6. 请求会一次经过PreFilter--微服务--PostFilter的方法,最终返回响应

3.  断言

Predicate(断言, 谓词) 用于进行条件判断,只有断言都返回真,才会真正的执行路由。

断言就是说: 在 什么条件下 才能进行路由转发

(1)内置路由断言工厂

        SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配体如下

3.1.1 基于Datetime类型的断言工厂

此类型的断言根据时间做判断,主要有三个:

AfterRoutePredicateFactory: 接收一个日期参数,判断请求日期是否晚于指定日期

BeforeRoutePredicateFactory: 接收一个日期参数,判断请求日期是否早于指定日期

BetweenRoutePredicateFactory: 接收两个日期参数,判断请求日期是否在指定时间段内

-After=2006-06-06T06:06:06.789+08:00[Asia/Shanghai]

3.1.2 基于远程地址的断言工厂 RemoteAddrRoutePredicateFactory

接收一个IP地址段,判断请求主机地址是否在地址段中

-RemoteAddr=xxx.xxx.xxx.xxx

3.1.3 基于Cookie的断言工厂 

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式。 判断请求

cookie是否具有给定名称且值与正则表达式匹配。

-Cookie=chocolate, ch.

3.1.4 基于Header的断言工厂 

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否

具有给定名称且值与正则表达式匹配。

-Header=X-Request-Id, \d+

3.1.5 基于Host的断言工厂 

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。

-Host=**.testhost.org

3.1.6 基于Method请求方法的断言工厂 

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

-Method=GET

3.1.7 基于Path请求路径的断言工厂 

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

-Path=/foo/{segment}基于Query请求参数的断言工厂

QueryRoutePredicateFactory :接收两个参数,请求param和正则表达式, 判断请求参数是否具

有给定名称且值与正则表达式匹配。

-Query=baz, ba.

3.1.8 基于路由权重的断言工厂 

WeightRoutePredicateFactory:接收一个[组名,权重], 然后对于同一个组内的路由按照权重转发

routes:
  - id: product_route   # 路由的唯一标识,只要不重复都可以,如果不写默认会通过UUID产生,一般写成被路由的服务名称
    uri: http://localhost:8083  # 被路由的地址
    #uri: lb://product# 被路由的地址 lb代表负载均衡  loadbalanced
    predicates:                  #断言: 执行路由的判断条件
      - Path=/order/**
    filters:                     # 过滤器: 可以在请求前或请求后作一些手脚
      - StripPrefix=1
  - id: product_route2
    uri: http://localhost:8082
    predicates: #断言: 执行路由的判断条件
      - Path=/order2/**
    filters: # 过滤器: 可以在请求前或请求后作一些手脚
      - StripPrefix=1

Gateway服务网关_第9张图片

3.1.9 内置路由断言工厂的使用  

Gateway服务网关_第10张图片

server:
  port: 8000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: product_route
          uri: http://localhost:8083
          predicates:
            - Path=/order/**
            - Before=2022-06-06T06:06:06.789+08:00
          filters:
            - StripPrefix=1

Gateway服务网关_第11张图片

(2)自定义路由断言工厂

假设我们的应用仅仅让age在(min,max)之间的人来访问

第一步:

        在配置文件中,添加一个Age的断言配置

        Gateway服务网关_第12张图片

server:
  port: 8000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: product_route
          uri: http://localhost:8083
          predicates:
            - Path=/order/**
            - Before=2024-06-06T06:06:06.789+08:00
            - Age=18,66
          filters:
            - StripPrefix=1

第二步:

        自定义一个断言工厂, 实现断言方法

        

import com.alibaba.cloud.commons.lang.StringUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;


import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

@Component
public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory {

    // 构造函数
    public AgeRoutePredicateFactory() {
        super(AgeRoutePredicateFactory.Config.class);
    }

    // 返回快捷字段的顺序
    public List shortcutFieldOrder() {
        return Arrays.asList("minAge", "maxAge");
    }

    // 应用路由谓词
    public Predicate apply(AgeRoutePredicateFactory.Config config) {
        return new Predicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                // 获取请求中的年龄参数
                String age = exchange.getRequest().getQueryParams().getFirst("age");
                System.out.println("您的年龄:" + age);

                // 如果年龄参数不存在,则返回 false
                if (StringUtils.isBlank(age)) {
                    return false;
                }

                // 将年龄参数转换为整数
                int a = Integer.parseInt(age);

                // 如果年龄参数在指定范围内,则返回 true
                if (a >= config.minAge && a <= config.maxAge) {
                    return true;
                }

                // 否则返回 false
                return false;
            }
        };
    }

    // 配置类
    public static class Config {
        private Integer minAge;
        private Integer maxAge;

        // 设置最小年龄
        public void setMinAge(Integer minAge) {
            this.minAge = minAge;
        }

        // 设置最大年龄
        public void setMaxAge(Integer maxAge) {
            this.maxAge = maxAge;
        }
    }
}

第三步:

Controller

Gateway服务网关_第13张图片

    @GetMapping("/t8")
    public ResponseMsg t8(Integer age){
        return ResponseMsg.SUCCESS(200,"成功",age);
    }

第四步:

        启动测试

测试发现当age在(18,66)可以访问,其它范围不能访问

Gateway服务网关_第14张图片

Gateway服务网关_第15张图片

 Gateway服务网关_第16张图片

六、过滤器

作用: 过滤器就是在请求的传递过程中,对请求和响应做一些操作

生命周期: Pre Post

分类: 局部过滤器(作用在某一个路由上) 全局过滤器(作用全部路由上)

在Gateway中, Filter的生命周期只有两个:“pre” 和 “post”。

PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。

1. 局部过滤器

(1)内置局部过滤器

在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器

https://www.cnblogs.com/zhaoxiangjun/p/13042189.html

过滤器工厂

作用

参数

AddRequestHeader

为原始请求添加Header

Header的名称及值

AddRequestParameter

为原始请求添加请求参数

参数名称及值

AddResponseHeader

为原始响应添加Header

Header的名称及值

DedupeResponseHeader

剔除响应头中重复的值

需要去重的Header名称及去重策略

Hystrix

为路由引入Hystrix的断路器保护

HystrixCommand的名称

FallbackHeaders

为fallbackUri的请求头中添加具体的异常信息

Header的名称

PrefixPath

为原始请求路径添加前缀

前缀路径

PreserveHostHeader

为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host

RequestRateLimiter

用于对请求限流,限流算法为令牌桶

keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus

RedirectTo

将原始请求重定向到指定的URL

http状态码及重定向的url

RemoveHopByHopHeadersFilter

为原始请求删除IETF组织规定的一系列Header

默认就会启用,可以通过配置指定仅删除哪些Header

RemoveRequestHeader

为原始请求删除某个Header

Header名称

RemoveResponseHeader

为原始响应删除某个Header

Header名称

RewritePath

重写原始的请求路径

原始路径正则表达式以及重写后路径的正则表达式

RewriteResponseHeader

重写原始响应中的某个Header

Header名称,值的正则表达式,重写后的值

SaveSession

在转发请求之前,强制执行WebSession::save操作

secureHeaders

为原始响应添加一系列起安全作用的响应头

无,支持修改这些安全响应头的值

SetPath

修改原始的请求路径

修改后的路径

SetResponseHeader

修改原始响应中某个Header的值

Header名称,修改后的值

SetStatus

修改原始响应的状态码

HTTP 状态码,可以是数字,也可以是字符串

StripPrefix

用于截断原始请求的路径

使用数字表示要截断的路径的数量

Retry

针对不同的响应进行重试

retries、statuses、methods、series

RequestSize

设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large

请求包大小,单位为字节,默认值为5M

ModifyRequestBody

在转发请求之前修改原始请求体内容

修改后的请求体内容

ModifyResponseBody

修改原始响应体的内容

修改后的响应体内容

Default

为所有路由添加过滤器

过滤器工厂名称及值

(2) 自定义局部过滤器

    第一步: 添加配置文件

Gateway服务网关_第17张图片

    第二步: 在配置文件中,添加一个Log的过滤器配置

2. 全局过滤器

全局过滤器作用于所有路由, 无需配置。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。

(1)内置全局过滤器 

SpringCloud Gateway内部也是通过一系列的内置全局过滤器对整个路由转发进行处理如下:

全局过滤器

作用

Combined Global Filter and GatewayFilter Ordering

对过滤器执行顺序进行排序

Forward Routing Filter

用于本地forward,也就是将请求在Gateway服务内进行转发,而不是转发到下游服务

Netty Routing Filter

使用Netty的 HttpClient 转发http、https请求

Netty Write Response Filter

将代理响应写回网关的客户端侧

RouteToRequestUrl Filter

将从request里获取的原始url转换成Gateway进行请求转发时所使用的url

Websocket Routing Filter

使用Spring Web Socket将转发 Websocket 请求

Gateway Metrics Filter

整合监控相关,提供监控指标

Marking An Exchange As Routed

防止重复的路由转发

(2)自定义全局过滤器

内置的过滤器已经可以完成大部分的功能,但是对于企业开发的一些业务功能处理,还是需要我们自己编写过滤器来实现的,那么我们一起通过代码的形式自定义一个过滤器,去完成统一的认证校验。

开发中的鉴权逻辑:

当客户端第一次请求服务时,服务端对用户进行信息认证(登录)

认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证

以后每次请求,客户端都携带认证的token

服务端对token进行解密,判断是否有效。

Gateway服务网关_第18张图片

对于验证用户是否已经登录鉴权的过程可以在网关统一检验。

检验的标准就是请求中是否携带token凭证以及token的正确性。

自定义全局过滤器 要求:必须实现GlobalFilter,Ordered接口

加入依赖



    cn.hutool
    hutool-all
    5.8.16

写一个自定义Token过滤器

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.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.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

@Component
public class TestGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求头中的token
        String token = exchange.getRequest().getHeaders().getFirst("token");

        if (StringUtils.isNotBlank(token)) {
            // 如果token不为空,继续执行后续过滤器
            return chain.filter(exchange);
        } else {
            // 如果token为空,返回错误信息
            Map map = new HashMap<>();
            map.put("code", 666);
            map.put("msg", "出现错误,没有Token");
            map.put("data", "出现错误,没有Token,出现错误,没有Token,出现错误,没有Token,出现错误,没有Token");
            String s = JSONUtil.toJsonStr(map);
            ServerHttpResponse response = exchange.getResponse();
            DataBuffer dataBuffer;
            try {
                // 将字符串转换为字节数组
                byte[] bytes = s.getBytes("utf8");
                // 使用字节数组创建数据缓冲区
                 dataBuffer = response.bufferFactory().wrap(bytes);
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            return response.writeWith(Mono.just(dataBuffer));
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

测试

使用工具Postman

Gateway服务网关_第19张图片

Gateway服务网关_第20张图片

你可能感兴趣的:(gateway,网络)