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

Cloudflare导致流式数据合并为单块,进度条异常问题求助

Express流式接口在Cloudflare代理下被合并为单块返回的问题解决

问题描述

基于Express实现的流式返回接口,开发环境下数据分块返回,可正常驱动进度条更新。但经Cloudflare代理的生产环境中,所有数据被合并为单块返回,进度条长时间停留在0%后瞬间跳到100%。

实现代码

/**
 *
 * @param {import("express").Request} req
 * @param {import("express").Response} res
 */
async function urls_post(req, res) {
    const { id: guild } = req.params;

    /** @type {{ urls?: string[] }} */
    const { urls } = req.body;

    if (!urls?.length) return res.status(400).json({ success: false, message: "Please provide URLs to crawl" });

    const app = new URLApp(guild);

    const invalidURLs = [];

    urls.forEach((u) => {
        try {
            new URL(u);
        } catch (_) {
            invalidURLs.push(u);
        }
    });

    if (invalidURLs.length)
        return res
            .status(400)
            .json({ success: false, message: "One or many of the URLs is/are not valid", invalidURLs, urls });

    await write(res, { acknowledge: true });

    await app.resetURLs().catch(console.error);

    const guildData = await GuildModel.findOne({ guild_id: guild });

    if (!guildData) return res.end(JSON.stringify({ success: false, message: "Guild is not initiated..!" }));

    guildData.urls = urls;

    await guildData.save();

    const urlProcessGenerator = app.processDocuments(...urls);

    for await (const response of urlProcessGenerator) await write(res, response);

    res.end(JSON.stringify({ success: true, urls }));
}

/**
 * @param {import("express").Response} res
 * @param {unknown} data
 */
function write(res, data) {
    if (!res.write(JSON.stringify(data) + ",")) return new Promise((r) => res.once("drain", r));
    else return new Promise((r) => process.nextTick(r));
}

环境对比

  • 生产环境(Cloudflare代理):所有数据合并为单块返回
    生产环境返回情况

  • 开发环境:数据分块返回(期望效果)
    开发环境返回情况

问题原因

Cloudflare默认会对响应进行缓冲处理,目的是实现缓存、GZIP压缩等优化,但这会导致流式响应被暂存,直到整个请求处理完成后才一次性发送给客户端,破坏了分块返回的逻辑。

解决方案

1. 禁用Cloudflare的响应缓冲

在Cloudflare控制台配置页面规则:

  • 进入「规则」->「页面规则」,创建新规则,匹配你的流式接口路径(例如your-domain.com/api/urls/*)
  • 添加两项设置:
    • 「缓存级别」设置为绕过缓存
    • 「响应缓冲」设置为关闭
  • 保存规则并部署生效

2. 设置Express响应头

在接口中添加响应头,明确告知Cloudflare和客户端不要缓冲响应:

async function urls_post(req, res) {
    // 添加以下响应头
    res.setHeader('Transfer-Encoding', 'chunked');
    res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
    res.setHeader('Pragma', 'no-cache');
    res.setHeader('Expires', '0');

    // 原有逻辑...
}

3. 禁用流式接口的压缩中间件

如果你的Express应用全局启用了compression中间件,需要针对流式接口禁用压缩,避免压缩导致的缓冲:

const compression = require('compression');

app.use(compression({
  filter: (req, res) => {
    // 排除流式接口路径
    if (req.path.includes('/api/urls')) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

4. 优化分块写入逻辑

调整write函数,强制刷新响应缓冲区,确保分块及时发送:

function write(res, data) {
  const chunk = JSON.stringify(data) + ',';
  return new Promise((resolve) => {
    if (!res.write(chunk)) {
      res.once('drain', resolve);
    } else {
      // 强制刷新缓冲区
      res.flushHeaders();
      process.nextTick(resolve);
    }
  });
}

验证

完成上述配置后,重新测试生产环境接口,数据会恢复分块返回,进度条即可正常更新。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:47:54