You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot Actuator追踪添加请求体抛出HttpMessageNotReadableException问题

嘿,这个问题我之前踩过坑!核心根源其实是Servlet的请求/响应流只能被读取一次,你的自定义RequestTraceFilter直接读取流后没做处理,导致后面的控制器、Swagger组件再去读的时候就拿不到内容了,自然就触发异常和页面加载失败。

具体问题拆解

  • 请求体丢失异常:当你的过滤器读取了request.getInputStream()后,流的指针会跳到末尾,后续Spring MVC的消息转换器再尝试读取请求体时,就会提示Required request body is missing——因为流里已经没内容了。
  • Swagger-ui无法加载:Swagger的相关请求(比如/swagger-ui.html、/v3/api-docs/)也会被你的过滤器拦截,同样因为流被提前读取且未重置,Swagger的前端/后端组件无法正确解析请求,导致页面加载失败。

解决方案:用Spring提供的缓存包装类处理流

Spring已经帮我们准备了ContentCachingRequestWrapper和ContentCachingResponseWrapper,它们会缓存请求/响应内容,允许我们多次读取流,同时不影响后续组件的处理。修改你的过滤器如下:

import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

public class RequestTraceFilter extends OncePerRequestFilter {

    private final HttpTraceRepository traceRepository;

    public RequestTraceFilter(HttpTraceRepository traceRepository) {
        this.traceRepository = traceRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 包装请求和响应,缓存内容以便重复读取
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
        ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

        try {
            // 先让后续过滤器/控制器处理请求
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } finally {
            // 从包装类中获取请求体和响应体
            String requestBody = getContent(wrappedRequest.getContentAsByteArray());
            String responseBody = getContent(wrappedResponse.getContentAsByteArray());

            // 构建并保存包含请求/响应体的追踪记录
            HttpTrace trace = buildTraceRecord(wrappedRequest, wrappedResponse, requestBody, responseBody);
            traceRepository.add(trace);

            // 必须调用此方法,将缓存的响应内容写入原始响应流,否则客户端收不到响应
            wrappedResponse.copyBodyToResponse();
        }
    }

    private String getContent(byte[] content) {
        return content.length == 0 ? null : new String(content, StandardCharsets.UTF_8);
    }

    private HttpTrace buildTraceRecord(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, String requestBody, String responseBody) {
        return HttpTrace.builder()
                .timestamp(java.time.Instant.now())
                .request(HttpTrace.Request.builder()
                        .method(request.getMethod())
                        .uri(request.getRequestURI())
                        .headers(request.getHeaderNames().stream()
                                .collect(Collectors.toMap(h -> h, request::getHeader)))
                        .body(requestBody) // 加入请求体
                        .build())
                .response(HttpTrace.Response.builder()
                        .status(response.getStatus())
                        .headers(response.getHeaderNames().stream()
                                .collect(Collectors.toMap(h -> h, response::getHeader)))
                        .body(responseBody) // 加入响应体
                        .build())
                .build();
    }

    // 排除Swagger相关请求,避免不必要的拦截
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        String path = request.getRequestURI();
        return path.startsWith("/swagger-ui/") 
                || path.startsWith("/v3/api-docs/") 
                || path.equals("/swagger-ui.html");
    }
}

关键注意点

  1. 必须调用wrappedResponse.copyBodyToResponse():如果跳过这一步,缓存的响应内容不会写入原始响应流,客户端(比如浏览器)会收不到任何响应,Swagger页面自然加载失败。
  2. 添加shouldNotFilter规则:主动排除Swagger相关路径,减少不必要的处理,也能避免潜在的流处理冲突。
  3. 注册过滤器:记得在配置类中注册这个过滤器,让Spring管理它:
import org.springframework.boot.actuate.trace.http.HttpTraceRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TraceConfig {
    @Bean
    public RequestTraceFilter requestTraceFilter(HttpTraceRepository traceRepository) {
        return new RequestTraceFilter(traceRepository);
    }
}

这样修改后,不仅能正常记录请求/响应体,之前的异常和Swagger加载问题也会解决啦。

内容的提问来源于stack exchange,提问作者vishnukumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 09:25:54