前提

GlobalFilter的作用域是所有的路由配置,我们可以通过自定义GlobalFilter,做额外的扩展,用来实现一些全局的功能。

如何自定义GlobalFilter

org.springframework.cloud.gateway.filter.GlobalFilter的接口定义如下:

public interface GlobalFilter {

Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

我们只需要实现org.springframework.cloud.gateway.filter.GlobalFilter接口,并且把实现类注册到Spring的容器中即可,官方例子如下:

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

实践

我们通过自定义实现一个GlobalFilter,实现类似Nginx的Access Log的功能,也就是对每一个请求都记录请求的一些核心参数和响应的一些核心参数。注意的是,我们实现的这个GlobalFilter是pre类型同时是post类型。

@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("请求路径:{},远程IP地址:{},响应码:{}", path, remoteAddress, statusCode);
}));
}
}

上面的例子中,我们只打印了:

  • 请求的路径。
  • 请求的远程IP地址。
  • 响应码。
curl http://localhost:9090/order/remote

日志输出如下:

2019-05-04 19:13:19.101  INFO 25388 --- [ctor-http-nio-7] c.t.route.support.AccessLogGlobalFilter  : 请求路径:/order/remote,远程IP地址:/0:0:0:0:0:0:0:1:63861,响应码:200 OK

这样显然不够详细,我们接着尝试添加下面的参数:

  • 如果是GET请求,则提取它的Query参数,如果是POST请求,则尝试读取RequestBody的参数,打印请求的参数。
  • 请求方法。
  • 目标URI。

修改代码如下:

@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);
}
});
// 重写请求体,因为请求体数据只能被消费一次
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("请求路径:{},远程IP地址:{},请求方法:{},请求参数:{},目标URI:{},响应码:{}",
path, remoteAddress, method, builder.toString(), targetUri, statusCode);
}));
}
}
curl -X POST -d "name=doge" localhost:9090/order/remote

由于下游服务只接受GET方法请求,网关打印日志如下:

请求路径:/order/remote,远程IP地址:/0:0:0:0:0:0:0:1:65158,请求方法:POST,请求参数:name=doge,目标URI:http://localhost:9091/order/remote,响应码:405 METHOD_NOT_ALLOWED

小结

其实,GlobalFilter既然会对所有的路由配置都生效,我们扩展它实现的功能是一般全局的功能。上面的例子中涉及到重新装饰请求对象,解析请求参数的操作会有一定的性能损耗,具体要看实际的应用场景。

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