The scope of GatewayFilter
is the specified routing configuration, and the routing configuration options require filters
to specify the list of GatewayFilters
that you want to use. We can customize the GatewayFilter
to do additional extensions to achieve some functionality that does not exist in the built-in GatewayFilter
and apply it to our routing configuration.
How to customize GatewayFilter
To customize GatewayFilter
, you need to implement the org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory
interface, and the definition of GatewayFilterFactory
is as follows.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
@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();
}
|
It looks quite complicated, but in fact many of them are the default methods of the interface, and there are actually very few methods to implement.
Another way is to inherit from org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory
and look at the definition of the abstract class AbstractGatewayFilterFactory
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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;
}
}
}
|
1 generic parameter C is the configuration class, from the existing AbstractGatewayFilterFactory
or GatewayFilterFactory
subclass implementation, the configuration class is generally defined as a public static internal class.
-
implement the GatewayFilterFactory
interface or inherit from AbstractGatewayFilterFactory
.
-
the corresponding subclasses are registered to the Spring
container.
-
add the corresponding GatewayFilter
configuration to the filters
property in the routing configuration. Note that the filter name is determined by GatewayFilterFactory#name()
.
Practices
The following is an attempt to implement the GatewayFilterFactory
interface and to inherit from the AbstractGatewayFilterFactory
abstract class.
Implement the GatewayFilterFactory interface
The GatewayFilterFactory
interface is implemented with reference to the SetRequestHeaderGatewayFilterFactory
to add a custom request header for each request.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
@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;
}
}
}
|
As you can see, the core functional operation is actually required to implement the GatewayFilter apply(C config)
method to write a custom function. Note the logic of this Lambda
expression:
1
2
3
4
5
6
|
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());
});
|
In fact, it can be simply understood as an anonymous implementation of the GatewayFilter
interface.
The application.yaml
configuration file is as follows.
1
2
3
4
5
6
7
8
9
10
|
spring:
cloud:
gateway:
routes:
- id: custom_add_request_header_route
uri: http://localhost:9091
predicates:
- Host=localhost:9090
filters:
- CustomAddRequestHeader=customHeaderName,customHeaderValue
|
To match the test, the downstream service adds an endpoint.
1
2
3
4
|
@GetMapping(value = "/customAddRequestHeader")
public ResponseEntity<String> customAddRequestHeader(@RequestHeader(name = "customHeaderName") String value) {
return ResponseEntity.ok(value);
}
|
1
2
3
4
|
curl localhost:9090/order/customAddRequestHeader
// response
customHeaderValue
|
Inherits AbstractGatewayFilterFactory abstract class
Inheriting AbstractGatewayFilterFactory
is actually pretty much the same way, we try to make a relatively complex modification: for each successful request (status code 200) response, add a custom cookie
and a response header. GatewayFilterFactory
abstract class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
@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;
}
}
}
|
The application.yaml
configuration file is as follows:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
|
Note here that the name
property under the filters
collection is required to point to the name()
method of the AbstractGatewayFilterFactory
implementation class, and the args
property is used to specify the properties assembled to the Config
class.
1
2
3
4
5
6
7
8
|
curl localhost:9090/order/remote
// response header
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
|
Summary
Customizing GatewayFilter
allows us to flexibly extend the filter and add custom properties or judgment logic to the request or response. The fact that GatewayFilter
is not globally valid allows us to flexibly combine the functionality of multiple instances of GatewayFilter
that have already been written when writing routing configurations.
Reference https://www.throwx.cn/2019/05/05/spring-cloud-gateway-custom-gateway-filter/