SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词/断言,Filter 过滤器(十三)

这篇具有很好参考价值的文章主要介绍了SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词/断言,Filter 过滤器(十三)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

阅读本文前可先参考

​​​​​​SpringCloud - Spring Cloud根/父项目,开发准备(二)_MinggeQingchun的博客-CSDN博客

SpringCloud - Spring Cloud 之 Gateway网关(十三)_MinggeQingchun的博客-CSDN博客

Web 有三大组件(监听器 过滤器 servlet),Spring Cloud GateWay 最主要的功能就是路由转发,而在定义转发规则时主要涉及了以下三个核心概念

1、Route(路由)

2、Predicate(谓词/断言)

3、Filter(过滤)

一、Routes路由配置 

路由断言/谓词工厂有12个

Spring Cloud Gateway

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

Gateway有两种配置路由方式

1、Java代码配置类路由

参考官网给出demo Spring Cloud Gateway

@SpringBootApplication
public class DemogatewayApplication {
	@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		return builder.routes()
			.route("path_route", r -> r.path("/get")
				.uri("http://httpbin.org"))
			.route("host_route", r -> r.host("*.myhost.org")
				.uri("http://httpbin.org"))
			.route("rewrite_route", r -> r.host("*.rewrite.org")
				.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
				.uri("http://httpbin.org"))
			.route("hystrix_route", r -> r.host("*.hystrix.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
				.uri("http://httpbin.org"))
			.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
				.uri("http://httpbin.org"))
			.route("limit_route", r -> r
				.host("*.limited.org").and().path("/anything/**")
				.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
				.uri("http://httpbin.org"))
			.build();
	}
}

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

 在 springcloud-7-service-eureka-gateway 模块总 自定义一个配置类 GatewayRouteConfig

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 GatewayRouteConfig {

    /**
     * 代码的路由 和 application.yml文件 不冲突,都可以用
     * 如果 uri后面给了一个访问地址 和匹配地址相同,就不会再拼接
     */
    @Bean
    public RouteLocator customRoiteLocator(RouteLocatorBuilder builder){
        //以下url从B站中引用
        return builder.routes()
                .route("movie-id",r->r.path("/movie").uri("https://www.bilibili.com/movie/?spm_id_from=333.1007.0.0"))
                .route("douga-id",r->r.path("/v/douga").uri("https://www.bilibili.com/v/douga/?spm_id_from=333.1007.0.0"))
                .build();
    }
}

启动springboot 类 

输入访问 http://localhost:81/movie

即会跳转到相应 url 进行访问资源

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

2、application.properties 或 application.yml 配置文件

即 SpringCloud - Spring Cloud 之 Gateway网关(十三)_MinggeQingchun的博客-CSDN博客

文中的 三、Gateway应用方式 

application.properties

server.port=81

#eureka注册中心首页的Application这一栏
spring.application.name=springcloud-7-service-eureka-gateway

#每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然“存活”
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
eureka.instance.lease-expiration-duration-in-seconds=10
#告诉服务端,服务实例以IP作为链接,不是取机器名
eureka.instance.prefer-ip-address=false

#注册服务实例ID,,服务ID必须唯一 springcloud-7-service-eureka-gateway
eureka.instance.instance-id=${spring.application.name}:${server.port}
#注册中心的链接地址  http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

#网关路由配置
#开启网关,默认开启
spring.cloud.gateway.enabled=true
#节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
#路由 id,没有固定规则,但唯一
spring.cloud.gateway.routes[0].id=login-service-route
#匹配后提供服务的路由地址;uri统一资源定位符   url 统一资源标识符
spring.cloud.gateway.routes[0].uri=http://localhost:9001
#以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
#断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
#也可以全局匹配,如 /service/**
spring.cloud.gateway.routes[0].predicates[0]=Path=/doLogin
#只能是 GET 请求时,才能访问
spring.cloud.gateway.routes[0].predicates[0]=Method=GET,POST

#配置第二个路由规则
spring.cloud.gateway.routes[1].id=admin-service-route
spring.cloud.gateway.routes[1].uri=http://localhost:9001
spring.cloud.gateway.routes[1].predicates[0]=Path=/doAdmin
spring.cloud.gateway.routes[1].predicates[0]=Method=GET,POST

