如何在LogRequestFilter中正确记录请求体并修复请求体丢失问题
请求日志过滤器错误修复与代码重构
错误根源
你的代码里直接调用request.getReader()读取请求体,这会消耗原始请求流。Spring MVC控制器后续读取请求体时,流已被耗尽,因此抛出"Required request body is missing"异常。ContentCachingRequestWrapper的设计初衷是缓存请求体以支持多次读取,必须使用它提供的缓存API获取内容,不能直接操作原始流。
修复+重构后的完整代码
@Slf4j public class LogRequestFilter extends OncePerRequestFilter { // 定义所有可能携带请求体的HTTP方法 private static final Set<String> METHODS_WITH_BODY = Set.of("POST", "PUT", "PATCH"); private static final String DEFAULT_CHARSET = "UTF-8"; @Override protected void doFilterInternal(@Nonnull HttpServletRequest request, @Nonnull HttpServletResponse response, @Nonnull FilterChain filterChain) throws ServletException, IOException { // 仅在debug日志开启时才包装请求,减少不必要的性能损耗 HttpServletRequest wrappedRequest = log.isDebugEnabled() ? new ContentCachingRequestWrapper(request) : request; try { // 先执行请求链,再打日志,避免日志逻辑阻塞正常请求 filterChain.doFilter(wrappedRequest, response); } finally { // 确保请求处理完成后再输出日志,且仅当是包装后的请求才处理 if (log.isDebugEnabled() && wrappedRequest instanceof ContentCachingRequestWrapper) { logRequest((ContentCachingRequestWrapper) wrappedRequest); } } } private void logRequest(@Nonnull ContentCachingRequestWrapper request) { log.debug("\n" + "==================== request begin ====================\n" + "URI : {}\n" + "Method : {}\n" + "Headers : {}\n" + "Request Body : {}\n" + "==================== request end ======================", request.getRequestURI(), request.getMethod(), getHeaders(request), getRequestContent(request) ); } @Nonnull private String getRequestContent(@Nonnull ContentCachingRequestWrapper request) { String method = request.getMethod(); // 非带请求体的方法直接返回提示 if (!METHODS_WITH_BODY.contains(method.toUpperCase())) { return "No request body (non-body method)"; } byte[] contentBytes = request.getContentAsByteArray(); // 缓存内容为空则返回提示 if (contentBytes.length == 0) { return "Empty request body"; } try { // 优先使用请求指定的字符编码,默认UTF-8 String charset = request.getCharacterEncoding(); return new String(contentBytes, charset != null ? charset : DEFAULT_CHARSET); } catch (UnsupportedEncodingException e) { log.error("Failed to decode request body", e); return "Failed to decode request body"; } } @Nonnull private Map<String, List<String>> getHeaders(@Nonnull HttpServletRequest request) { return Collections.list(request.getHeaderNames()) .stream() .collect(Collectors.toMap( Function.identity(), header -> Collections.list(request.getHeaders(header)), // 兼容极端场景下的重复header名,保留原有值 (existing, replacement) -> existing )); } }
关键优化点说明
- 流安全处理:改用
ContentCachingRequestWrapper.getContentAsByteArray()读取缓存的请求体,避免消耗原始请求流,解决了"Required request body is missing"的核心问题。 - 性能优化:仅在debug日志开启时才包装请求,避免无意义的缓存操作。
- 日志时机调整:把日志输出放在
finally块,确保请求正常执行完成后再打日志,不会阻塞业务流程。 - 请求方法覆盖:扩展支持PUT/PATCH方法(这类方法也常携带请求体),比只判断POST更全面。
- 编码兼容:处理请求的字符编码,避免乱码问题。
- Header收集健壮性:添加了重复header名的合并策略,兼容特殊场景。
关于请求体判断的疑问
- 需要判断请求方法:GET/DELETE等方法按HTTP规范一般不携带请求体,直接跳过读取逻辑即可,避免无效操作。
- 不需要额外判断ContentType:即使设置了ContentType,请求体也可能为空;通过
getContentAsByteArray().length就能准确判断是否有有效请求体,同时避免了空指针风险。
内容的提问来源于stack exchange,提问作者DonDon
相关产品推荐
相关产品推荐

