Spring Cloud Gateway-自定义GatewayFilter

Spring Cloud Gateway-自定义GatewayFilter

前提

GatewayFilter的作用域是指定的路由配置,路由配置选项里面需要通过filters指定想要使用的GatewayFilter列表。我们可以通过自定义GatewayFilter,做额外的扩展,实现一些内建GatewayFilter不存在的功能,并且应用到我们的路由配置中。

如何自定义GatewayFilter

需要定制GatewayFilter,则需要实现org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory接口,GatewayFilterFactory的定义如下:

@FunctionalInterface
public interface GatewayFilterFactory<C> extends ShortcutConfigurable, Configurable<C> {

String NAME_KEY = "name";

String VALUE_KEY = "value";

default GatewayFilter apply(Consumer<C> consumer) {
C config = newConfig();
consumer.accept(config);
return apply(config);
}

default Class<C> getConfigClass() {
throw new UnsupportedOperationException("getConfigClass() not implemented");
}

@Override
default C newConfig() {
throw new UnsupportedOperationException("newConfig() not implemented");
}

GatewayFilter apply(C config);

default String name() {
return NameUtils.normalizeFilterFactoryName(getClass());
}

@Deprecated
default ServerHttpRequest.Builder mutate(ServerHttpRequest request) {
return request.mutate();
}
}

public interface ShortcutConfigurable {

default ShortcutType shortcutType() {
return ShortcutType.DEFAULT;
}

default List<String> shortcutFieldOrder() {
return Collections.emptyList();
}

default String shortcutFieldPrefix() {
return "";
}
}

public interface Configurable<C> {

Class<C> getConfigClass();

C newConfig();
}

看起来挺复杂的,但是实际上很多都是接口的默认方法,实际上要实现的方法很少。

另一种方式是继承org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory,看抽象类AbstractGatewayFilterFactory的定义:

public abstract class AbstractGatewayFilterFactory<C> extends AbstractConfigurable<C>
implements GatewayFilterFactory<C> {

@SuppressWarnings("unchecked")
public AbstractGatewayFilterFactory() {
super((Class<C>) Object.class);
}

public AbstractGatewayFilterFactory(Class<C> configClass) {
super(configClass);
}

public static class NameConfig {

private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
}

泛型参数C是配置类,从现有的AbstractGatewayFilterFactory或者GatewayFilterFactory的子类实现来看,配置类一般定义为公有静态内部类。

  1. 实现GatewayFilterFactory接口或者继承AbstractGatewayFilterFactory
  2. 对应的子类注册到Spring的容器。
  3. 路由配置中的filters属性添加对应GatewayFilter配置,注意一下,过滤器名称由GatewayFilterFactory#name()决定。

实践

下面实现GatewayFilterFactory接口和继承AbstractGatewayFilterFactory抽象类两种方式都做了尝试。

实现GatewayFilterFactory接口

实现GatewayFilterFactory接口的时候,参考了SetRequestHeaderGatewayFilterFactory,为每个请求添加自定义的请求头。

@Component
public class CustomAddRequestHeaderGatewayFilterFactory implements
GatewayFilterFactory<CustomAddRequestHeaderGatewayFilterFactory.CustomAddRequestHeaderConfig> {

private final Class<CustomAddRequestHeaderConfig> configClass = CustomAddRequestHeaderConfig.class;

@Override
public List<String> shortcutFieldOrder() {
return new ArrayList<>(Arrays.asList("headerName", "headerValue"));
}

@Override
public GatewayFilter apply(CustomAddRequestHeaderConfig config) {
return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
httpHeaders.set(config.getHeaderName(), config.getHeaderValue());
}).build();
return chain.filter(exchange.mutate().request(request).build());
});
}

@Override
public Class<CustomAddRequestHeaderConfig> getConfigClass() {
return configClass;
}

@Override
public CustomAddRequestHeaderConfig newConfig() {
return BeanUtils.instantiateClass(this.configClass);
}

