使用ServerHttpResponseDecorator记录响应日志导致swagger-ui无法加载的问题
故障根因定位
- 核心问题出在
Mono.from(body)对响应式流的错误转换- WebFlux的响应体
body是通用的Publisher<out DataBuffer>类型,既可能是单元素流(比如普通JSON接口的整包响应),也可能是多元素分片流(比如swagger-ui的静态HTML、JS、CSS资源,或是大文件下载、SSE流式接口) Mono.from()的语义是只取Publisher发出的第一个元素,后续所有元素直接丢弃,立刻完结流。哪怕你在doOnNext里不写任何业务逻辑,只要把原本是多元素Flux的body强行用Mono.from()包装,截断逻辑就已经生效了:当碰到多分片的静态资源响应时,装饰器只会把第一个分片的DataBuffer传给下游,剩下的所有资源分片都被截掉,浏览器拿到残缺的静态资源文件,自然会出现swagger-ui白屏- 之前Postman调用业务接口能正常返回,是因为普通业务接口的JSON响应体不大,会被WebFlux的响应编码器自动拼成单个DataBuffer,这时候
Mono.from()虽然写法不规范,但因为流本身只有一个元素,没有触发截断逻辑,所以没有暴露问题
- WebFlux的响应体
- 原有实现存在额外隐患
- 没有正确处理DataBuffer的内存生命周期:WebFlux的DataBuffer多为堆外内存实现,转换流的过程中如果随意持有buffer引用、或是提前消费buffer内容,会导致内存泄漏、buffer被提前释放的问题
- 没有区分响应类型,后续如果真的在
doOnNext里读取buffer内容,会直接把原始buffer的读指针移到末尾,导致下游写回客户端时读到空内容,响应直接返回空体
正确的实现思路
- 绝对不要强行修改原始
body的流类型,要完整保留原始流的分片数量、传输顺序,不做任何截断操作 - 读取响应日志时,使用副本方式读取buffer内容,不要修改原始buffer的读指针,保证下游能正常把内容写回客户端
- 区分响应内容类型,对静态资源、二进制流、大体积响应做日志降级,避免把大量二进制资源打印到日志造成性能问题
- 正确处理buffer的引用计数,不要在装饰器层额外释放原始buffer,内存释放的权限归下游写组件所有
正确实现示例
import org.slf4j.LoggerFactory import org.springframework.core.io.buffer.DataBuffer import org.springframework.http.server.reactive.ServerHttpResponse import org.springframework.http.server.reactive.ServerHttpResponseDecorator import reactor.core.publisher.Flux import java.nio.charset.StandardCharsets class ResponseLoggingDecorator( delegate: ServerHttpResponse ) : ServerHttpResponseDecorator(delegate) { private val logger = LoggerFactory.getLogger(this::class.java) override fun writeWith(body: Publisher<out DataBuffer>): Mono<Void> { // 直接保留原始Flux语义,不强行转Mono,避免截断多分片响应 return super.writeWith( Flux.from(body) .doOnNext { dataBuffer -> // 生成buffer的只读副本读取,不影响原始buffer的读指针 val contentSnapshot = StandardCharsets.UTF_8.decode(dataBuffer.toByteBuffer().asReadOnlyBuffer()).toString() // 仅对JSON类型的业务响应打日志,跳过静态资源、二进制流 if (delegate.headers.contentType?.toString()?.startsWith("application/json") == true) { logger.info("接口响应内容: {}", contentSnapshot) } } ) } }
内容的提问来源于stack exchange,提问作者Niamh
相关产品推荐
相关产品推荐

