Spring Cloud Gateway自定义过滤器
Spring Cloud Gateway自定义过滤器背景最近项目需要切换网关,由zuul切换为spring cloud gateway,研究了部分spring cloud gateway能力,本文主要记录了spring cloud gateway如何自定义过滤器。创建Spring Cloud Gateway工程添加如下maven依赖引入Spring Cloud Gateway,这边我测试的工程使用
Spring Cloud Gateway自定义过滤器
背景
最近项目需要切换网关,由zuul切换为spring cloud gateway,研究了部分spring cloud gateway能力,本文主要记录了spring cloud gateway如何自定义过滤器。
创建Spring Cloud Gateway工程
添加如下maven依赖引入Spring Cloud Gateway,这边我测试的工程使用的Spring Cloud以及Spring Boot版本分别为Hoxton.SR12和2.3.12.RELEASE
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--SpringCloud 和 SpringBoot版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在工程中,我添加了一个路由,请求对于地址会被转发到百度。application.yml文件如下:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: baidu
uri: https://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- RedirectTo=302, https://www.baidu.com
开启debug日志模式,方便跟踪网关进程:
logging:
level:
org.springframework.cloud.gateway: DEBUG
reactor.netty.http.client: DEBUG
自定义Global Filters全局过滤器
全局过滤器,顾名思义,应用于全局,对于每一个经过网关的请求都会走到全局过滤器。全局过滤器可以做日志的监控以及鉴权等等功能。
前置全局过滤器
新增加一个过滤器,需要实现GlobalFilter接口,重写接口中的filter方法,并将其作为bean添加到Spring应用上下文中。
前置过滤器是在请求执行之前,比如在进行路由之前,我们可以添加对应的执行逻辑。
案例代码(代码中只记录了一行日志):
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Component
@Slf4j
public class PreGlobalCustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Global Pre Filter Execute...");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 1;
}
}
后置全局过滤器
后置过滤器是在调用链执行完成之后,运行一个新的Mono实例,这边没有具体仔细烟酒,只是先了解了如何编写和使用。
之前在zuul中,pre和post过滤器是根据重写filterType方法,自定义返回的类型来区分前置还是后置过滤器的,这边和Spring Cloud Gateway有区别。
案例代码(这边还是记录一行日志):
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Component
@Slf4j
public class PostGlobalCustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange)
.then(Mono.fromRunnable(
() -> log.info("Global Post Filter Execute...")
));
}
@Override
public int getOrder() {
return 1;
}
}
启动网关服务,通过网关服务请求/baidu/**地址,可以看到具体打印的日志信息
我们可以将前置过滤器和后置过滤器组合在一个过滤器中,效果和上面是一样的:
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Component
@Slf4j
public class GlobalCustomFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("Global Pre Filter Execute...");
return chain.filter(exchange)
.then(Mono.fromRunnable(
() -> log.info("Global Post Filter Execute...")
));
}
@Override
public int getOrder() {
return 1;
}
}
过滤器优先级
如果我们有多个自定义的过滤器,我们想让这些过滤器按照一定的顺序去执行,可以实现Ordered接口,重写getorder方法来定义过滤器执行的先后顺序,这边执行的逻辑是order的值越小,前置过滤器越先执行,后置过滤器越后执行。通过下面这张图可以更清晰的理解。这边大家可以自己编写几个过滤器进行验证,文章中就不具体举例了,可以参考我的代码库中的代码。
自定义GatewayFilters
上面的全局过滤器应用于全局,Spring Cloud Gateway也提供了另一种自定义过滤器,它的粒度更小,可以应用于我们需要指定的某些路由上。
编写过滤器
首先需要继承AbstractGatewayFilterFactory抽象类,它是一个泛型类,实现这个抽象类需要指定一个配置类,通过配置类中的定义来具体实现我们的过滤器。这边需要注意,编写的过滤器必须以"GatewayFilterFactory"为结尾。
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomGatewayFilterFactory.Config> {
public CustomGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.isPreLogger()) {
log.info("CustomGatewayFilterFactory pre message is {}", config.getMessage());
}
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()) {
log.info("CustomGatewayFilterFactory post message is {}", config.getMessage());
}
}));
}
};
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Config {
private String message;
private boolean preLogger;
private boolean postLogger;
}
}
上面的示例代码中,Config配置类有三个属性:
- message指定需要打印的日志
- preLogger是一个boolean类型,用于判断是否在前置过滤器中打印日志
- postLogger判断是否在后置过滤器中打印日志
通过配置文件生效过滤器
在配置文件中新增如下配置生效自定义的过滤器,这边的名称就是自定义过滤器的前缀:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: baidu
uri: https://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- name: Custom
args:
message: My Custom Message
preLogger: true
postLogger: true
- RedirectTo=302, https://www.baidu.com
还有一种更简洁的定义方式:
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: baidu
uri: https://www.baidu.com
predicates:
- Path=/baidu/**
filters:
- Custom= My Custom Message, true, true
- RedirectTo=302, https://www.baidu.com
不过这边需要重写shortcutFieldOrder方法,该方法返回一个List列表,列表中指定参数使用的顺序和数量:
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("message", "preLogger", "postLogger");
}
启动网关服务,通过网关服务请求/baidu/**地址,可以看到具体打印的日志信息
如果我们想要指定过滤器的执行顺序,可以返回一个OrderedGatewayFilter实例,它提供了一个构造函数可以传入GatewayFilter以及order信息。
@Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter(new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (config.isPreLogger()) {
log.info("CustomGatewayFilterFactory pre message is {}", config.getMessage());
}
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
if (config.isPostLogger()) {
log.info("CustomGatewayFilterFactory post message is {}", config.getMessage());
}
}));
}
}, -1);
}
通过编码的方式生效过滤器
除了在配置文件中指定,也可以通过编码的方式注入我们自定义的过滤器,代码中需要注入一个RouteLocator的bean(通过编码的方式没有配置文件方式清晰)
@Bean
public RouteLocator routes(RouteLocatorBuilder builder, CustomGatewayFilterFactory filterFactory) {
return builder
.routes()
.route("163", p -> p.path("/163/**")
.filters(f -> f
.filter(filterFactory
.apply(new CustomGatewayFilterFactory.Config("Base 163 mesage", true, false)))
.redirect(302, "https://www.163.com"))
.uri("http://localhost"))
.build();
}
其他应用场景
到目前为止,示例中只是添加了一行日志,在自定义过滤器里面我们可以做比如检查或者修改经过网关的请求,也可以修改具体响应。下面例举两个简单的应用场景。
修改请求
在前置过滤器中,我们可以获取当前的请求头,并对请求头进行业务处理,然后将信息透传到下面的调用链中,比如现在我在请求到达微服务之前,添加了一个header头。
首先在自定义过滤器中添加header头:
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Slf4j
@Component
public class ModifyRequestGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyRequestGatewayFilterFactory.Config> {
public ModifyRequestGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange
.getRequest()
.mutate()
.header(config.getName(), config.getValue())
.build();
log.info("Begin add header [{}]", config);
return chain.filter(exchange.mutate().request(request).build());
};
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name", "value");
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Config {
private String name;
private String value;
}
}
在配置文件中添加过滤器信息,这边是路由到eureka-client1服务时,会添加一个name为yzh1996的header头:
- id: client1
uri: lb://eureka-client1
predicates:
- Path=/client1/**
filters:
- ModifyRequest=name, yzh1996
client1中接口打印header头信息:
@GetMapping("/headerName")
public String headerName(HttpServletRequest request) {
String name = request.getHeader("name");
return "Header name is " + name;
}
浏览器请求对应接口,可以看到我们上游添加的header头已经透传到下游的微服务
修改响应
在后置过滤器里面,我们可以修改响应的请求,比如我们可以修改响应的响应码。
/**
* @author yuanzhihao
* @since 2022/4/22
*/
@Slf4j
@Component
public class ModifyResponseGatewayFilterFactory extends AbstractGatewayFilterFactory<ModifyResponseGatewayFilterFactory.Config> {
public ModifyResponseGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("Modify Response status code begin...");
exchange.getResponse().setRawStatusCode(Integer.parseInt(config.getStatusCode()));
}));
}
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("statusCode");
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Config {
private String statusCode;
}
}
添加配置信息:
- id: client1
uri: lb://eureka-client1
predicates:
- Path=/client1/**
filters:
- ModifyRequest=name, yzh1996
- ModifyResponse=205
之后请求服务,可以看到服务的响应已经被设置为205
结语
关于自定义过滤器就整理到这边,文章中所有的代码都能在我的代码仓库找到,后续有时间还会继续整理相关其他用法。
参考链接:
https://docs.spring.io/spring-cloud-gateway/docs/3.0.4/reference/html/#developer-guide
https://www.baeldung.com/spring-cloud-custom-gateway-filters
代码地址:https://github.com/yzh19961031/SpringCloudDemo/tree/main/gateway
更多推荐
所有评论(0)