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

