POI+Servlet导出Excel遇504超时,优化后出异常求助
首先,咱们先拆解你遇到的两个核心问题:初始的504超时,以及后续异步改造后出现的ClientAbortException和ERR_INCOMPLETE_CHUNKED_ENCODING错误。
为什么会出现新的错误?
你的异步改造思路方向是对的,但几个细节处理不当导致了异常:
响应头设置时机错误
当你调用response.getOutputStream()时,Servlet容器已经开始发送HTTP响应头了。此时再设置response.setContentLength(outArray.length)完全无效,反而会和容器默认的chunked编码传输规则冲突——因为未提前指定Content-Length时,容器会自动用分块编码传输,后续强行设置长度会导致响应格式混乱,触发ERR_INCOMPLETE_CHUNKED_ENCODING。空Flush导致客户端主动断开
你在等待过程中只调用outStream.flush()但没有写入任何内容,浏览器长时间收不到响应数据,会判定连接异常并主动断开,这就是ClientAbortException的根源。HTTP头语法错误
你写的response.setHeader("Expires:", "0");多了个冒号!HTTP头的名称是Expires,不是Expires:,这个错误会导致缓存控制头完全不生效。
修正后的代码实现
下面是调整后的完整代码,解决了上述所有问题,同时优化了内存占用和资源清理:
ExecutorService threadPool = Executors.newSingleThreadExecutor(); Future<SXSSFWorkbook> future = threadPool.submit(() -> genrateExcel(id)); SXSSFWorkbook workbook = null; try { // 先设置所有响应头,再获取输出流(必须保证头在流之前发送) response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Expires", "0"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(excelFileName, "UTF-8")); // 不提前设置Content-Length,让容器自动用chunked编码传输 OutputStream outStream = response.getOutputStream(); int waitTimes = 0; final int MAX_WAIT = 50; // 最多等待250秒(50*5秒) boolean isTimeout = false; while (waitTimes < MAX_WAIT) { if (future.isDone()) { workbook = future.get(); break; } // 发送心跳数据(换行符),让浏览器知道连接还活着 outStream.write("\r\n".getBytes(StandardCharsets.UTF_8)); outStream.flush(); Thread.sleep(5000); waitTimes++; } // 处理超时情况:生成带提示的Excel if (workbook == null) { isTimeout = true; workbook = new SXSSFWorkbook(); Sheet tipSheet = workbook.createSheet("提示"); Row row = tipSheet.createRow(0); Cell cell = row.createCell(0); cell.setCellValue("Excel生成超时,请稍后重试"); } // 直接写入响应流,避免中转ByteArrayOutputStream浪费内存 workbook.write(outStream); outStream.flush(); } catch (Exception e) { e.printStackTrace(); // 向客户端返回错误提示 response.setContentType("text/plain;charset=UTF-8"); response.getOutputStream().write("Excel生成失败,请稍后重试".getBytes(StandardCharsets.UTF_8)); response.getOutputStream().flush(); } finally { // 强制清理资源 if (workbook != null) { workbook.dispose(); try { workbook.close(); } catch (IOException e) { e.printStackTrace(); } } // 关闭线程池,避免资源泄漏 threadPool.shutdown(); }
额外优化建议
如果Excel生成耗时极长(比如超过5分钟),这种保持HTTP连接的方式还是有风险,建议换成异步生成+通知机制:
- 用户发起请求后,服务器立即返回一个任务ID,后台线程开始生成Excel
- 生成完成后,通过邮件、站内信通知用户,或者提供一个查询接口让用户轮询状态
- 用户点击下载链接直接获取已生成好的文件
另外,使用SXSSFWorkbook时,建议在创建时指定行缓存大小(比如new SXSSFWorkbook(100)),减少内存占用,提升生成效率。
内容的提问来源于stack exchange,提问作者GWLAN