#表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
spring.cloud.gateway.discovery.locator.enabled=true
#是将请求路径上的服务名配置为小写(服务注册的时候,向注册中心注册时将服务名转成大写了),如以/service/*的请求路径被路由转发到服务名为service的服务上
spring.cloud.gateway.discovery.locator.lower-case-service-id=true

application.yml 

server:
  port: 81

#eureka注册中心首页的Application这一栏
spring:
  application:
    name: springcloud-7-service-eureka-gateway
  #网关路由配置
  cloud:
    gateway:
      #开启网关,默认开启
      enabled: true
      #节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
      routes:
        #路由 id,没有固定规则,但唯一
        - id: login-service-route
          #匹配后提供服务的路由地址;uri统一资源定位符   url 统一资源标识符
          uri: http://localhost:9001
          #以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
          predicates:
            #断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
            #也可以全局匹配,如 /service/**
            - Path=/doLogin
            #只能是 GET,POST 请求时,才能访问
            - Method=GET,POST
        #配置第二个路由规则
        - id: admin-service-route
          uri: http://localhost:9001
          predicates:
            - Path=/doAdmin
            - Method=GET,POST
    #表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务
    discovery:
      locator:
        enabled: true
        #是将请求路径上的服务名配置为小写(服务注册的时候,向注册中心注册时将服务名转成大写了),如以/service/*的请求路径被路由转发到服务名为service的服务上
        lower-case-service-id: true

eureka:
  instance:
    #每间隔5s,向Eureka服务注册中心发送一次心跳,证明服务是否依然“存活”
    lease-renewal-interval-in-seconds: 2
    #告诉服务端,如果10s之内没有发送心跳,就代表故障,将本服务踢出
    lease-expiration-duration-in-seconds: 10
    #告诉服务端,服务实例以IP作为链接,不是取机器名
    prefer-ip-address: false
    #注册服务实例ID,,服务ID必须唯一 springcloud-7-service-eureka-gateway
    instance-id: ${spring.application.name}:${server.port}
  #注册中心的链接地址  http://eureka8761:8761/eureka,http://eureka8762:8762/eureka,http://eureka8763:8763/eureka
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka


3、Gateway 微服务名动态路由,负载均衡 

我们设置的 routes的 uri 都是写死的,这样不符合微服务的要求,微服务是只要知道服务的名字,根据名字去找,且直接写死URI就没有负载均衡的效果

默认情况下 Gateway 会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路

由进行转发,从而实现动态路由的功能

uri 的协议(如 http 协议)为 lb(load Balance),表示启用 Gateway 的负载均衡功能。

lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri

#匹配后提供服务的路由地址;uri统一资源定位符   url 统一资源标识符
#uri 的协议为 lb(load Balance),表示启用 Gateway 的负载均衡功能
#lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri;serviceName要和启动的微服务名保持一致
spring.cloud.gateway.routes[0].uri=lb://springcloud-7-service-eureka-gateway-login

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

#eureka注册中心首页的Application这一栏
spring:
  application:
    name: springcloud-7-service-eureka-gateway
  #网关路由配置
  cloud:
    gateway:
      #开启网关,默认开启
      enabled: true
      #节点 routes 是一个List 对象,其中 routes 集合中中又包含多个对象,每个对象有三个属性(一个 索引[0]代表一个对象)
      routes:
        #路由 id,没有固定规则,但唯一
        - id: login-service-route
          #匹配后提供服务的路由地址;uri统一资源定位符   url 统一资源标识符
          #uri: http://localhost:9001
          #uri 的协议为 lb(load Balance),表示启用 Gateway 的负载均衡功能
          #lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri;serviceName要和启动的微服务名保持一致
          uri: lb://springcloud-7-service-eureka-gateway-login
          #以下是断言条件,必选全部符合条件;断言是给某一个路由来设定的一种匹配规则 默认不能作用在动态路由上
          predicates:
            #断言,路径匹配,只要Path匹配上了/doLogin 就往 uri 转发 并且将路径带上 注意:Path 中 P 为大写
            #也可以全局匹配,如 /service/**
            - Path=/doLogin
            #只能是 GET,POST 请求时,才能访问
            - Method=GET,POST

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

