You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

POI+Servlet导出Excel遇504超时,优化后出异常求助

问题分析与解决方案

首先,咱们先拆解你遇到的两个核心问题:初始的504超时,以及后续异步改造后出现的ClientAbortExceptionERR_INCOMPLETE_CHUNKED_ENCODING错误。


为什么会出现新的错误?

你的异步改造思路方向是对的,但几个细节处理不当导致了异常:

  1. 响应头设置时机错误
    当你调用response.getOutputStream()时,Servlet容器已经开始发送HTTP响应头了。此时再设置response.setContentLength(outArray.length)完全无效,反而会和容器默认的chunked编码传输规则冲突——因为未提前指定Content-Length时,容器会自动用分块编码传输,后续强行设置长度会导致响应格式混乱,触发ERR_INCOMPLETE_CHUNKED_ENCODING

  2. 空Flush导致客户端主动断开
    你在等待过程中只调用outStream.flush()但没有写入任何内容,浏览器长时间收不到响应数据,会判定连接异常并主动断开,这就是ClientAbortException的根源。

  3. 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连接的方式还是有风险,建议换成异步生成+通知机制

  1. 用户发起请求后,服务器立即返回一个任务ID,后台线程开始生成Excel
  2. 生成完成后,通过邮件、站内信通知用户,或者提供一个查询接口让用户轮询状态
  3. 用户点击下载链接直接获取已生成好的文件

另外,使用SXSSFWorkbook时,建议在创建时指定行缓存大小(比如new SXSSFWorkbook(100)),减少内存占用,提升生成效率。

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

火山引擎 最新活动