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

如何在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
                ));
    }
}

关键优化点说明

  1. 流安全处理:改用ContentCachingRequestWrapper.getContentAsByteArray()读取缓存的请求体,避免消耗原始请求流,解决了"Required request body is missing"的核心问题。
  2. 性能优化:仅在debug日志开启时才包装请求,避免无意义的缓存操作。
  3. 日志时机调整:把日志输出放在finally块,确保请求正常执行完成后再打日志,不会阻塞业务流程。
  4. 请求方法覆盖:扩展支持PUT/PATCH方法(这类方法也常携带请求体),比只判断POST更全面。
  5. 编码兼容:处理请求的字符编码,避免乱码问题。
  6. Header收集健壮性:添加了重复header名的合并策略,兼容特殊场景。

关于请求体判断的疑问

  • 需要判断请求方法:GET/DELETE等方法按HTTP规范一般不携带请求体,直接跳过读取逻辑即可,避免无效操作。
  • 不需要额外判断ContentType:即使设置了ContentType,请求体也可能为空;通过getContentAsByteArray().length就能准确判断是否有有效请求体,同时避免了空指针风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:10:58