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

使用ContentCachingResponseWrapper+DeferredResult无法返回响应体的原因

问题原因分析

当使用DeferredResult处理异步请求时,Servlet容器的请求线程会在控制器返回DeferredResult后立即结束,而实际的响应体是在后续的异步工作线程(示例中的ForkJoinPool线程)中设置的。但你当前的过滤器实现中,responseWrapper.copyBodyToResponse()是在filterChain.doFilter()执行完成后立即调用的——此时异步线程还未完成响应体的写入,ContentCachingResponseWrapper的缓存区是空的,自然无法将响应体复制到原始HttpServletResponse中,导致客户端接收不到payload。

解决方案

需要监听异步请求的完成事件,在异步处理真正完成后再调用copyBodyToResponse(),确保缓存区已经有完整的响应体。修改后的过滤器代码如下:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ContentCachingFilter extends OncePerRequestFilter {
    private static final Logger logger = LoggerFactory.getLogger(ContentCachingFilter.class);
    public static final String REST_UUID = "X-request-id";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

        // 设置X-request-id逻辑保持不变
        if (request.getHeader(REST_UUID) == null) {
            String uuid = UUID.randomUUID().toString();
            request.setAttribute(REST_UUID, uuid);
            response.setHeader(REST_UUID, uuid);
        } else {
            String headerParam = request.getHeader(REST_UUID);
            request.setAttribute(REST_UUID, headerParam);
            response.setHeader(REST_UUID, headerParam);
        }

        logger.info("Before ContentCachingFilter");
        filterChain.doFilter(request, responseWrapper);
        logger.info("After ContentCachingFilter");

        // 处理异步请求的情况
        if (request.isAsyncStarted()) {
            request.getAsyncContext().addListener(new AsyncListener() {
                @Override
                public void onComplete(AsyncEvent event) throws IOException {
                    // 异步处理完成后,复制缓存的响应体到原始response
                    responseWrapper.copyBodyToResponse();
                }

                @Override
                public void onTimeout(AsyncEvent event) throws IOException {
                    // 超时场景也需要复制,确保超时响应能正常返回
                    responseWrapper.copyBodyToResponse();
                }

                @Override
                public void onError(AsyncEvent event) throws IOException {
                    // 异常场景同样处理
                    responseWrapper.copyBodyToResponse();
                }

                @Override
                public void onStartAsync(AsyncEvent event) throws IOException {
                    // 无需额外操作
                }
            });
        } else {
            // 同步请求直接复制
            responseWrapper.copyBodyToResponse();
        }
    }
}
原理说明
  • 同步请求:filterChain.doFilter()执行完成时响应体已经写入ContentCachingResponseWrapper,直接调用copyBodyToResponse()即可正常返回。
  • 异步请求:filterChain.doFilter()返回时,请求进入异步模式,响应体尚未生成。通过添加AsyncListener,在onComplete(异步处理完成)、onTimeout(超时)、onError(异常)等事件触发时,再执行copyBodyToResponse(),此时缓存区已经有完整的响应内容,能正确复制到原始响应中,客户端就能收到预期的payload。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:03:23