public static class CustomAddRequestHeaderConfig {

private String headerName;
private String headerValue;

public String getHeaderName() {
return headerName;
}

public void setHeaderName(String headerName) {
this.headerName = headerName;
}

public String getHeaderValue() {
return headerValue;
}

public void setHeaderValue(String headerValue) {
this.headerValue = headerValue;
}
}
}

可以看到,其实最核心的功能操作是需要实现GatewayFilter apply(C config)方法,编写自定义的功能。注意这段Lambda表达式的逻辑:

return ((exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
httpHeaders.set(config.getHeaderName(), config.getHeaderValue());
}).build();
return chain.filter(exchange.mutate().request(request).build());
});

其实,它可以简单理解为GatewayFilter接口的匿名实现。

application.yaml配置如下:

spring:
cloud:
gateway:
routes:
- id: custom_add_request_header_route
uri: http://localhost:9091
predicates:
- Host=localhost:9090
filters:
- CustomAddRequestHeader=customHeaderName,customHeaderValue

为了配合测试,下游服务添加一个端点:

@GetMapping(value = "/customAddRequestHeader")
public ResponseEntity<String> customAddRequestHeader(@RequestHeader(name = "customHeaderName") String value) {
return ResponseEntity.ok(value);
}
curl localhost:9090/order/customAddRequestHeader

// 响应
customHeaderValue

继承AbstractGatewayFilterFactory抽象类

继承AbstractGatewayFilterFactory的方式其实差不多,我们尝试做一个相对复杂的改造:对每一个请求成功(状态码为200)的响应,添加一个自定义的cookie和一个响应头。

@Component
public class CustomResponseGatewayFilterFactory extends
AbstractGatewayFilterFactory<CustomResponseGatewayFilterFactory.Config> {

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

@Override
public GatewayFilter apply(Config config) {
return ((exchange, chain) -> {
ServerHttpResponse response = exchange.getResponse();
if (HttpStatus.OK.equals(response.getStatusCode())) {
for (Map.Entry<String, String> entry : toMap(config.getCookie()).entrySet()) {
response.addCookie(ResponseCookie.from(entry.getKey(), entry.getValue()).build());
}
for (Map.Entry<String, String> entry : toMap(config.getHeader()).entrySet()) {
response.getHeaders().add(entry.getKey(), entry.getValue());
}
return chain.filter(exchange.mutate().response(response).build());
} else {
return chain.filter(exchange);
}
});
}

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

private static Map<String, String> toMap(String value) {
String[] split = value.split("=");
Map<String, String> map = new HashMap<>(8);
map.put(split[0], split[1]);
return map;
}

public static class Config {

private String cookie;
private String header;

public String getCookie() {
return cookie;
}

public void setCookie(String cookie) {
this.cookie = cookie;
}

public String getHeader() {
return header;
}

public void setHeader(String header) {
this.header = header;
}
}
}

application.yaml配置文件如下:

spring:
cloud:
gateway:
routes:
- id: custom_response_route
uri: http://localhost:9091
predicates:
- Host=localhost:9090
filters:
- name: CustomResponse
args:
cookie: customCookieName=customCookieValue
header: customHeaderName=customHeaderValue

这里注意,filters集合下的name属性是必须的,指向AbstractGatewayFilterFactory实现类的name()方法,args属性是用于指定装配到Config类的属性。

curl localhost:9090/order/remote

// 响应头如下
customHeaderName: customHeaderValue
Content-Type: text/plain;charset=UTF-8
Content-Length: 6
Date: Sun, 05 May 2019 11:06:35 GMT
set-cookie: customCookieName=customCookieValue

小结

自定义GatewayFilter允许我们很灵活地扩展过滤器,并且对于请求或者响应添加自定义的一些属性或者判断逻辑。GatewayFilter不是全局生效的特性,使得我们在编写路由配置的时候可以灵活组合多个已经编写好的GatewayFilter实例的功能。

(c-1-d e-a-20190505)

文章作者: throwable
文章链接: http://www.throwable.club/2019/05/05/spring-cloud-gateway-custom-gateway-filter/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Throwable
❤支付宝打赏❤
❤微信打赏❤