一. 前言

Spring Cloud Gateway 根据过滤器Filter的作用范围划分为GatewayFilterGlobalFilter,二者区别如下:
GatewayFilter : GatewayFilter称为内置过滤器,需要通过 spring.cloud.routes.filters配置在具体路由下,只作用在当前路由上或者特定路由上,可以通过配置 spring.cloud.default-filters,表明作用在所有路由上,GatewayFilter允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway提供了许多内置的GatewayFilter工厂。

GlobalFilter: GlobalFilter称为全局过滤器,不需要在配置文件中配置,作用在所有的路由上,全局有效。

二. GatewayFilter(内置过滤器)

内置提供的网关过滤器用于拦截并链式处理 Web 请求,可以实现横切与应用无关的需求,比如:安全、访问超时的设置等。修改传入的 HTTP 请求或传出 HTTP 响应。Spring Cloud Gateway 包含许多内置的网关过滤器工厂一共有 22 个,包括头部过滤器、 路径类过滤器、Hystrix 过滤器和重写请求 URL 的过滤器, 还有参数和状态码等其他类型的过滤器。根据过滤器工厂的用途来划分,可以分为以下几种:Header、Parameter、Path、Body、Status、Session、Redirect、Retry、RateLimiter 和 Hystrix。

spring gateway 可以设置timeout吗 spring gateway教程_gateway

三. 内置GatewayFilter介绍

Spring Cloud Gateway中内置了很多过滤器,实现类有二十多个,下面挑一些常见的重点列举一下:

3.1 AddRequestHeader

给请求加上一条header信息;

spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #通过Path匹配路由
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
          filters:
            - AddRequestHeader=X-Request-red, blue

此清单将X-Request-red:blue标头添加到所有匹配请求的下游请求的标头中。

3.2 AddRequestParameter

给请求加上Paramter参数

spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #通过Path匹配路由
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
          filters:
            - AddRequestParameter=key,value

这将添加red=blue到所有匹配请求的下游请求的查询字符串中。

3.3 AddResponseHeader

对网关的响应添加Header

spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #通过Path匹配路由
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
          filters:
            - AddResponseHeader=X-Response-Red, Blue

这会将X-Response-Foo:Bar标头添加到所有匹配请求的下游响应的标头中。

3.4 StripPrefix

用于去除url的前缀

spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #通过Path匹配路由
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
          filters:
            - StripPrefix=1

上面这个配置的例子表示,当请求路径匹配到/product/**会将包含product和后边的字符串接去掉转发, StripPrefix=1就代表截取路径的个数,这样配置后当请求http://127.0.0.1:8015/product/getProductInfo后端匹配到的请求,路由转发后的路径就会变成http://127.0.0.1:8016/getProductInfo,说明请求路径中的/product/已经被截取。

3.5 PrefixPath

PrefixPath Filter 的作用和 StripPrefix 正相反,是在 URL 路径前面添加一部分的前缀。

spring:
  application:
    name: api-gateway   # 应用名称
  cloud:
    gateway:
      discovery:
        locator:
          # 是否与服务发现组件进行结合,通过 serviceId 转发到具体服务实例。
          enabled: true      # 是否开启基于服务发现的路由规则
          lower-case-service-id: true  # 是否将服务名称转小写
      # 路由规则
      routes:
        #通过Path匹配路由
        - id: product-service        # 路由 ID,唯一
          uri: lb://product-service  # 目标 URI,路由到微服务的地址
          predicates:                # 断言(判断条件)
            - Path=/product/**       # 匹配对应 URL 的请求,将匹配到的请求追加在目标 URI 之后
          filters:
            - PrefixPath=/mypath

3.6 Retry

当求出现异常时进行重试
重试GatewayFilter工厂支持以下参数:

  • retries: 应尝试的重试次数。
  • statuses: 应重试的HTTP状态代码,以表示org.springframework.http.HttpStatus。
  • methods: 应重试的HTTP方法,以表示org.springframework.http.HttpMethod。
  • series: 要重试的一系列状态代码,使用表示org.springframework.http.HttpStatus.Series。
  • exceptions: 应重试的引发异常的列表。
  • backoff: 为重试配置的指数补偿。重试在的退避间隔后执行firstBackoff * (factor ^ n),其中n为迭代。如果maxBackoff已配置,则应用的最大补偿限制为maxBackoff。如果basedOnPreviousValue为true,则使用计算退避prevBackoff * factor。

Retry如果启用了以下默认过滤器配置:

  • retries: 3次
  • series: 5XX系列
  • methods: GET方法
  • exceptions: IOException和TimeoutException
  • backoff: 禁用

以下清单配置了Retry GatewayFilter

例子3.6. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: retry_test
        uri: http://localhost:8080/flakey
        predicates:
        - Host=*.retry.com
        filters:
        - name: Retry
          args:
            retries: 3
            statuses: BAD_GATEWAY
            backoff:
              firstBackoff: 10ms
              maxBackoff: 50ms
              factor: 2
              basedOnPreviousValue: false

3.7 Hystrix

Hystrix是Netflix的一个库,用于实现断路器模式。Hystrix GatewayFilter允许您将断路器引入网关路由,保护您的服务免受级联故障的影响,并在下游故障时提供后备响应。

要GatewayFilter在项目中启用Hystrix 实例,请spring-cloud-starter-netflix-hystrix从Spring Cloud Netflix添加依赖项。

Hystrix GatewayFilter工厂需要一个name参数,即参数的名称HystrixCommand。以下示例配置Hystrix GatewayFilter:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

例子3.7. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: https://example.org
        filters:
        - Hystrix=myCommandName

这会将其余过滤器包装在HystrixCommand命令名称为的中myCommandName。

Hystrix过滤器还可以接受可选fallbackUri参数。当前,仅forward:支持计划的URI。如果调用了后备,则请求将转发到与URI匹配的控制器。以下示例配置了这种后备:

例子21. application.yml

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: lb://backing-service:8088
        predicates:
        - Path=/consumingserviceendpoint
        filters:
        - name: Hystrix
          args:
            name: fallbackcmd
            fallbackUri: forward:/incaseoffailureusethis

fallbackUri: forward:/incaseoffailureusethis配置了fallback时要回调的路径,当调用 Hystrixfallback被调用时,请求将转发到/incaseoffailureuset这个 URI

您可以使用应用程序属性设置全局默认值或逐条路由配置Hystrix的超时时间(例如超时),要为上述显示的示例路由设置五秒钟的超时时间,可以使用以下配置:

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 5000

Ribbon+Feign+Hystrix完成配置超时设置:

feign:
  hystrix:
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 5000
ribbon:
  ReadTimeout: 5000
  ConnectTimeout: 5000

四. 自定义GatewayFilter

创建一个自定的filter,功能:打印请求的耗时

/**
 * 自定义GatewayFilter
 * 打印一条请求下的耗时
 */
