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

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();
	}
}

 在 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 进行访问资源

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

#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

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

二、Predicate 谓词/断言

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

Spring Cloud Gateway

  

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

Spring Cloud Gateway

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

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

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

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

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

在这里插入图片描述

规则实例说明
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的默认时间格式,采用JDK8里面的格式化:

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

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

处理的顶层接口是WebExceptionHandler

默认实现是DefaultErrorWebExceptionHandler

需要覆盖它的默认实现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;
    }
}

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;
    }
}

3、限流

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

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

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

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

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

令牌桶算法

(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 // 主候选的 注解,不然报如下错误

启动快速访问 

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的全局过滤器

@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
    return new SentinelGatewayFilter();
}
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Gateway 提供了很多常用的过滤器,下面列举一些比较常用的过滤器及使用示例: 1. 添加请求头过滤器(AddRequestHeader Filter) 该过滤器可以在请求头中添加指定的键值对,示例: ``` spring: cloud: gateway: routes: - id: add_request_header_route uri: http://www.baidu.com predicates: - Path=/baidu/** filters: - AddRequestHeader=X-Request-Id, 123 ``` 上面的配置表示在请求转发到 `http://www.baidu.com` 的时候,会在请求头中添加一个键值对 `X-Request-Id: 123`。 2. 去重请求头过滤器(RemoveRequestHeader Filter) 该过滤器可以去掉指定的请求头,示例: ``` spring: cloud: gateway: routes: - id: remove_request_header_route uri: http://www.baidu.com predicates: - Path=/baidu/** filters: - RemoveRequestHeader=X-Request-Id ``` 上面的配置表示在请求转发到 `http://www.baidu.com` 的时候,会去掉请求头中的 `X-Request-Id`。 3. 重写路径过滤器(RewritePath Filter) 该过滤器可以重写请求路径,示例: ``` spring: cloud: gateway: routes: - id: rewrite_path_route uri: http://www.baidu.com predicates: - Path=/baidu/** filters: - RewritePath=/baidu/(?<segment>.*), /${segment} ``` 上面的配置表示在请求转发到 `http://www.baidu.com` 的时候,会将请求路径中的 `/baidu/` 去掉,例如请求 `/baidu/search` 会被重写为 `/search`。 4. 重试过滤器(Retry Filter) 该过滤器可以在请求失败的时候进行重试,示例: ``` spring: cloud: gateway: routes: - id: retry_route uri: http://www.baidu.com predicates: - Path=/baidu/** filters: - Retry=2, 5000, INTERNAL_SERVER_ERROR ``` 上面的配置表示当请求转发到 `http://www.baidu.com` 失败时,会进行最多 2 次重试,每次重试间隔 5000 毫秒,并且只有当返回状态码为 INTERNAL_SERVER_ERROR 时才会进行重试。 5. 限流过滤器(RequestRateLimiter Filter) 该过滤器可以对请求进行限流,示例: ``` spring: cloud: gateway: routes: - id: request_rate_limiter_route uri: http://www.baidu.com predicates: - Path=/baidu/** filters: - RequestRateLimiter=redis, #限流器类型 key-resolver=#{T(org.springframework.cloud.gateway.handler.predicate.SpELKeyResolver).resolve('request_remote_address')}, #限流器 key 算法 redis-rate-limiter.replenishRate=1, #令牌桶填充速率 redis-rate-limiter.burstCapacity=1 #令牌桶最大容量 ``` 上面的配置表示使用 Redis 实现限流,限制每个 IP 地址每秒只能请求一次。 以上是一些比较常用的 Spring Cloud Gateway 过滤器及使用示例,开发者可以根据自己的需求选择合适的过滤器来实现特定的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值