浏览器输入访问http://localhost:81/springcloud-7-service-eureka-gateway-login/doLogin

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

二、Predicate 谓词/断言

路由断言/谓词工厂有12个

Spring Cloud Gateway

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter  

Gateway 启动时会去加载一些路由断言工厂(一个 boolean 表达式 )

Spring Cloud Gateway

我们可以通过上述官网查看断言表达式 

断言就是路由添加一些条件(通俗的说,断言就是一些布尔表达式,满足条件的返回 true,不满足的返回 false)

Spring Cloud Gateway 将路由作为 Spring WebFlux HandlerMapping 基础架构的一部分进行匹配。

Spring Cloud Gateway 包括许多内置的路由断言工厂。所有这些断言都与 HTTP请求的不同属性匹配。可以将多个路由断言可以组合使用

Spring Cloud Gateway 创建对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

规则 实例 说明
Path - Path=/gate/,/rule/ ## 当请求的路径为gate、rule开头的时,转发到http://localhost:9023服务器上
Before - Before=2020-01-20T17:42:47.789-07:00[America/Denver] 在某个时间之前的请求才会被转发到 http://localhost:9023服务器上
After - After=2020-01-20T17:42:47.789-07:00[America/Denver] 在某个时间之后的请求才会被转发
Between - Between=2020-01-20T17:42:47.789-07:00[America/Denver],2020-01-21T17:42:47.789-07:00[America/Denver] 在某个时间段之间的才会被转发
Cookie - Cookie=chocolate, ch.p 名为chocolate的表单或者满足正则ch.p的表单才会被匹配到进行请求转发
Header - Header=X-Request-Id, \d+ 携带参数X-Request-Id或者满足\d+的请求头才会匹配
Host - Host=www.hd123.com 当主机名为www.hd123.com的时候直接转发到http://localhost:9023服务器上
Method - Method=GET 只有GET方法才会匹配转发请求,还可以限定POST、PUT等请求方式

application.yml配置文件

server:
  port: 81

