使用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
相关产品推荐
相关产品推荐

