spring cloud gateway请求相应报文体日志输出

在集成spring cloud gateway的时候,想要在网关上加上一个全局filter,监控进入网关的请求,和响应的报文内容。因为熟悉了servelet的开发方式,本以为是一个简简单单的功能,没想到在这个问题上耗费了一天的时间。
spring cloud gateway基于webflux,webflux虽然可以选择使用tomcat,undertow等实现方案,但是在网关就没有这么多选择。webflux基于netty的reactivity非阻塞框架,带来了很大的性能上的提升,问题就是,这东西确实挺复杂的。在不断的实践中不断摸索。


现实问题

在spring cloud gateway的全局filter,ServerWebExchange类分装了request,response,甚至可以方便地取出Route信息。
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
在获取request和response后,开开心心的获取body,然后记录日志。虽然response获取body还是颇费周章,需要用DataBuffer读取后再写入。但是运行起来后,发现body内容都是null,事情并没有想象中那么简单。

分析原因

还好对netty框架有一定程度的了解,因为webflux使用的是netty框架,所以应该从此处分析。netty的读写是基于缓冲区实现,当我们读取request或是response的body时,实际是在消费缓冲区的内容。比如在Databuffer中有1KB的数据,在读取前,可读数据为1KB,读取后缓冲区的队列从头移动到队列尾部,被消费了,这个requst在经过其他filter或是被路由到服务节点上后,缓冲区已经没有数据,就会引起异常。response也是同样道理。

解决思路

既然缓冲区消费了,那重新写入不就行了。思路没有问题,但是遇到技术障碍了,因为基于流式的,很容以引起内容不完整等问题。
查阅了官方文档后,发现官方提供了修改body等实现,ModifyRequestBodyGatewayFilterFactory,ModifyResponseBodyGatewayFilterFactory这两个类。但是只能在配置类中进行硬编码,不能够在全局filter,或是配置文件的方式实现。

spring官方文档地址

则中方案

在前面的swaggrer集成文章中,提供了一种基于eureka的路由方式,可以用一个配置覆盖注册中心的服务的路由规则,可以用这种方式,完成对网关路由规则的覆盖。然后使用硬编码的方式去配置,可以将网关的内容给固化下来。代码如下:

@Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("remove_api_prefix", r -> r.path("/api/**")
                        .filters(f -> f.stripPrefix(1)
                                .modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> {
                                            log.info("Get Request: {}", s);
                                                return Objects.nonNull(s) ? Mono.just(s) : Mono.empty();
                                        })
                                .modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> {
                                            log.info("Get Response: {}", s);
                                            return Objects.nonNull(s) ? Mono.just(s) : Mono.empty();
                                        })
                        ).uri("http://127.0.0.1:80").order(-2))
                .build();
    }

这里将body内容获取后,在重新写入到body,完成对body内容的读取。
网上也有基于ModifyRequestBodyGatewayFilterFactory的代码,改写的全局filter,但是不是很保险,不排除在某些场景下会出现意外情况,不是一个很稳妥的方案。

不由感叹,webflux是个好东西,但真不是轻轻松松就能够掌握的技能,活到老学到老,这也正是这个行业的魅力所在。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