spring:
  application:
    name: gateway-81
  cloud:
    gateway:
      enabled: true #开启网关,默认是开启的
      routes: #设置路由,注意是数组,可以设置多个,按照 id 做隔离
        - id: user-service #路由 id,没有要求,保持唯一即可
          uri: lb://provider #使用 lb 协议 微服务名称做负均衡
          predicates: #断言匹配
            - Path=/info/** #和服务中的路径匹配,是正则匹配的模式
            - After=2020-01-20T17:42:47.789-07:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之后的请求,ZonedDateTime dateTime=ZonedDateTime.now()获得
            - Before=2020-06-18T21:26:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定 日期时间之前的请求
            - Between=2020-06-18T21:26:26.711+08:00[Asia/Shanghai],2020-06-18T21:32:26.711+08:00[Asia/Shanghai] #此断言匹配发生在指定日期时间之间的请求
            - Cookie=name,xiaobai #Cookie 路由断言工厂接受两个参数,Cookie 名称和 regexp(一 个 Java 正则表达式)。此断言匹配具有给定名称且其值与正则表达式匹配的 cookie
            - Header=token,123456 #头路由断言工厂接受两个参数,头名称和 regexp(一个 Java 正 则表达式)。此断言与具有给定名称的头匹配,该头的值与正则表达式匹配。
            - Host=**.bai*.com:* #主机路由断言工厂接受一个参数:主机名模式列表。该模式是一 个 ant 样式的模式。作为分隔符。此断言匹配与模式匹配的主机头 
            - Method=GET,POST #方法路由断言工厂接受一个方法参数,该参数是一个或多个参数: 要匹配的 HTTP 方法 
            - Query=username,cxs #查询路由断言工厂接受两个参数:一个必需的 param 和一个 可选的 regexp(一个 Java 正则表达式)。 
            - RemoteAddr=192.168.1.1/24 #RemoteAddr 路由断言工厂接受一个源列表(最小大小 1), 这些源是 cidr 符号(IPv4 或 IPv6)字符串,比如 192.168.1.1/24(其中 192.168.1.1 是 IP 地址,24 是子网掩码)。

1、After路由谓词工厂

After route谓词工厂采用一个参数,即datetime(这是一个Java ZonedDateTime),该谓词匹配在指定日期时间之后发生的请求,以下示例配置了路由后谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

这条路由符合2017年1月20日17:42:47时间([America/Denver])之后的任何请求;

时间通过获取:System.out.println(ZonedDateTime.now());

2、Before路由谓词工厂

Before路由谓词工厂采用一个参数,即datetime(这是一个Java ZonedDateTime),该谓词匹配在指定日期时间之前发生的请求,下面的示例配置路由之前谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: before_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

这条路由符合2017年1月20日17:42:47时间([America/Denver])之前的任何请求;

3、Between路由谓词工厂

路由谓词之间的工厂使用两个参数datetime1和datetime2,它们是java ZonedDateTime对象,该谓词匹配在datetime1之后和datetime2之前发生的请求,datetime2参数必须在datetime1之后,以下示例配置了路由之间的谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: between_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]

该路线与2017年1月20日山区时间(丹佛)之后和2017年1月21日17:42山区时间(丹佛)之前的任何请求相匹配,这对于维护时段可能很有用;

4、Cookie 路由谓词工厂

Cookie路由谓词工厂采用两个参数,即cookie名称和一个regexp(这是Java正则表达式),该谓词匹配具有给定名称且其值与正则表达式匹配的cookie,以下示例配置Cookie路由谓词工厂:

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

此路由匹配具有名为Chocolate的cookie的请求,该cookie的值与ch.p正则表达式匹配;

举例:curl http://192.168.0.104/index --cookie token=123456

5、Header 路由谓词工厂

header 路由谓词工厂使用两个参数,header 名称和一个regexp(这是Java正则表达式),该谓词与具有给定名称的header 匹配,该header 的值与正则表达式匹配,以下示例配置标头路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+

如果请求具有名为X-Request-Id的标头,且其值与\ d +正则表达式匹配(即,其值为一个或多个数字),则此路由匹配;

举例:curl http://192.168.0.104/index --header "X-Request-Id:19228"

6、Host 路由谓词工厂

host路由谓词工厂使用一个参数:主机名模式列表,以下示例配置主机路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

还支持URI模板变量(例如{sub} .myhost.org),如果请求的主机标头的值为www.somehost.org或beta.somehost.org或www.anotherhost.org,则此路由匹配;

7、Method 路由谓词工厂

方法路由谓词工厂使用方法参数,该参数是一个或多个参数:要匹配的HTTP方法,以下示例配置方法route谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST

如果请求方法是GET或POST,则此路由匹配;

8、Path路由谓词工厂

路径路由谓词工厂使用两个参数:Spring PathMatcher模式列表和一个称为matchOptionalTrailingSeparator的可选标志,以下示例配置路径路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/{segment},/blue/{segment}

如果请求路径为例如/red/1或/red/blue或/blue/green,则此路由匹配;

9、Query路由谓词工厂

查询路由谓词工厂采用两个参数:必需的参数和可选的regexp(这是Java正则表达式),以下示例配置查询路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

如果请求包含green查询参数,则前面的路由匹配;

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=red, gree.

如果请求包含值与gree匹配的red查询参数,则上述路由匹配;

10、RemoteAddr 路由谓词工厂

RemoteAddr路由谓词工厂使用源列表(最小大小为1),这些源是标记(IPv4或IPv6)字符串,例如192.168.0.1/16(其中192.168.0.1是IP地址,而16是子网掩码)),下面的示例配置RemoteAddr路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

如果请求的远程地址是例如192.168.1.10,则此路由匹配;

11、Weight 路由谓词工厂

权重路由谓词工厂采用两个参数:group和weight(一个int),权重是按组计算的,以下示例配置权重路由谓词:

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

这条路由会将约80%的流量转发至weighthigh.org,并将约20%的流量转发至weightlow.org;

12、XForwarded 路由谓词工厂

spring:
  cloud:
    gateway:
      routes:
      - id: xforwarded_remoteaddr_route
        uri: https://example.org
        predicates:
        - XForwardedRemoteAddr=192.168.1.1/24

如果x - forward - for报头包含,例如192.168.1.10,则此路由匹配

一、自定义谓词/断言

1、首先定义一个配置类,用于承载配置参数

@Data //lombok
public class TokenConfig {
    private String token;
}

2、定义一个路由谓词工厂

@Slf4j
@Component
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenConfig> {

    public TokenRoutePredicateFactory() {
        super(TokenConfig.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("token");
    }

    @Override
    public Predicate<ServerWebExchange> apply(TokenConfig tokenConfig) {
        // (T t) -> true
        return exchange -> {
            MultiValueMap<String, String> valueMap = exchange.getRequest().getQueryParams();

            boolean flag = false;

            List<String> list = new ArrayList<>();

            valueMap.forEach((k, v) -> {
                list.addAll(v);
            });

            for (String s : list) {
                log.info("Token -> {} ", s);
                if (StringUtils.equalsIgnoreCase(s, tokenConfig.getToken())) {
                    flag = true;
                    break;
                }
            }
            return flag;
        };
    }
}

注:TokenRoutePredicateFactory类,前面的Token与.yml配置文件里面配置的名字对应,后面的RoutePredicateFactory名字是固定的,不能随便写,这是Spring Cloud Gateway的约定,类名须为“谓词工厂名(如:Token)” + RoutePredicateFactory

3、在配置文件中启用该路由谓词工厂,即配置一个Token=123456

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

时间格式不是随便配置,而是Spring Cloud Gateway的默认时间格式,采用JDK8里面的格式化:

DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
String nowTime = dateTimeFormatter.format(ZonedDateTime.now());
System.out.println(nowTime);

二、谓词/断言不匹配404处理

处理的顶层接口是WebExceptionHandler

默认实现是DefaultErrorWebExceptionHandler

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

需要覆盖它的默认实现DefaultErrorWebExceptionHandler,覆盖里面的方法,在方法里面编写返回的结果

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.web.reactive.function.server.*;

import java.util.HashMap;
import java.util.Map;

public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes,
                                      ResourceProperties resourceProperties,
                                      ErrorProperties errorProperties,
                                      ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 获取异常属性
     */
    /*@Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        int code = 500;
        Throwable error = super.getError(request);
        if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
            code = 404;
        }
        return response(code, this.buildMessage(request, error));
    }*/

    /**
     * 指定响应处理方法为JSON处理的方法
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * 根据code获取对应的HttpStatus
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        int statusCode = (int) errorAttributes.get("status");
        return statusCode;
    }

    /**
     * 构建异常信息
     * @param request
     * @param ex
     * @return
     */
    private String buildMessage(ServerRequest request, Throwable ex) {
        StringBuilder message = new StringBuilder("Failed to handle request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if (ex != null) {
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }

    /**
     * 构建返回的JSON数据格式
     *
     * @param status		状态码
     * @param errorMessage  异常信息
     * @return
     */
    public static Map<String, Object> response(int status, String errorMessage) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", status);
        map.put("message", errorMessage);
        map.put("data", null);
        return map;
    }
}

