The scope of GlobalFilter
is all the routing configuration, we can do additional extensions by customizing GlobalFilter
, which can be used to implement some global functions.
The interface definition for org.springframework.cloud.gateway.filter.GlobalFilter
is as follows.
1
2
3
4
|
public interface GlobalFilter {
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
|
We just need to implement the org.springframework.cloud.gateway.filter.GlobalFilter
interface and register the implementation class in Spring’s container, the official example 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
|
@Bean
@Order(-1)
public GlobalFilter a() {
return (exchange, chain) -> {
log.info("first pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("third post filter");
}));
};
}
@Bean
@Order(0)
public GlobalFilter b() {
return (exchange, chain) -> {
log.info("second pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("second post filter");
}));
};
}
@Bean
@Order(1)
public GlobalFilter c() {
return (exchange, chain) -> {
log.info("third pre filter");
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
log.info("first post filter");
}));
};
}
|
We implement a custom GlobalFilter
to achieve similar functionality to Nginx
’s Access Log
, that is, to log some core parameters of the request and some core parameters of the response for each request. Note that the GlobalFilter
we implement is of type pre
and type post
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
InetSocketAddress remoteAddress = request.getRemoteAddress();
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("requestPath:{},remoteIP :{},statusCode:{}", path, remoteAddress, statusCode);
}));
}
}
|
In the example above, we only printed.
- The path of the request.
- The remote IP address of the request.
- The response code.
1
|
curl http://localhost:9090/order/remote
|
The log output is as follows.
1
|
2019-05-04 19:13:19.101 INFO 25388 --- [ctor-http-nio-7] c.t.route.support.AccessLogGlobalFilter : requestPath:/order/remote,remoteIP :/0:0:0:0:0:0:0:1:63861,statusCode:200 OK
|
This is obviously not detailed enough, so we then try to add the following parameters.
- If it is a GET request, then extract its Query parameters, and if it is a POST request, then try to read the RequestBody parameters and print the parameters of the request.
- RequestMethod.
- TargetURI.
Modify the code 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
|
@Slf4j
@Component
public class AccessLogGlobalFilter implements GlobalFilter {
private final ObjectMapper mapper = new ObjectMapper();
private final DataBufferFactory dataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().pathWithinApplication().value();
HttpMethod method = request.getMethod();
StringBuilder builder = new StringBuilder();
URI targetUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
if (HttpMethod.GET.equals(method)) {
MultiValueMap<String, String> queryParams = request.getQueryParams();
try {
builder.append(mapper.writeValueAsString(queryParams));
} catch (JsonProcessingException e) {
log.error(e.getMessage(), e);
}
} else if (HttpMethod.POST.equals(method)) {
Flux<DataBuffer> body = request.getBody();
ServerHttpRequest serverHttpRequest = request.mutate().uri(request.getURI()).build();
body.subscribe(dataBuffer -> {
InputStream inputStream = dataBuffer.asInputStream();
try {
builder.append(StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8));
} catch (IOException e) {
log.error(e.getMessage(), e);
}
});
// Rewrite the request body, because the request data can only be consumed once
request = new ServerHttpRequestDecorator(serverHttpRequest) {
@Override
public Flux<DataBuffer> getBody() {
return Flux.just(dataBufferFactory.wrap(builder.toString().getBytes(StandardCharsets.UTF_8)));
}
};
}
InetSocketAddress remoteAddress = request.getRemoteAddress();
return chain.filter(exchange.mutate().request(request).build()).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
HttpStatus statusCode = response.getStatusCode();
log.info("requestPath:{},remoteIP :{},requestMethod:{},requestParam:{},targetURI:{},statusCode:{}",
path, remoteAddress, method, builder.toString(), targetUri, statusCode);
}));
}
}
|
1
|
curl -X POST -d "name=doge" localhost:9090/order/remote
|
Since the downstream service only accepts GET method requests, the gateway prints the log as follows.
1
|
requestPath:/order/remote,remoteIP :/0:0:0:0:0:0:0:1:65158,requestMethod:POST,requestParam:name=doge,targetURI:http://localhost:9091/order/remote,statusCode:405 METHOD_NOT_ALLOWED
|
In fact, since GlobalFilter
will take effect for all routing configurations, we extend it to achieve the general global functionality. The above example involves redecorating the request object, and the operation of parsing the request parameters will have some performance loss, depending on the actual application scenario.
Reference https://www.throwx.cn/2019/05/05/spring-cloud-gateway-custom-global-filter/