You need to enable JavaScript to run this app. 使用ServerHttpResponseDecorator记录响应日志导致swagger-ui无法加载的问题
优惠活动
大模型
产品
解决方案
定价
更多

使用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()虽然写法不规范,但因为流本身只有一个元素,没有触发截断逻辑,所以没有暴露问题
  • 原有实现存在额外隐患
    • 没有正确处理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.24 07:45:03