三、Filter 过滤器

Filter有33个

Spring Cloud Gateway

Gateway 里面的过滤器和 Servlet 里面的过滤器,功能差不多,路由过滤器可以用于修改进入

Http 请求和返回 Http 响应

Gateway 中过滤器

1、按生命周期

pre 在业务逻辑之前

post 在业务逻辑之后

2、按种类

GatewayFilter 需要配置某个路由,才能过滤。如果需要使用全局路由,需要配置 DefaultFilters

GlobalFilter 全局过滤器,不需要配置路由,系统初始化作用到所有路由上

全局过滤器 统计请求次数;限流;token 的校验;ip 黑名单拦截 ;跨域本质(filter) ;144 开头的电话;限制一些 ip 的访问

1、自定义 GlobalFilter 全局过滤器

GlobalFilter 是一种作用于所有的路由上的全局过滤器,通过它,我们可以实现一些统一化的业务功能,例如权限认证、IP 访问限制等。当某个请求被路由匹配时,那么所有的 GlobalFilter 会和该路由自身配置的 GatewayFilter 组合成一个过滤器链

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.HttpHeaders;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashMap;

/**
 * 自定义全局过滤器
 */
@Component
public class GlobalFilterConfig implements GlobalFilter, Ordered {

    /**
     * 过滤方法
     * 过滤器链模式;责任链模式
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //针对请求的过滤,拿到请求的header、url、参数等

        // HttpServletRequest  是web里面的
        // ServerHttpRequest   是webFlux里面(响应式)
        ServerHttpRequest request = exchange.getRequest();

        String path = request.getURI().getPath();
        System.out.println("path====" + path);

        HttpHeaders headers = request.getHeaders();
        System.out.println("headers====" + headers);

        String methodName = request.getMethod().name();
        System.out.println("methodName====" + methodName);

        //IPV4、IPV6地址
        String hostName = request.getRemoteAddress().getHostName();
        System.out.println("hostName====" + hostName);

        String ip = request.getHeaders().getHost().getHostString();
        System.out.println("ip====" + ip);


        // 响应相关的数据
        ServerHttpResponse response = exchange.getResponse();
        //响应头设置编码
        response.getHeaders().set("content-type","application/json;charset=utf-8");

        HashMap<String ,Object> map = new HashMap<>(4);
        map.put("code", HttpStatus.UNAUTHORIZED.value());
        map.put("msg","你未授权");

        ObjectMapper objectMapper = new ObjectMapper();
        //将一个map转成一个字节数组
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //通过buffer工厂将字节数组包装成一个数据包
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));

        // 放行 到下一个过滤器
//        return chain.filter(exchange);
    }

    /**
     * 指定顺序的方法,越小越先执行
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

2、IP、Token认证拦截

1、token认证拦截

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
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.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
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.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * token校验
 */
@Component
public class TokenCheckFilter implements GlobalFilter, Ordered {
    /**
     * 指定好放行的路径
     */
    public static final List<String> ALLOW_URL = Arrays.asList("/springcloud-7-service-eureka-gateway-login/doLogin", "/myUrl","/doLogin");

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 和前端约定好 一般放在请求头里面 一般 key   Authorization   value bearer token
     * 1、拿到请求url
     * 2、判断放行
     * 3、拿到请求头
     * 4、拿到token
     * 5、校验
     * 6、放行/拦截
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        if (ALLOW_URL.contains(path)) {
            return chain.filter(exchange);
        }
        // 检查
        HttpHeaders headers = request.getHeaders();
        List<String> authorization = headers.get("Authorization");
        if (!CollectionUtils.isEmpty(authorization)) {
            String token = authorization.get(0);
            if (StringUtils.hasText(token)) {
                // 约定好的有前缀的 bearer token
                String realToken = token.replaceFirst("bearer ", "");
                if (StringUtils.hasText(realToken) && redisTemplate.hasKey(realToken)) {
                    return chain.filter(exchange);
                }
            }
        }
        // 拦截
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type","application/json;charset=utf-8");

