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"); } }
关键注意点
- 必须调用
wrappedResponse.copyBodyToResponse():如果跳过这一步,缓存的响应内容不会写入原始响应流,客户端(比如浏览器)会收不到任何响应,Swagger页面自然加载失败。 - 添加
shouldNotFilter规则:主动排除Swagger相关路径,减少不必要的处理,也能避免潜在的流处理冲突。 - 注册过滤器:记得在配置类中注册这个过滤器,让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
相关产品推荐
相关产品推荐

