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.

How to customize GlobalFilter

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");
        }));
    };
}

Practices

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

Summary

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/