        HashMap<String, Object> map = new HashMap<>(4);
        // 返回401
        map.put("code", HttpStatus.UNAUTHORIZED.value());
        map.put("msg","未授权");
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

    /**
     * 这个顺序 最好在0附近  -2 -1 0 1
     * @return
     */
    @Override
    public int getOrder() {
        return 1;
    }

2、IP认证拦截

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.ServerHttpRequest;
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.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * 网关里面 过滤器
 * ip拦截
 * 请求都有一个源头
 * 电话 如:144 开头
 * 请求------->gateway------->service
 * 黑名单 black_list
 * 白名单 white_list
 * 根据数量
 * 像具体的业务服务 一般黑名单
 * 一般像数据库 用白名单
 */
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

    /**
     * 网关的并发比较高,不要在网关里面直接操作mysql
     * 后台系统可以查询数据库,用户量并发量不大
     * 如果并发量大 可以查redis 或者 在内存中写好
     */
    public static final List<String> BLACK_LIST = Arrays.asList("127.0.0.1", "144.128.139.137");

    /**
     * 1、获取到ip
     * 2、校验ip是否符合规范
     * 3、放行 OR 拦截
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String ip = request.getHeaders().getHost().getHostString();

        // 查询数据库 看这个ip是否存在黑名单里面
        if (!BLACK_LIST.contains(ip)){
            return chain.filter(exchange);
        }

        //拦截
        //注:此处如果拦截IP,就要放开 GlobalFilter 的拦截
        ServerHttpResponse response = exchange.getResponse();
        response.getHeaders().set("content-type","application/json;charset=utf-8");
        HashMap<String, Object> map = new HashMap<>(4);
        map.put("code", 438);
        map.put("msg","你是黑名单");

        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = new byte[0];
        try {
            bytes = objectMapper.writeValueAsBytes(map);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(wrap));
    }

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

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

3、限流

限流就是限制一段时间内,用户访问资源的次数,减轻服务器压力,限流大致分为两种:

1、IP 限流(5s 内同一个 ip 访问超过 3 次,则限制不让访问,过一段时间才可继续访问)

2.、请求量限流(只要在一段时间内(窗口期),请求次数达到阀值,就直接拒绝后面来的访问了,

过一段时间才可以继续访问)(粒度可以细化到一个 api(url),一个服务)

限流模型:漏斗算法 ,令牌桶算法,窗口滑动算法 计数器算法

令牌桶算法

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

(1)所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;

(2)根据限流大小,设置按照一定的速率往桶里添加令牌

(3)桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;

(4)请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;

(5)令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令

牌,以此保证足够的限流

Spring Cloud Gateway 已经内置了一个 RequestRateLimiterGatewayFilterFactory可以直接使用。

目前 RequestRateLimiterGatewayFilterFactory 的实现依赖于 Redis,所以我们还要引入 spring-boot-starter-data-redis-reactive 依赖

<!--限流要引入 Redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>

配置类

import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;

/**
 * 自定义请求限流
 */
@Configuration
public class RequestRateLimiterConfig {