public class RequestTimeFilter implements GatewayFilter, Ordered {

    private static final Log log = LogFactory.getLog(GatewayFilter.class);
    private static final String REQUEST_TIME_BEGIN = "requestTimeBegin";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis());
        return chain.filter(exchange).then(
                Mono.fromRunnable(() -> {
                    Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN);
                    if (startTime != null) {
                        log.info(exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms");
                    }
                })
        );

    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

代码解析:
Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。
还有有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。
然后将该过滤器注册到router中,代码如下:

@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
            .route(r -> r.path("/demo/**")
                    .filters(f -> f.filter(new RequestTimeFilter()))
                    .uri("http://httpbin.org:80/get")
                    .order(0)
                    .id("customer_filter_router")
            )
            .build();
}

执行程序,会打印出方法的耗时

五. 自定义过滤器工厂

4.1 继承AbstractGatewayFilterFactory

使用GatewayFilter,将应用注册到单个路由或一个分组的路由上,做一个可配置是否开启的打印请求耗时的过滤器开关,为Boolean类型值。

@Component
@Slf4j
public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<RequestTimeGatewayFilterFactory.Config> {

    private static final String KEY = "flag";

    /**
     * 封装yml传进来的参数
     */
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(KEY);
    }

    public RequestTimeGatewayFilterFactory() {
        super(Config.class);
    }

    /**
     * 实现拦截逻辑
     */
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            //pre过滤器
            exchange.getAttributes().put("requestTimeGateway", System.currentTimeMillis());
            return chain.filter(exchange).then(
                    Mono.fromRunnable(() -> {
                        //post过滤器
                        Long startTime = exchange.getAttribute("requestTimeGateway");
                        if (startTime != null) {
                            log.debug("请求路径:{},耗时:{}ms",exchange.getRequest().getURI().getRawPath(),System.currentTimeMillis() - startTime);
                            if (config.isFlag()) {
                                log.debug("参数:{}",exchange.getRequest().getQueryParams());
                            }
                        }
                    })
            );
        };
    }

    /**
     * 参数
     */
    public static class Config {
        private boolean flag;
        public boolean isFlag() {
            return flag;
        }
        public void setFlag(boolean flag) {
            this.flag = flag;
        }

    }
}

在上面的代码中 apply(Config config)方法内创建了一个GatewayFilter的匿名类,具体的实现逻辑跟之前一样,只不过加了是否打印请求参数的逻辑,而这个逻辑的开关是config.isFlag()。静态内部类类Config就是为了接收那个boolean类型的参数服务的,里边的变量名可以随意写,但是要重写List shortcutFieldOrder()这个方法。

需要注意的是,在类的构造器中一定要调用下父类的构造器把Config类型传过去,否则会报ClassCastException

最后,需要在工程的启动文件Application类中,向Srping Ioc容器注册RequestTimeGatewayFilterFactory类的Bean。

@Bean
public RequestTimeGatewayFilterFactory elapsedGatewayFilterFactory() {
   return new RequestTimeGatewayFilterFactory();
}

4.2 application.yml配置项

routes:
     #id标签配置的是router的id,每个router都需要一个唯一的id,
     - id: api-a
       uri: lb://web-demo
       predicates:
       #根据路由断言
         - Path=/a/**
       filters:
         #是否开启请求参数的输出
         - RequestTime=false  #通过配置false或者true来决定是否开启打印时间的日志

现在可以通过配置RequestTime=false或者true来决定是否开启打印时间的日志了。