一. 前言
Spring Cloud Gateway 根据过滤器Filter
的作用范围划分为GatewayFilter
和 GlobalFilter
,二者区别如下:
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。
三. 内置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
时要回调的路径,当调用 Hystrix
的fallback
被调用时,请求将转发到/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来决定是否开启打印时间的日志了。