    // 针对某一个接口,ip来限流,如/doLogin,每一个ip,10s只能访问3次
    @Bean
    @Primary // 主候选的
    public KeyResolver ipKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());
    }

    // 针对这个路径来限制  /doLogin
    // api 就是 接口  外面一般把gateway    api网关  新一代网关
    @Bean
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }
}

需要添加一个  @Primary // 主候选的 注解,不然报如下错误

spring.cloud.gateway.routes,SpringCloud,SpringBoot,spring cloud,Gateway,Routes,Predicate,Filter

启动快速访问 

4、CORS跨域配置

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedMethod("*");
        config.addAllowedOrigin("*");
        config.addAllowedHeader("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}
或者 yml 文件配置
spring: 
    cloud: 
        gateway: 
            globalcors: 
                corsConfigurations: '[/**]': // 针对哪些路径 
                allowCredentials: true // 这个是可以携带 cookie
                allowedHeaders: '*' 
                allowedMethods: '*' allowedOrigins: '*'

四、Spring Cloud Gateway集成ribbon负载均衡

实现原理是在全局LoadBalancerClientFilter中进行拦截,然后再该过滤器中依赖LoadBalancerClient loadBalancer,而此负载均衡接口的具体实现是RibbonLoadBalancerClient,即spring cloud gateway已经整合好了ribbon,已经可以实现负载均衡,我们不需要做任何工作,网关对后端微服务的转发就已经具有负载均衡功能;

1、添加依赖

<!-- sentinel-spring-cloud-gateway-adapter -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
    <version>1.7.2</version>
</dependency>

2、添加sentinel控制台配置

#sentinel dashboard管理后台
spring:
  cloud:
    sentinel:
      eager: true
      transport:
        dashboard: 192.168.133.128:8080

3、在spring容器中配置一个Sentinel的全局过滤器文章来源地址https://www.toymoban.com/news/detail-621385.html

@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
    return new SentinelGatewayFilter();
}

到了这里,关于SpringCloud - Spring Cloud 之 Gateway网关,Route路由,Predicate 谓词/断言,Filter 过滤器(十三)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Cloud Gateway 监控、多网关实例路由共享 | Spring Cloud 18

    Actuator 是 Spring Boot 提供的用来对应用系统进行监控的功能模块,借助于 Actuator 开发者可以很方便地对应用系统某些监控指标进行查看、统计等。 Actuator 的核心是端点 Endpoint 。 Endpoint 可以让我们监视应用程序并与其交互。 Spring Boot 包含许多内置端点,并允许您添加自己的端

    2024年02月09日
    浏览(49)
  • 【深入解析spring cloud gateway】02 网关路由断言

    断言是路由配置的一部分,当断言条件满足,即执行Filter的逻辑,如下例所示 当请求路径满足条件/red/,即添加头信息:X-Request-Red,value为Blue-{segment},segment是路径里面带的信息。 gateWay的主要功能之一是转发请求,转发规则的定义主要包含三个部分 Route(路由) 路由是网关

    2024年02月09日
    浏览(31)
  • 第八章 : Spring cloud 网关中心 Gateway (动态路由)

    第八章 : Spring cloud 网关中心 Gateway (动态路由) 前言 本章知识点:重点介绍动态网关路由的背景、动态路由与静态路由的概念,以及如何基于Nacos实现动态网关路由 的实战案例。 背景 前面章节介绍了Spring Cloud Gateway提供的配置路由规则的两种方法,但都是在Spring Cloud Ga

    2024年01月19日
    浏览(38)
  • 第七章 : Spring cloud 网关中心 Gateway (静态路由)

    第七章 : Spring cloud 网关中心 Gateway (静态路由) 前言 本章知识点:本章将会介绍什么是Spring Cloud Gateway、为什么会出现Spring Cloud Gateway,以及Spring Cloud Gateway的工作原理和实战用法,以及Spring Cloud Gateway 路由概念以及基于nacos注册中心Spring Cloud Gateway 静态路由的实战。 什么

    2024年02月02日
    浏览(36)
  • SpringCloud(五)Gateway 路由网关

    官网地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/ 我们需要连接互联网,那么就需要将手机或是电脑连接到家里的路由器才可以,而路由器则连接光猫,光猫再通过光纤连接到互联网,也就是说,互联网方向发送过来的数据,需要经过路由器才能到达我们的设

    2024年02月16日
    浏览(30)
  • SpringCloud之 Gateway路由网关

    提示:以下是本篇文章正文内容,SpringCloud 系列学习将会持续更新 官网地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/ 说到路由,想必各位一定最先想到的就是家里的路由器了,那么我们家里的路由器充当的是一个什么角色呢? 我们知道,如果我们需要连接互

    2024年02月06日
    浏览(34)
  • 【Spring Cloud 八】Spring Cloud Gateway网关

    【Spring Cloud一】微服务基本知识 【Spring Cloud 三】Eureka服务注册与服务发现 【Spring Cloud 四】Ribbon负载均衡 【Spring Cloud 五】OpenFeign服务调用 【Spring Cloud 六】Hystrix熔断 【Spring Cloud 七】Sleuth+Zipkin 链路追踪 在项目中是使用了Gateway做统一的请求的入口,以及统一的跨域处理以及

    2024年02月12日
    浏览(34)
  • Spring Cloud 之 Gateway 网关

    🍓 简介:java系列技术分享(👉持续更新中…🔥) 🍓 初衷:一起学习、一起进步、坚持不懈 🍓 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正🙏 🍓 希望这篇文章对你有所帮助,欢迎点赞 👍 收藏 ⭐留言 📝 🍓 更多文章请点击 Gateway官网 :https://spring.io/projects/

    2024年02月16日
    浏览(27)
  • Spring cloud教程Gateway服务网关

    写在前面的话: 本笔记在参考网上视频以及博客的基础上,只做个人学习笔记,如有侵权,请联系删除,谢谢! Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提

    2024年02月08日
    浏览(35)
  • SpringCloud Gateway网关多路由配置访问404解决方案

    现象:网关成功注册进Eureka,但是通过网关访问时出现404 可以通过以下几种方式尝试解决,不是必须都配置,根据自己项目情况处理。 配置 filters: - StripPrefix=1,与路由id同级,去除前缀 网关中如果有 spring-boot-starter-actuator、spring-boot-starter-web 依赖,删除。 启动类没有直接写

    2024年02月17日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包